/src/ghostpdl/psi/imainarg.c
Line | Count | Source |
1 | | /* Copyright (C) 2001-2026 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* Command line parsing and dispatching */ |
18 | | |
19 | | #include "ctype_.h" |
20 | | #include "memory_.h" |
21 | | #include "string_.h" |
22 | | #include <stdlib.h> /* for qsort */ |
23 | | |
24 | | #include "ghost.h" |
25 | | #include "gp.h" |
26 | | #include "gsargs.h" |
27 | | #include "gscdefs.h" |
28 | | #include "gsmalloc.h" /* for gs_malloc_limit */ |
29 | | #include "gsmdebug.h" |
30 | | #include "gspaint.h" /* for gs_erasepage */ |
31 | | #include "gxdevice.h" |
32 | | #include "gxdevmem.h" |
33 | | #include "gsdevice.h" |
34 | | #include "gxdevsop.h" /* for gxdso_* enums */ |
35 | | #include "gxclpage.h" |
36 | | #include "gdevprn.h" |
37 | | #include "stream.h" |
38 | | #include "ierrors.h" |
39 | | #include "estack.h" |
40 | | #include "ialloc.h" |
41 | | #include "strimpl.h" /* for sfilter.h */ |
42 | | #include "sfilter.h" /* for iscan.h */ |
43 | | #include "ostack.h" /* must precede iscan.h */ |
44 | | #include "iscan.h" |
45 | | #include "iconf.h" |
46 | | #include "imain.h" |
47 | | #include "imainarg.h" |
48 | | #include "iapi.h" |
49 | | #include "iminst.h" |
50 | | #include "iname.h" |
51 | | #include "store.h" |
52 | | #include "files.h" /* requires stream.h */ |
53 | | #include "interp.h" |
54 | | #include "iutil.h" |
55 | | #include "ivmspace.h" |
56 | | |
57 | | /* Import operator procedures */ |
58 | | extern int zflush(i_ctx_t *); |
59 | | extern int zflushpage(i_ctx_t *); |
60 | | |
61 | | #ifndef GS_LIB |
62 | 162k | # define GS_LIB "GS_LIB" |
63 | | #endif |
64 | | |
65 | | #ifndef GS_OPTIONS |
66 | 162k | # define GS_OPTIONS "GS_OPTIONS" |
67 | | #endif |
68 | | |
69 | | /* This is now the default number of entries we initially |
70 | | * allocate for the search path array objects. The arrays |
71 | | * will now enlarge as required - |
72 | | * see imain.c: file_path_add()/extend_path_list_container() |
73 | | */ |
74 | | |
75 | | #ifndef GS_MAX_LIB_DIRS |
76 | 162k | # define GS_MAX_LIB_DIRS 25 |
77 | | #endif |
78 | | |
79 | 0 | #define MAX_BUFFERED_SIZE 1024 |
80 | | |
81 | | /* Note: sscanf incorrectly defines its first argument as char * */ |
82 | | /* rather than const char *. This accounts for the ugly casts below. */ |
83 | | |
84 | | /* Redefine puts to use outprintf, */ |
85 | | /* so it will work even without stdio. */ |
86 | | #undef puts |
87 | 0 | #define puts(mem, str) outprintf(mem, "%s\n", str) |
88 | | |
89 | | /* Forward references */ |
90 | 1.00k | #define runInit 1 |
91 | 293k | #define runFlush 2 |
92 | | #define runBuffer 4 |
93 | | static int swproc(gs_main_instance *, const char *, arg_list *); |
94 | | static int argproc(gs_main_instance *, const char *); |
95 | | static int run_buffered(gs_main_instance *, const char *); |
96 | | static int esc_strlen(const char *); |
97 | | static void esc_strcat(char *, const char *); |
98 | | static int runarg(gs_main_instance *, const char *, const char *, const char *, int, int, int *, ref *); |
99 | | static int run_string(gs_main_instance *, const char *, int, int, int *, ref *); |
100 | | static int run_finish(gs_main_instance *, int, int, ref *); |
101 | | static int try_stdout_redirect(gs_main_instance * minst, |
102 | | const char *command, const char *filename); |
103 | | |
104 | | /* Forward references for help printout */ |
105 | | static void print_help(gs_main_instance *); |
106 | | static void print_revision(const gs_main_instance *); |
107 | | static void print_version(const gs_main_instance *); |
108 | | static void print_usage(const gs_main_instance *); |
109 | | static void print_devices(const gs_main_instance *); |
110 | | static void print_emulators(const gs_main_instance *); |
111 | | static void print_paths(gs_main_instance *); |
112 | | static void print_help_trailer(const gs_main_instance *); |
113 | | |
114 | | /* ------ Main program ------ */ |
115 | | |
116 | | /* Process the command line with a given instance. */ |
117 | | static stream * |
118 | | gs_main_arg_sopen(const char *fname, void *vminst) |
119 | 0 | { |
120 | 0 | gs_main_set_lib_paths((gs_main_instance *) vminst); |
121 | 0 | return lib_sopen(&((gs_main_instance *)vminst)->lib_path, |
122 | 0 | ((gs_main_instance *)vminst)->heap, fname); |
123 | 0 | } |
124 | | static void |
125 | | set_debug_flags(const char *arg, char *flags) |
126 | 0 | { |
127 | 0 | byte value = (*arg == '-' ? (++arg, 0) : 0xff); |
128 | |
|
129 | 0 | while (*arg) |
130 | 0 | flags[*arg++ & 127] = value; |
131 | 0 | } |
132 | | |
133 | | int |
134 | | gs_main_init_with_args01(gs_main_instance * minst, int argc, char *argv[]) |
135 | 162k | { |
136 | 162k | const char *arg; |
137 | 162k | arg_list args; |
138 | 162k | int code; |
139 | 162k | int have_dumped_args = 0; |
140 | | |
141 | | /* Now we actually process them */ |
142 | 162k | code = arg_init(&args, (const char **)argv, argc, |
143 | 162k | gs_main_arg_sopen, (void *)minst, |
144 | 162k | minst->get_codepoint, |
145 | 162k | minst->heap); |
146 | 162k | if (code < 0) |
147 | 0 | return code; |
148 | 162k | code = gs_main_init0(minst, 0, 0, 0, GS_MAX_LIB_DIRS); |
149 | 162k | if (code < 0) |
150 | 0 | return code; |
151 | | /* This first check is not needed on VMS since GS_LIB evaluates to the same |
152 | | value as that returned by gs_lib_default_path. Also, since GS_LIB is |
153 | | defined as a searchlist logical and getenv only returns the first entry |
154 | | in the searchlist, it really doesn't make sense to search that particular |
155 | | directory twice. |
156 | | */ |
157 | 162k | #ifndef __VMS |
158 | 162k | { |
159 | 162k | int len = 0; |
160 | 162k | int code = gp_getenv(GS_LIB, (char *)0, &len); |
161 | | |
162 | 162k | if (code < 0) { /* key present, value doesn't fit */ |
163 | 0 | char *path = (char *)gs_alloc_bytes(minst->heap, len, "GS_LIB"); |
164 | |
|
165 | 0 | if (path == NULL) |
166 | 0 | return_error(gs_error_VMerror); |
167 | | |
168 | 0 | gp_getenv(GS_LIB, path, &len); /* can't fail */ |
169 | 0 | minst->lib_path.env = path; |
170 | 0 | } |
171 | 162k | } |
172 | 162k | #endif /* __VMS */ |
173 | 162k | minst->lib_path.final = gs_lib_default_path; |
174 | 162k | code = gs_main_set_lib_paths(minst); |
175 | 162k | if (code < 0) |
176 | 0 | goto error; |
177 | | /* Prescan the command line for --help and --version. */ |
178 | 162k | { |
179 | 162k | int i; |
180 | 162k | bool helping = false; |
181 | | |
182 | 2.66M | for (i = 1; i < argc; ++i) { |
183 | 2.50M | if (!arg_strcmp(&args, argv[i], "--")) { |
184 | | /* A PostScript program will be interpreting all the */ |
185 | | /* remaining switches, so stop scanning. */ |
186 | 0 | helping = false; |
187 | 0 | break; |
188 | 2.50M | } else if (!arg_strcmp(&args, argv[i], "--help")) { |
189 | 0 | print_help(minst); |
190 | 0 | helping = true; |
191 | 2.50M | } else if (!arg_strcmp(&args, argv[i], "--debug")) { |
192 | 0 | gs_debug_flags_list(minst->heap); |
193 | 0 | helping = true; |
194 | 2.50M | } else if (!arg_strcmp(&args, argv[i], "--version")) { |
195 | 0 | print_version(minst); |
196 | 0 | puts(minst->heap, ""); /* \n */ |
197 | 0 | helping = true; |
198 | 0 | } |
199 | 2.50M | } |
200 | 162k | if (helping) { |
201 | 0 | code = gs_note_error(gs_error_Info); |
202 | 0 | goto error; |
203 | 0 | } |
204 | 162k | } |
205 | | /* Execute files named in the command line, */ |
206 | | /* processing options along the way. */ |
207 | | /* Wait until the first file name (or the end */ |
208 | | /* of the line) to finish initialization. */ |
209 | 162k | minst->run_start = true; |
210 | | |
211 | 162k | { |
212 | 162k | int len = 0; |
213 | 162k | int code = gp_getenv(GS_OPTIONS, (char *)0, &len); |
214 | | |
215 | 162k | if (code < 0) { /* key present, value doesn't fit */ |
216 | 0 | char *opts = |
217 | 0 | (char *)gs_alloc_bytes(minst->heap, len, "GS_OPTIONS"); |
218 | |
|
219 | 0 | if (opts == NULL) { |
220 | 0 | code = gs_note_error(gs_error_VMerror); |
221 | 0 | goto error; |
222 | 0 | } |
223 | 0 | gp_getenv(GS_OPTIONS, opts, &len); /* can't fail */ |
224 | 0 | if (arg_push_decoded_memory_string(&args, opts, false, true, minst->heap)) { |
225 | 0 | if (opts != NULL) |
226 | 0 | gs_free_object(minst->heap, opts, "error in gs_main_init_with_args"); |
227 | 0 | code = gs_note_error(gs_error_Fatal); |
228 | 0 | goto error; |
229 | 0 | } |
230 | 0 | } |
231 | 162k | } |
232 | 2.62M | while ((code = arg_next(&args, (const char **)&arg, minst->heap)) > 0) { |
233 | 2.50M | code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, arg); |
234 | 2.50M | if (code < 0) |
235 | 0 | goto error; |
236 | 2.50M | switch (*arg) { |
237 | 2.50M | case '-': |
238 | 2.50M | code = swproc(minst, arg, &args); |
239 | 2.50M | if (code < 0) |
240 | 38.0k | goto error; |
241 | 2.46M | if (code > 0) |
242 | 0 | outprintf(minst->heap, "Unknown switch %s - ignoring\n", arg); |
243 | 2.46M | if (gs_debug[':'] && !have_dumped_args) { |
244 | 0 | int i; |
245 | |
|
246 | 0 | if (gs_debug_c(gs_debug_flag_init_details)) |
247 | 0 | dmprintf1(minst->heap, "%% Args passed to instance "PRI_INTPTR": ", |
248 | 0 | (intptr_t)minst); |
249 | 0 | for (i=1; i<argc; i++) |
250 | 0 | dmprintf1(minst->heap, "%s ", argv[i]); |
251 | 0 | dmprintf(minst->heap, "\n"); |
252 | 0 | have_dumped_args = 1; |
253 | 0 | } |
254 | 2.46M | break; |
255 | 0 | default: |
256 | | /* default is to treat this as a file name to be run */ |
257 | 0 | code = argproc(minst, arg); |
258 | 0 | if (code < 0) |
259 | 0 | goto error; |
260 | 0 | if (minst->saved_pages_test_mode) { |
261 | 0 | gx_device *pdev; |
262 | 0 | int ret; |
263 | 0 | gxdso_device_child_request child_dev_data; |
264 | | |
265 | | /* get the real target (printer) device */ |
266 | 0 | pdev = gs_currentdevice(minst->i_ctx_p->pgs); |
267 | 0 | do { |
268 | 0 | child_dev_data.target = pdev; |
269 | 0 | ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, |
270 | 0 | sizeof(child_dev_data)); |
271 | 0 | if (ret > 0) |
272 | 0 | pdev = child_dev_data.target; |
273 | 0 | } while ((ret > 0) && (child_dev_data.n != 0)); |
274 | 0 | if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, |
275 | 0 | (byte *)"print normal flush", 18)) < 0) |
276 | 0 | goto error; |
277 | 0 | if (code > 0) |
278 | 0 | if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) |
279 | 0 | goto error; |
280 | 0 | } |
281 | 2.50M | } |
282 | 2.50M | } |
283 | | |
284 | 124k | return code; |
285 | | |
286 | 38.0k | error: |
287 | 38.0k | if (minst->lib_path.env != NULL) { |
288 | 0 | gs_free_object(minst->heap, (char *)minst->lib_path.env, "error in gs_main_init_with_args"); |
289 | 0 | minst->lib_path.env = NULL; |
290 | 0 | } |
291 | 38.0k | return code; |
292 | 162k | } |
293 | | |
294 | | int |
295 | | gs_main_init_with_args2(gs_main_instance * minst) |
296 | 124k | { |
297 | 124k | int code; |
298 | | |
299 | 124k | code = gs_main_init2(minst); |
300 | 124k | if (code < 0) |
301 | 0 | return code; |
302 | | |
303 | 124k | if (!minst->run_start) |
304 | 108k | return gs_error_Quit; |
305 | 16.0k | return code; |
306 | 124k | } |
307 | | |
308 | | int |
309 | | gs_main_init_with_args(gs_main_instance * minst, int argc, char *argv[]) |
310 | 146k | { |
311 | 146k | int code = gs_main_init_with_args01(minst, argc, argv); |
312 | | |
313 | 146k | if (code < 0) |
314 | 38.0k | return code; |
315 | 108k | return gs_main_init_with_args2(minst); |
316 | 146k | } |
317 | | |
318 | | /* |
319 | | * Specify a decoding function |
320 | | */ |
321 | | void gs_main_inst_arg_decode(gs_main_instance * minst, |
322 | | gs_arg_get_codepoint *get_codepoint) |
323 | 324k | { |
324 | 324k | minst->get_codepoint = get_codepoint; |
325 | 324k | } |
326 | | |
327 | | gs_arg_get_codepoint *gs_main_inst_get_arg_decode(gs_main_instance * minst) |
328 | 0 | { |
329 | 0 | return minst->get_codepoint; |
330 | 0 | } |
331 | | |
332 | | /* |
333 | | * Run the 'start' procedure (after processing the command line). |
334 | | */ |
335 | | int |
336 | | gs_main_run_start(gs_main_instance * minst) |
337 | 0 | { |
338 | 0 | return run_string(minst, "systemdict /start get exec", runFlush, minst->user_errors, NULL, NULL); |
339 | 0 | } |
340 | | |
341 | | static int |
342 | | do_arg_match(const char **arg, const char *match, size_t match_len) |
343 | 0 | { |
344 | 0 | const char *s = *arg; |
345 | 0 | if (strncmp(s, match, match_len) != 0) |
346 | 0 | return 0; |
347 | 0 | s += match_len; |
348 | 0 | if (*s == '=' || *s == '#') |
349 | 0 | *arg = ++s; |
350 | 0 | else if (*s != 0) |
351 | 0 | return 0; |
352 | 0 | else |
353 | 0 | *arg = NULL; |
354 | 0 | return 1; |
355 | 0 | } |
356 | | |
357 | | #ifdef GS_HAS_THREADS |
358 | | static void |
359 | | trigger_timeout(void *data) |
360 | 0 | { |
361 | 0 | *(int *)data = 1; |
362 | 0 | } |
363 | | #endif |
364 | | |
365 | 0 | #define arg_match(A, B) do_arg_match(A, B, sizeof(B)-1) |
366 | | |
367 | | /* Process switches. Return 0 if processed, 1 for unknown switch, */ |
368 | | /* <0 if error. */ |
369 | | static int |
370 | | swproc(gs_main_instance * minst, const char *arg, arg_list * pal) |
371 | 2.64M | { |
372 | 2.64M | char sw = arg[1]; |
373 | 2.64M | ref vtrue; |
374 | 2.64M | int code = 0; |
375 | | |
376 | 2.64M | make_true(&vtrue); |
377 | 2.64M | arg += 2; /* skip - and letter */ |
378 | 2.64M | switch (sw) { |
379 | 0 | default: |
380 | 0 | return 1; |
381 | 0 | case 0: /* read stdin as a file char-by-char */ |
382 | | /* This is a ******HACK****** for Ghostview. */ |
383 | 0 | minst->heap->gs_lib_ctx->core->stdin_is_interactive = true; |
384 | 0 | goto run_stdin; |
385 | 146k | case '_': /* read stdin with normal buffering */ |
386 | 146k | minst->heap->gs_lib_ctx->core->stdin_is_interactive = false; |
387 | 146k | run_stdin: |
388 | 146k | minst->run_start = false; /* don't run 'start' */ |
389 | | /* Set NOPAUSE so showpage won't try to read from stdin. */ |
390 | 146k | code = swproc(minst, "-dNOPAUSE", pal); |
391 | 146k | if (code) |
392 | 0 | return code; |
393 | 146k | code = gs_main_init2(minst); /* Finish initialization */ |
394 | 146k | if (code < 0) |
395 | 385 | return code; |
396 | | |
397 | 145k | code = run_string(minst, ".runstdin", runFlush, minst->user_errors, NULL, NULL); |
398 | 145k | if (code < 0) |
399 | 37.6k | return code; |
400 | | /* If in saved_pages_test_mode, print and flush previous job before the next file */ |
401 | 108k | if (minst->saved_pages_test_mode) { |
402 | 0 | gx_device *pdev; |
403 | 0 | int ret; |
404 | 0 | gxdso_device_child_request child_dev_data; |
405 | | |
406 | | /* get the real target (printer) device */ |
407 | 0 | pdev = gs_currentdevice(minst->i_ctx_p->pgs); |
408 | 0 | do { |
409 | 0 | child_dev_data.target = pdev; |
410 | 0 | ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, |
411 | 0 | sizeof(child_dev_data)); |
412 | 0 | if (ret > 0) |
413 | 0 | pdev = child_dev_data.target; |
414 | 0 | } while ((ret > 0) && (child_dev_data.n != 0)); |
415 | 0 | if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, |
416 | 0 | (byte *)"print normal flush", 18)) < 0) |
417 | 0 | return code; |
418 | 0 | if (code > 0) |
419 | 0 | if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) |
420 | 0 | return code; |
421 | 0 | } |
422 | 108k | break; |
423 | 108k | case '-': /* run with command line args */ |
424 | 0 | if (strncmp(arg, "debug=", 6) == 0) { |
425 | 0 | code = gs_debug_flags_parse(minst->heap, arg+6); |
426 | 0 | if (code < 0) |
427 | 0 | return code; |
428 | 0 | break; |
429 | 0 | } else if (strncmp(arg, "saved-pages=", 12) == 0) { |
430 | 0 | gx_device *pdev; |
431 | | |
432 | | /* If init2 not yet done, just save the argument for processing then */ |
433 | 0 | if (minst->init_done < 2) { |
434 | 0 | if (minst->saved_pages_initial_arg == NULL) { |
435 | | /* Tuck the parameters away for later when init2 is done (usually "begin") */ |
436 | 0 | minst->saved_pages_initial_arg = (char *)gs_alloc_bytes(minst->heap, |
437 | 0 | 1+strlen((char *)arg+(size_t)12), |
438 | 0 | "GS_OPTIONS"); |
439 | 0 | if (minst->saved_pages_initial_arg != NULL) { |
440 | 0 | strcpy(minst->saved_pages_initial_arg,(char *)arg+12); |
441 | 0 | } else { |
442 | 0 | outprintf(minst->heap, |
443 | 0 | " saved_pages_initial_arg larger than expected\n"); |
444 | 0 | arg_finit(pal); |
445 | 0 | return gs_error_Fatal; |
446 | 0 | } |
447 | 0 | } else { |
448 | 0 | outprintf(minst->heap, |
449 | 0 | " Only one --saved-pages=... command allowed before processing input\n"); |
450 | 0 | arg_finit(pal); |
451 | 0 | return gs_error_Fatal; |
452 | 0 | } |
453 | 0 | } else { |
454 | 0 | int ret; |
455 | 0 | gxdso_device_child_request child_dev_data; |
456 | | |
457 | | /* get the current device */ |
458 | 0 | pdev = gs_currentdevice(minst->i_ctx_p->pgs); |
459 | 0 | if (dev_proc(pdev, dev_spec_op)(pdev, gxdso_supports_saved_pages, NULL, 0) <= 0) { |
460 | 0 | outprintf(minst->heap, |
461 | 0 | " --saved-pages not supported by the '%s' device.\n", |
462 | 0 | pdev->dname); |
463 | 0 | arg_finit(pal); |
464 | 0 | return gs_error_Fatal; |
465 | 0 | } |
466 | | /* get the real target (printer) device */ |
467 | 0 | do { |
468 | 0 | child_dev_data.target = pdev; |
469 | 0 | ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, |
470 | 0 | sizeof(child_dev_data)); |
471 | 0 | if (ret > 0) |
472 | 0 | pdev = child_dev_data.target; |
473 | 0 | } while ((ret > 0) && (child_dev_data.n != 0)); |
474 | 0 | if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, |
475 | 0 | (byte *)arg+12, strlen(arg+12))) < 0) |
476 | 0 | return code; |
477 | 0 | if (code > 0) |
478 | 0 | if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) |
479 | 0 | return code; |
480 | 0 | } |
481 | 0 | break; |
482 | | /* The following code is only to allow regression testing of saved-pages */ |
483 | 0 | } else if (strncmp(arg, "saved-pages-test", 16) == 0) { |
484 | 0 | minst->saved_pages_test_mode = true; |
485 | 0 | break; |
486 | | /* Now handle the explicitly added paths to the file control lists */ |
487 | 0 | } else if (arg_match(&arg, "permit-file-read")) { |
488 | 0 | code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_reading); |
489 | 0 | if (code < 0) return code; |
490 | 0 | break; |
491 | 0 | } else if (arg_match(&arg, "permit-file-write")) { |
492 | 0 | code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_writing); |
493 | 0 | if (code < 0) return code; |
494 | 0 | break; |
495 | 0 | } else if (arg_match(&arg, "permit-file-control")) { |
496 | 0 | code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_control); |
497 | 0 | if (code < 0) return code; |
498 | 0 | break; |
499 | 0 | } else if (arg_match(&arg, "permit-file-all")) { |
500 | 0 | code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_reading); |
501 | 0 | if (code < 0) return code; |
502 | 0 | code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_writing); |
503 | 0 | if (code < 0) return code; |
504 | 0 | code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_control); |
505 | 0 | if (code < 0) return code; |
506 | 0 | break; |
507 | 0 | } else if (arg_match(&arg, "permit-devices")) { |
508 | 0 | code = gs_add_explicit_permitted_device(minst->heap, arg); |
509 | 0 | if (code < 0) return code; |
510 | 0 | break; |
511 | 0 | } else if (strncmp(arg, "timeout=", 8) == 0) { |
512 | 0 | #ifdef GS_HAS_THREADS |
513 | 0 | int secs = atoi(arg+8); |
514 | 0 | if (secs == 0) |
515 | 0 | { |
516 | 0 | outprintf(minst->heap, |
517 | 0 | " Illegal timeout value '%s' - should be in seconds.\n", |
518 | 0 | arg+8); |
519 | 0 | arg_finit(pal); |
520 | 0 | return gs_error_Fatal; |
521 | 0 | } |
522 | 0 | code = gp_alarm_start(&minst->timeout, minst->heap, secs, trigger_timeout, &minst->heap->gs_lib_ctx->core->timedout); |
523 | 0 | if (code < 0) return code; |
524 | 0 | break; |
525 | | #else |
526 | | outprintf(minst->heap, "Timeouts not supported on builds without threading\n"); |
527 | | return gs_error_Fatal; |
528 | | #endif |
529 | 0 | } |
530 | 0 | if (*arg != 0) { |
531 | | /* Unmatched switch. */ |
532 | 0 | outprintf(minst->heap, |
533 | 0 | " Unknown switch '--%s'.\n", |
534 | 0 | arg); |
535 | 0 | arg_finit(pal); |
536 | 0 | return gs_error_Fatal; |
537 | 0 | } |
538 | | /* FALLTHROUGH */ |
539 | 0 | case '+': |
540 | 0 | pal->expand_ats = false; |
541 | | /* FALLTHROUGH */ |
542 | 0 | case '@': /* ditto with @-expansion */ |
543 | 0 | { |
544 | 0 | char *psarg; |
545 | |
|
546 | 0 | code = arg_next(pal, (const char **)&psarg, minst->heap); |
547 | | /* Don't stash the @ file name */ |
548 | |
|
549 | 0 | if (code < 0) |
550 | 0 | return gs_error_Fatal; |
551 | 0 | if (psarg == NULL) { |
552 | 0 | outprintf(minst->heap, "Usage: gs ... -%c file.ps arg1 ... argn\n", sw); |
553 | 0 | arg_finit(pal); |
554 | 0 | return gs_error_Fatal; |
555 | 0 | } |
556 | 0 | psarg = arg_copy(psarg, minst->heap); |
557 | 0 | if (psarg == NULL) |
558 | 0 | code = gs_error_Fatal; |
559 | 0 | else |
560 | 0 | code = gs_main_init2(minst); |
561 | 0 | if (code >= 0) |
562 | 0 | code = run_string(minst, "userdict/ARGUMENTS[", 0, minst->user_errors, NULL, NULL); |
563 | 0 | if (code >= 0) |
564 | 0 | while ((code = arg_next(pal, (const char **)&arg, minst->heap)) > 0) { |
565 | 0 | code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, arg); |
566 | 0 | if (code < 0) |
567 | 0 | break; |
568 | 0 | code = runarg(minst, "", arg, "", runInit, minst->user_errors, NULL, NULL); |
569 | 0 | if (code < 0) |
570 | 0 | break; |
571 | 0 | } |
572 | 0 | if (code >= 0) |
573 | 0 | code = run_string(minst, "]put", 0, minst->user_errors, NULL, NULL); |
574 | 0 | if (code >= 0) |
575 | 0 | code = argproc(minst, psarg); |
576 | 0 | arg_free((char *)psarg, minst->heap); |
577 | 0 | if (code >= 0) |
578 | 0 | code = gs_error_Quit; |
579 | |
|
580 | 0 | return code; |
581 | 0 | } |
582 | 0 | case 'B': /* set run_string buffer size */ |
583 | 0 | if (*arg == '-') |
584 | 0 | minst->run_buffer_size = 0; |
585 | 0 | else { |
586 | 0 | uint bsize; |
587 | |
|
588 | 0 | if (sscanf((const char *)arg, "%u", &bsize) != 1 || |
589 | 0 | bsize <= 0 || bsize > MAX_BUFFERED_SIZE |
590 | 0 | ) { |
591 | 0 | outprintf(minst->heap, |
592 | 0 | "-B must be followed by - or size between 1 and %u\n", |
593 | 0 | MAX_BUFFERED_SIZE); |
594 | 0 | return gs_error_Fatal; |
595 | 0 | } |
596 | 0 | minst->run_buffer_size = bsize; |
597 | 0 | } |
598 | 0 | break; |
599 | 0 | case 'c': /* code follows */ |
600 | 0 | { |
601 | 0 | bool ats = pal->expand_ats; |
602 | |
|
603 | 0 | code = gs_main_init2(minst); |
604 | 0 | if (code < 0) |
605 | 0 | return code; |
606 | 0 | pal->expand_ats = false; |
607 | 0 | while ((code = arg_next(pal, (const char **)&arg, minst->heap)) > 0) { |
608 | 0 | if (arg[0] == '@' || |
609 | 0 | (arg[0] == '-' && !isdigit((unsigned char)arg[1])) |
610 | 0 | ) |
611 | 0 | break; |
612 | 0 | code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, "?"); |
613 | 0 | if (code < 0) |
614 | 0 | return code; |
615 | 0 | code = runarg(minst, "", arg, ".runstring", 0, minst->user_errors, NULL, NULL); |
616 | 0 | if (code < 0) |
617 | 0 | return code; |
618 | 0 | } |
619 | 0 | if (code < 0) |
620 | 0 | return gs_error_Fatal; |
621 | 0 | if (arg != NULL) { |
622 | 0 | char *p = arg_copy(arg, minst->heap); |
623 | 0 | if (p == NULL) |
624 | 0 | return gs_error_Fatal; |
625 | 0 | arg_push_string(pal, p, true); |
626 | 0 | } |
627 | 0 | pal->expand_ats = ats; |
628 | 0 | break; |
629 | 0 | } |
630 | 0 | case 'f': /* run file of arbitrary name */ |
631 | 0 | if (*arg != 0) { |
632 | 0 | code = argproc(minst, arg); |
633 | 0 | if (code < 0) |
634 | 0 | return code; |
635 | | /* If in saved_pages_test_mode, print and flush previous job before the next file */ |
636 | 0 | if (minst->saved_pages_test_mode) { |
637 | 0 | gx_device *pdev; |
638 | 0 | int ret; |
639 | 0 | gxdso_device_child_request child_dev_data; |
640 | | |
641 | | /* get the real target (printer) device */ |
642 | 0 | pdev = gs_currentdevice(minst->i_ctx_p->pgs); |
643 | 0 | do { |
644 | 0 | child_dev_data.target = pdev; |
645 | 0 | ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, |
646 | 0 | sizeof(child_dev_data)); |
647 | 0 | if (ret > 0) |
648 | 0 | pdev = child_dev_data.target; |
649 | 0 | } while ((ret > 0) && (child_dev_data.n != 0)); |
650 | 0 | if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, |
651 | 0 | (byte *)"print normal flush", 18)) < 0) |
652 | 0 | return code; |
653 | 0 | if (code > 0) |
654 | 0 | if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) |
655 | 0 | return code; |
656 | 0 | } |
657 | 0 | } |
658 | 0 | break; |
659 | 0 | case 'F': /* run file with buffer_size = 1 */ |
660 | 0 | if (!*arg) { |
661 | 0 | puts(minst->heap, "-F requires a file name"); |
662 | 0 | return gs_error_Fatal; |
663 | 0 | } { |
664 | 0 | uint bsize = minst->run_buffer_size; |
665 | |
|
666 | 0 | minst->run_buffer_size = 1; |
667 | 0 | code = argproc(minst, arg); |
668 | 0 | minst->run_buffer_size = bsize; |
669 | 0 | if (code < 0) |
670 | 0 | return code; |
671 | | /* If in saved_pages_test_mode, print and flush previous job before the next file */ |
672 | 0 | if (minst->saved_pages_test_mode) { |
673 | 0 | gx_device *pdev; |
674 | 0 | int ret; |
675 | 0 | gxdso_device_child_request child_dev_data; |
676 | | |
677 | | /* get the real target (printer) device */ |
678 | 0 | pdev = gs_currentdevice(minst->i_ctx_p->pgs); |
679 | 0 | do { |
680 | 0 | child_dev_data.target = pdev; |
681 | 0 | ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, |
682 | 0 | sizeof(child_dev_data)); |
683 | 0 | if (ret > 0) |
684 | 0 | pdev = child_dev_data.target; |
685 | 0 | } while ((ret > 0) && (child_dev_data.n != 0)); |
686 | 0 | if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, |
687 | 0 | (byte *)"print normal flush", 18)) < 0) |
688 | 0 | return code; |
689 | 0 | if (code > 0) |
690 | 0 | if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) |
691 | 0 | return code; |
692 | 0 | } |
693 | 0 | } |
694 | 0 | break; |
695 | 0 | case 'g': /* define device geometry */ |
696 | 0 | { |
697 | 0 | long dimensions[2]; |
698 | |
|
699 | 0 | if ((code = gs_main_init1(minst)) < 0) |
700 | 0 | return code; |
701 | 0 | if (sscanf((const char *)arg, "%ldx%ld", &dimensions[0], &dimensions[1]) != 2) { |
702 | 0 | puts(minst->heap, "-g must be followed by <width>x<height>"); |
703 | 0 | return gs_error_Fatal; |
704 | 0 | } |
705 | 0 | gs_main_force_dimensions(minst, dimensions); |
706 | 0 | break; |
707 | 0 | } |
708 | 0 | case 'h': /* print help */ |
709 | 0 | case '?': /* ditto */ |
710 | 0 | print_help(minst); |
711 | 0 | return gs_error_Info; /* show usage info on exit */ |
712 | 0 | case 'I': /* specify search path */ |
713 | 0 | { |
714 | 0 | const char *path; |
715 | |
|
716 | 0 | if (arg[0] == 0) { |
717 | 0 | code = arg_next(pal, (const char **)&path, minst->heap); |
718 | 0 | if (code < 0) |
719 | 0 | return code; |
720 | 0 | code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, "?"); |
721 | 0 | if (code < 0) |
722 | 0 | return code; |
723 | 0 | } else |
724 | 0 | path = arg; |
725 | 0 | if (path == NULL) |
726 | 0 | return gs_error_Fatal; |
727 | 0 | gs_main_add_lib_path(minst, path); |
728 | 0 | } |
729 | 0 | break; |
730 | 146k | case 'K': /* set malloc limit */ |
731 | 146k | { |
732 | 146k | long msize = 0; |
733 | 146k | gs_malloc_memory_t *rawheap = gs_malloc_wrapped_contents(minst->heap); |
734 | | |
735 | 146k | (void)sscanf((const char *)arg, "%ld", &msize); |
736 | 146k | if (msize <= 0 || msize > max_long >> 10) { |
737 | 0 | outprintf(minst->heap, "-K<numK> must have 1 <= numK <= %ld\n", |
738 | 0 | max_long >> 10); |
739 | 0 | return gs_error_Fatal; |
740 | 0 | } |
741 | 146k | rawheap->limit = msize << 10; |
742 | 146k | } |
743 | 0 | break; |
744 | 0 | case 'M': /* set memory allocation increment */ |
745 | 0 | { |
746 | 0 | unsigned msize = 0; |
747 | |
|
748 | 0 | (void)sscanf((const char *)arg, "%u", &msize); |
749 | 0 | if (msize <= 0 || msize >= (max_uint >> 10)) { |
750 | 0 | outprintf(minst->heap, "-M must be between 1 and %d\n", (int)((max_uint >> 10) - 1)); |
751 | 0 | return gs_error_Fatal; |
752 | 0 | } |
753 | 0 | minst->memory_clump_size = msize << 10; |
754 | 0 | } |
755 | 0 | break; |
756 | 0 | case 'N': /* set size of name table */ |
757 | 0 | { |
758 | 0 | unsigned int nsize = 0; |
759 | |
|
760 | 0 | (void)sscanf((const char *)arg, "%ud", &nsize); |
761 | 0 | if (nsize < 2 || nsize > (max_uint >> 10)) { |
762 | 0 | outprintf(minst->heap, "-N must be between 2 and %d\n", (int)(max_uint >> 10)); |
763 | 0 | return gs_error_Fatal; |
764 | 0 | } |
765 | 0 | minst->name_table_size = (ulong) nsize << 10; |
766 | 0 | } |
767 | 0 | break; |
768 | 0 | case 'o': /* set output file name and batch mode */ |
769 | 0 | { |
770 | 0 | i_ctx_t *i_ctx_p; |
771 | 0 | uint space; |
772 | 0 | const char *adef; |
773 | 0 | byte *str; |
774 | 0 | ref value; |
775 | 0 | int len; |
776 | |
|
777 | 0 | if ((code = gs_main_init1(minst)) < 0) |
778 | 0 | return code; |
779 | | |
780 | 0 | i_ctx_p = minst->i_ctx_p; |
781 | 0 | space = icurrent_space; |
782 | 0 | if (arg[0] == 0) { |
783 | 0 | code = arg_next(pal, (const char **)&adef, minst->heap); |
784 | 0 | if (code < 0) |
785 | 0 | return code; |
786 | 0 | if (code == 0) |
787 | 0 | return gs_error_undefinedfilename; |
788 | 0 | code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, "?"); |
789 | 0 | if (code < 0) |
790 | 0 | return code; |
791 | 0 | } else |
792 | 0 | adef = arg; |
793 | 0 | if ((code = gs_main_init1(minst)) < 0) |
794 | 0 | return code; |
795 | | |
796 | 0 | if (strlen(adef) > 0) { |
797 | 0 | code = gs_add_outputfile_control_path(minst->heap, adef); |
798 | 0 | if (code < 0) return code; |
799 | 0 | } |
800 | | |
801 | 0 | ialloc_set_space(idmemory, avm_system); |
802 | 0 | len = strlen(adef); |
803 | 0 | str = ialloc_string(len, "-o"); |
804 | 0 | if (str == NULL) |
805 | 0 | return gs_error_VMerror; |
806 | 0 | memcpy(str, adef, len); |
807 | 0 | make_const_string(&value, a_readonly | avm_system, len, str); |
808 | 0 | ialloc_set_space(idmemory, space); |
809 | 0 | i_initial_enter_name(minst->i_ctx_p, "OutputFile", &value); |
810 | 0 | i_initial_enter_name(minst->i_ctx_p, "NOPAUSE", &vtrue); |
811 | 0 | i_initial_enter_name(minst->i_ctx_p, "BATCH", &vtrue); |
812 | 0 | } |
813 | 0 | break; |
814 | 0 | case 'P': /* choose whether search '.' first */ |
815 | 0 | if (!strcmp(arg, "")) |
816 | 0 | minst->search_here_first = true; |
817 | 0 | else if (!strcmp(arg, "-")) |
818 | 0 | minst->search_here_first = false; |
819 | 0 | else { |
820 | 0 | puts(minst->heap, "Only -P or -P- is allowed."); |
821 | 0 | return gs_error_Fatal; |
822 | 0 | } |
823 | 0 | break; |
824 | 0 | case 'q': /* quiet startup */ |
825 | 0 | if ((code = gs_main_init1(minst)) < 0) |
826 | 0 | return code; |
827 | 0 | i_initial_enter_name(minst->i_ctx_p, "QUIET", &vtrue); |
828 | 0 | break; |
829 | 146k | case 'r': /* define device resolution */ |
830 | 146k | { |
831 | 146k | float res[2]; |
832 | | |
833 | 146k | if ((code = gs_main_init1(minst)) < 0) |
834 | 0 | return code; |
835 | 146k | switch (sscanf((const char *)arg, "%fx%f", &res[0], &res[1])) { |
836 | 0 | default: |
837 | 0 | puts(minst->heap, "-r must be followed by <res> or <xres>x<yres>"); |
838 | 0 | return gs_error_Fatal; |
839 | 0 | case 1: /* -r<res> */ |
840 | 0 | res[1] = res[0]; |
841 | | /* fall through */ |
842 | 146k | case 2: /* -r<xres>x<yres> */ |
843 | 146k | gs_main_force_resolutions(minst, res); |
844 | 146k | } |
845 | 146k | break; |
846 | 146k | } |
847 | 146k | case 'D': /* define name */ |
848 | 1.61M | case 'd': |
849 | 1.61M | case 'S': /* define name as string */ |
850 | 2.20M | case 's': |
851 | 2.20M | { |
852 | 2.20M | char *adef = arg_copy(arg, minst->heap); |
853 | 2.20M | char *eqp; |
854 | 2.20M | bool isd = (sw == 'D' || sw == 'd'); |
855 | 2.20M | ref value; |
856 | | |
857 | 2.20M | if (adef == NULL) |
858 | 0 | return gs_error_Fatal; |
859 | 2.20M | eqp = strchr(adef, '='); |
860 | | |
861 | 2.20M | if (eqp == NULL) |
862 | 1.03M | eqp = strchr(adef, '#'); |
863 | | /* Initialize the object memory, scanner, and */ |
864 | | /* name table now if needed. */ |
865 | 2.20M | if ((code = gs_main_init1(minst)) < 0) { |
866 | 0 | arg_free((char *)adef, minst->heap); |
867 | 0 | return code; |
868 | 0 | } |
869 | 2.20M | if (eqp == adef) { |
870 | 0 | puts(minst->heap, "Usage: -dNAME, -dNAME=TOKEN, -sNAME=STRING"); |
871 | 0 | arg_free((char *)adef, minst->heap); |
872 | 0 | return gs_error_Fatal; |
873 | 0 | } |
874 | 2.20M | if (eqp == NULL) { |
875 | 1.03M | if (isd) |
876 | 1.03M | make_true(&value); |
877 | 0 | else |
878 | 1.03M | make_empty_string(&value, a_readonly); |
879 | 1.17M | } else { |
880 | 1.17M | int code; |
881 | 1.17M | i_ctx_t *i_ctx_p = minst->i_ctx_p; |
882 | 1.17M | uint space = icurrent_space; |
883 | | |
884 | 1.17M | *eqp++ = 0; |
885 | | |
886 | 1.17M | if (strlen(adef) == 10 && strncmp(adef, "OutputFile", 10) == 0 && strlen(eqp) > 0) { |
887 | 146k | code = gs_add_outputfile_control_path(minst->heap, eqp); |
888 | 146k | if (code < 0) { |
889 | 0 | arg_free((char *)adef, minst->heap); |
890 | 0 | return code; |
891 | 0 | } |
892 | 146k | } |
893 | | |
894 | 1.17M | ialloc_set_space(idmemory, avm_system); |
895 | 1.17M | if (isd) { |
896 | 573k | int i; |
897 | 573k | int64_t num; |
898 | | |
899 | | /* Check for numbers so we can provide for suffix scalers */ |
900 | | /* Note the check for '#' is for PS "radix" numbers such as 16#ff */ |
901 | | /* and check for '.' and 'e' or 'E' which are 'real' numbers */ |
902 | 573k | if ((strchr(eqp, '#') == NULL) && (strchr(eqp, '.') == NULL) && |
903 | 573k | (strchr(eqp, 'e') == NULL) && (strchr(eqp, 'E') == NULL) && |
904 | 573k | ((i = sscanf((const char *)eqp, "%"PRIi64, &num)) == 1)) { |
905 | 573k | char suffix = eqp[strlen(eqp) - 1]; |
906 | | |
907 | 573k | switch (suffix) { |
908 | 143k | case 'k': |
909 | 143k | case 'K': |
910 | 143k | num *= 1024; |
911 | 143k | break; |
912 | 0 | case 'm': |
913 | 0 | case 'M': |
914 | 0 | num *= 1024 * 1024; |
915 | 0 | break; |
916 | 0 | case 'g': |
917 | 0 | case 'G': |
918 | | /* caveat emptor: more than 2g will overflow */ |
919 | | /* and really should produce a 'real', so don't do this */ |
920 | 0 | num *= 1024 * 1024 * 1024; |
921 | 0 | break; |
922 | 430k | default: |
923 | 430k | break; /* not a valid suffix or last char was digit */ |
924 | 573k | } |
925 | 573k | make_int(&value, (ps_int)num); |
926 | 573k | } else { |
927 | | /* use the PS scanner to capture other valid token types */ |
928 | 0 | stream astream; |
929 | 0 | scanner_state state; |
930 | |
|
931 | 0 | s_init(&astream, NULL); |
932 | 0 | sread_string(&astream, |
933 | 0 | (const byte *)eqp, strlen(eqp)); |
934 | 0 | gs_scanner_init_stream(&state, &astream); |
935 | 0 | code = gs_scan_token(minst->i_ctx_p, &value, &state); |
936 | 0 | if (code) { |
937 | 0 | outprintf(minst->heap, "Invalid value for option -d%s, -dNAME= must be followed by a valid token\n", arg); |
938 | 0 | arg_free((char *)adef, minst->heap); |
939 | 0 | return gs_error_Fatal; |
940 | 0 | } |
941 | 0 | if (r_has_type_attrs(&value, t_name, |
942 | 0 | a_executable)) { |
943 | 0 | ref nsref; |
944 | |
|
945 | 0 | name_string_ref(minst->heap, &value, &nsref); |
946 | 0 | #undef string_is |
947 | 0 | #define string_is(nsref, str, len)\ |
948 | 0 | (r_size(&(nsref)) == (len) &&\ |
949 | 0 | !strncmp((const char *)(nsref).value.const_bytes, str, (len))) |
950 | 0 | if (string_is(nsref, "null", 4)) |
951 | 0 | make_null(&value); |
952 | 0 | else if (string_is(nsref, "true", 4)) |
953 | 0 | make_true(&value); |
954 | 0 | else if (string_is(nsref, "false", 5)) |
955 | 0 | make_false(&value); |
956 | 0 | else { |
957 | 0 | outprintf(minst->heap, "Invalid value for option -d%s, use -sNAME= to define string constants\n", arg); |
958 | 0 | arg_free((char *)adef, minst->heap); |
959 | 0 | return gs_error_Fatal; |
960 | 0 | } |
961 | 0 | } |
962 | 0 | } |
963 | 598k | } else { |
964 | 598k | int len = strlen(eqp); |
965 | 598k | byte *body = ialloc_string(len, "-s"); |
966 | | |
967 | 598k | if (body == NULL) { |
968 | 0 | lprintf("Out of memory!\n"); |
969 | 0 | arg_free((char *)adef, minst->heap); |
970 | 0 | return gs_error_Fatal; |
971 | 0 | } |
972 | 598k | memcpy(body, eqp, len); |
973 | 598k | make_const_string(&value, a_readonly | avm_system, len, body); |
974 | 598k | if ((code = try_stdout_redirect(minst, adef, eqp)) < 0) { |
975 | 0 | arg_free((char *)adef, minst->heap); |
976 | 0 | return code; |
977 | 0 | } |
978 | 598k | } |
979 | 1.17M | ialloc_set_space(idmemory, space); |
980 | 1.17M | } |
981 | | /* Enter the name in systemdict. */ |
982 | 2.20M | i_initial_enter_name_copy(minst->i_ctx_p, adef, &value); |
983 | 2.20M | arg_free((char *)adef, minst->heap); |
984 | 2.20M | break; |
985 | 2.20M | } |
986 | 0 | case 'p': |
987 | 0 | { |
988 | 0 | char *adef = arg_copy(arg, minst->heap); |
989 | 0 | char *eqp; |
990 | |
|
991 | 0 | if (adef == NULL) |
992 | 0 | return gs_error_Fatal; |
993 | 0 | eqp = strchr(adef, '='); |
994 | |
|
995 | 0 | if (eqp == NULL) |
996 | 0 | eqp = strchr(adef, '#'); |
997 | 0 | if (eqp == NULL) { |
998 | 0 | outprintf(minst->heap, "Usage: -pNAME=STRING\n"); |
999 | 0 | arg_free((char *)adef, minst->heap); |
1000 | 0 | return gs_error_Fatal; |
1001 | 0 | } |
1002 | 0 | *eqp++ = 0; |
1003 | | /* Slightly uncomfortable calling back up to a higher |
1004 | | * level, but we'll live with it. */ |
1005 | 0 | code = gsapi_set_param(gs_lib_ctx_get_interp_instance(minst->heap), |
1006 | 0 | adef, eqp, gs_spt_parsed | gs_spt_more_to_come); |
1007 | 0 | if (code < 0) { |
1008 | 0 | arg_free((char *)adef, minst->heap); |
1009 | 0 | return code; |
1010 | 0 | } |
1011 | 0 | break; |
1012 | 0 | } |
1013 | 0 | case 'u': /* undefine name */ |
1014 | 0 | if (!*arg) { |
1015 | 0 | puts(minst->heap, "-u requires a name to undefine."); |
1016 | 0 | return gs_error_Fatal; |
1017 | 0 | } |
1018 | 0 | if ((code = gs_main_init1(minst)) < 0) |
1019 | 0 | return code; |
1020 | 0 | i_initial_remove_name(minst->i_ctx_p, arg); |
1021 | 0 | break; |
1022 | 0 | case 'v': /* print revision */ |
1023 | 0 | print_revision(minst); |
1024 | 0 | return gs_error_Info; |
1025 | | /*#ifdef DEBUG */ |
1026 | | /* |
1027 | | * Here we provide a place for inserting debugging code that can be |
1028 | | * run in place of the normal interpreter code. |
1029 | | */ |
1030 | 0 | case 'X': |
1031 | 0 | code = gs_main_init2(minst); |
1032 | 0 | if (code < 0) |
1033 | 0 | return code; |
1034 | 0 | { |
1035 | 0 | int xec; /* exit_code */ |
1036 | 0 | ref xeo; /* error_object */ |
1037 | |
|
1038 | 0 | #define start_x()\ |
1039 | 0 | gs_main_run_string_begin(minst, 1, &xec, &xeo) |
1040 | 0 | #define run_x(str)\ |
1041 | 0 | gs_main_run_string_continue(minst, str, strlen(str), 1, &xec, &xeo) |
1042 | 0 | #define stop_x()\ |
1043 | 0 | gs_main_run_string_end(minst, 1, &xec, &xeo) |
1044 | 0 | start_x(); |
1045 | 0 | run_x("\216\003abc"); |
1046 | 0 | run_x("== flush\n"); |
1047 | 0 | stop_x(); |
1048 | 0 | } |
1049 | 0 | return gs_error_Quit; |
1050 | | /*#endif */ |
1051 | 0 | case 'Z': |
1052 | 0 | set_debug_flags(arg, gs_debug); |
1053 | 0 | break; |
1054 | 2.64M | } |
1055 | 2.60M | return 0; |
1056 | 2.64M | } |
1057 | | |
1058 | | /* Define versions of strlen and strcat that encode strings in hex. */ |
1059 | | /* This is so we can enter escaped characters regardless of whether */ |
1060 | | /* the Level 1 convention of ignoring \s in strings-within-strings */ |
1061 | | /* is being observed (sigh). */ |
1062 | | static int |
1063 | | esc_strlen(const char *str) |
1064 | 1.00k | { |
1065 | 1.00k | return strlen(str) * 2 + 2; |
1066 | 1.00k | } |
1067 | | static void |
1068 | | esc_strcat(char *dest, const char *src) |
1069 | 1.00k | { |
1070 | 1.00k | char *d = dest + strlen(dest); |
1071 | 1.00k | const char *p; |
1072 | 1.00k | static const char *const hex = "0123456789abcdef"; |
1073 | | |
1074 | 1.00k | *d++ = '<'; |
1075 | 19.0k | for (p = src; *p; p++) { |
1076 | 18.0k | byte c = (byte) * p; |
1077 | | |
1078 | 18.0k | *d++ = hex[c >> 4]; |
1079 | 18.0k | *d++ = hex[c & 0xf]; |
1080 | 18.0k | } |
1081 | 1.00k | *d++ = '>'; |
1082 | 1.00k | *d = 0; |
1083 | 1.00k | } |
1084 | | |
1085 | | /* Process file names */ |
1086 | | static int |
1087 | | argproc(gs_main_instance * minst, const char *arg) |
1088 | 0 | { |
1089 | 0 | int code1, code = gs_main_init1(minst); /* need i_ctx_p to proceed */ |
1090 | |
|
1091 | 0 | if (code < 0) |
1092 | 0 | return code; |
1093 | | |
1094 | 0 | code = gs_add_control_path(minst->heap, gs_permit_file_reading, arg); |
1095 | 0 | if (code < 0) return code; |
1096 | | |
1097 | 0 | if (minst->run_buffer_size) { |
1098 | | /* Run file with run_string. */ |
1099 | 0 | code = run_buffered(minst, arg); |
1100 | 0 | } else { |
1101 | | /* Run file directly in the normal way. */ |
1102 | 0 | code = runarg(minst, "", arg, ".runfile", runInit | runFlush, minst->user_errors, NULL, NULL); |
1103 | 0 | } |
1104 | |
|
1105 | 0 | code1 = gs_remove_control_path(minst->heap, gs_permit_file_reading, arg); |
1106 | 0 | if (code >= 0 && code1 < 0) code = code1; |
1107 | |
|
1108 | 0 | return code; |
1109 | 0 | } |
1110 | | static int |
1111 | | run_buffered(gs_main_instance * minst, const char *arg) |
1112 | 0 | { |
1113 | 0 | gp_file *in = gp_fopen(minst->heap, arg, gp_fmode_rb); |
1114 | 0 | int exit_code; |
1115 | 0 | ref error_object; |
1116 | 0 | int code; |
1117 | |
|
1118 | 0 | if (in == 0) { |
1119 | 0 | outprintf(minst->heap, "Unable to open %s for reading", arg); |
1120 | 0 | return_error(gs_error_invalidfileaccess); |
1121 | 0 | } |
1122 | 0 | code = gs_main_init2(minst); |
1123 | 0 | if (code < 0) { |
1124 | 0 | gp_fclose(in); |
1125 | 0 | return code; |
1126 | 0 | } |
1127 | 0 | code = gs_main_run_string_begin(minst, minst->user_errors, |
1128 | 0 | &exit_code, &error_object); |
1129 | 0 | if (!code) { |
1130 | 0 | char buf[MAX_BUFFERED_SIZE]; |
1131 | 0 | int count; |
1132 | |
|
1133 | 0 | code = gs_error_NeedInput; |
1134 | 0 | while ((count = gp_fread(buf, 1, minst->run_buffer_size, in)) > 0) { |
1135 | 0 | code = gs_main_run_string_continue(minst, buf, count, |
1136 | 0 | minst->user_errors, |
1137 | 0 | &exit_code, &error_object); |
1138 | 0 | if (code != gs_error_NeedInput) |
1139 | 0 | break; |
1140 | 0 | } |
1141 | 0 | if (code == gs_error_NeedInput) { |
1142 | 0 | code = gs_main_run_string_end(minst, minst->user_errors, |
1143 | 0 | &exit_code, &error_object); |
1144 | 0 | } |
1145 | 0 | } |
1146 | 0 | gp_fclose(in); |
1147 | 0 | zflush(minst->i_ctx_p); |
1148 | 0 | zflushpage(minst->i_ctx_p); |
1149 | 0 | return run_finish(minst, code, exit_code, &error_object); |
1150 | 0 | } |
1151 | | static int |
1152 | | runarg(gs_main_instance *minst, |
1153 | | const char *pre, |
1154 | | const char *arg, |
1155 | | const char *post, |
1156 | | int options, |
1157 | | int user_errors, |
1158 | | int *pexit_code, |
1159 | | ref *perror_object) |
1160 | 1.00k | { |
1161 | 1.00k | int len = strlen(pre) + esc_strlen(arg) + strlen(post) + 1; |
1162 | 1.00k | int code; |
1163 | 1.00k | char *line; |
1164 | | |
1165 | 1.00k | if (len < 0) |
1166 | 0 | return gs_note_error(gs_error_rangecheck); |
1167 | | |
1168 | 1.00k | if (options & runInit) { |
1169 | 0 | code = gs_main_init2(minst); /* Finish initialization */ |
1170 | |
|
1171 | 0 | if (code < 0) |
1172 | 0 | return code; |
1173 | 0 | } |
1174 | 1.00k | line = (char *)gs_alloc_bytes(minst->heap, len, "runarg"); |
1175 | 1.00k | if (line == 0) { |
1176 | 0 | lprintf("Out of memory!\n"); |
1177 | 0 | return_error(gs_error_VMerror); |
1178 | 0 | } |
1179 | 1.00k | strcpy(line, pre); |
1180 | 1.00k | esc_strcat(line, arg); |
1181 | 1.00k | strcat(line, post); |
1182 | 1.00k | minst->i_ctx_p->starting_arg_file = true; |
1183 | 1.00k | code = run_string(minst, line, options, user_errors, pexit_code, perror_object); |
1184 | 1.00k | minst->i_ctx_p->starting_arg_file = false; |
1185 | 1.00k | gs_free_object(minst->heap, line, "runarg"); |
1186 | 1.00k | return code; |
1187 | 1.00k | } |
1188 | | int |
1189 | | gs_main_run_file2(gs_main_instance *minst, |
1190 | | const char *filename, |
1191 | | int user_errors, |
1192 | | int *pexit_code, |
1193 | | ref *perror_object) |
1194 | 1.00k | { |
1195 | 1.00k | int code, code1; |
1196 | | |
1197 | 1.00k | code = gs_add_control_path(minst->heap, gs_permit_file_reading, filename); |
1198 | 1.00k | if (code < 0) return code; |
1199 | | |
1200 | 1.00k | code = runarg(minst, "", filename, ".runfile", runFlush, user_errors, pexit_code, perror_object); |
1201 | | |
1202 | 1.00k | code1 = gs_remove_control_path(minst->heap, gs_permit_file_reading, filename); |
1203 | 1.00k | if (code >= 0 && code1 < 0) code = code1; |
1204 | | |
1205 | 1.00k | return code; |
1206 | 1.00k | } |
1207 | | static int |
1208 | | run_string(gs_main_instance *minst, |
1209 | | const char *str, |
1210 | | int options, |
1211 | | int user_errors, |
1212 | | int *pexit_code, |
1213 | | ref *perror_object) |
1214 | 146k | { |
1215 | 146k | int exit_code; |
1216 | 146k | ref error_object; |
1217 | 146k | int code; |
1218 | | |
1219 | 146k | if (pexit_code == NULL) |
1220 | 145k | pexit_code = &exit_code; |
1221 | 146k | if (perror_object == NULL) |
1222 | 145k | perror_object = &error_object; |
1223 | | |
1224 | 146k | code = gs_main_run_string(minst, str, user_errors, |
1225 | 146k | pexit_code, perror_object); |
1226 | | |
1227 | 146k | if ((options & runFlush) || code != 0) { |
1228 | 146k | zflush(minst->i_ctx_p); /* flush stdout */ |
1229 | 146k | zflushpage(minst->i_ctx_p); /* force display update */ |
1230 | 146k | } |
1231 | 146k | return run_finish(minst, code, *pexit_code, perror_object); |
1232 | 146k | } |
1233 | | static int |
1234 | | run_finish(gs_main_instance *minst, int code, int exit_code, |
1235 | | ref * perror_object) |
1236 | 146k | { |
1237 | 146k | switch (code) { |
1238 | 14 | case gs_error_Quit: |
1239 | 108k | case 0: |
1240 | 108k | break; |
1241 | 37.8k | case gs_error_Fatal: |
1242 | 37.8k | if (exit_code == gs_error_InterpreterExit) |
1243 | 0 | code = exit_code; |
1244 | 37.8k | else |
1245 | 37.8k | emprintf1(minst->heap, |
1246 | 37.8k | "Unrecoverable error, exit code %d\n", |
1247 | 37.8k | exit_code); |
1248 | 37.8k | break; |
1249 | 0 | default: |
1250 | 0 | gs_main_dump_stack(minst, code, perror_object); |
1251 | 146k | } |
1252 | 146k | return code; |
1253 | 146k | } |
1254 | | |
1255 | | /* Redirect stdout to a file: |
1256 | | * -sstdout=filename |
1257 | | * -sstdout=- |
1258 | | * -sstdout=%stdout |
1259 | | * -sstdout=%stderr |
1260 | | * -sOutputFile=- is not affected. |
1261 | | * File is closed at program exit (if not stdout/err) |
1262 | | * or when -sstdout is used again. |
1263 | | */ |
1264 | | static int |
1265 | | try_stdout_redirect(gs_main_instance * minst, |
1266 | | const char *command, const char *filename) |
1267 | 598k | { |
1268 | 598k | gs_lib_ctx_core_t *core = minst->heap->gs_lib_ctx->core; |
1269 | 598k | if (strcmp(command, "stdout") == 0) { |
1270 | 143k | core->stdout_to_stderr = 0; |
1271 | 143k | core->stdout_is_redirected = 0; |
1272 | | /* If stdout already being redirected and it is not stdout |
1273 | | * or stderr, close it |
1274 | | */ |
1275 | 143k | if (core->fstdout2 |
1276 | 0 | && (gp_get_file(core->fstdout2) != core->fstdout) |
1277 | 0 | && (gp_get_file(core->fstdout2) != core->fstderr)) { |
1278 | 0 | gp_fclose(core->fstdout2); |
1279 | 0 | core->fstdout2 = NULL; |
1280 | 0 | } |
1281 | | /* If stdout is being redirected, set minst->fstdout2 */ |
1282 | 143k | if ( (filename != 0) && strlen(filename) && |
1283 | 143k | strcmp(filename, "-") && strcmp(filename, "%stdout") ) { |
1284 | 143k | if (strcmp(filename, "%stderr") == 0) { |
1285 | 0 | core->stdout_to_stderr = 1; |
1286 | 143k | } else { |
1287 | 143k | core->fstdout2 = gp_fopen(minst->heap, filename, "w"); |
1288 | 143k | if (core->fstdout2 == NULL) |
1289 | 0 | return_error(gs_error_invalidfileaccess); |
1290 | 143k | } |
1291 | 143k | core->stdout_is_redirected = 1; |
1292 | 143k | } |
1293 | 143k | return 0; |
1294 | 143k | } |
1295 | 454k | return 1; |
1296 | 598k | } |
1297 | | |
1298 | | /* ---------------- Print information ---------------- */ |
1299 | | |
1300 | | /* |
1301 | | * Help strings. We have to break them up into parts, because |
1302 | | * the Watcom compiler has a limit of 510 characters for a single token. |
1303 | | * For PC displays, we want to limit the strings to 24 lines. |
1304 | | */ |
1305 | | static const char help_usage1[] = "\ |
1306 | | Usage: gs [switches] [file1.ps file2.ps ...]\n\ |
1307 | | Most frequently used switches: (you can use # in place of =)\n\ |
1308 | | -dNOPAUSE no pause after page | -q `quiet', fewer messages\n\ |
1309 | | -g<width>x<height> page size in pixels | -r<res> pixels/inch resolution\n"; |
1310 | | static const char help_usage2[] = "\ |
1311 | | -sDEVICE=<devname> select device | -dBATCH exit after last file\n\ |
1312 | | -sOutputFile=<file> select output file: - for stdout, |command for pipe,\n\ |
1313 | | embed %d or %ld for page #\n"; |
1314 | | #ifdef DEBUG |
1315 | | static const char help_debug[] = "\ |
1316 | | --debug=<option>[,<option>]* select debugging options\n\ |
1317 | | --debug list debugging options\n"; |
1318 | | #endif |
1319 | | static const char help_trailer[] = "\ |
1320 | | For more information, see %s\n\ |
1321 | | Please report bugs to bugs.ghostscript.com.\n"; |
1322 | | static const char help_devices[] = "Available devices:"; |
1323 | | static const char help_default_device[] = "Default output device:"; |
1324 | | static const char help_emulators[] = "Input formats:"; |
1325 | | static const char help_paths[] = "Search path:"; |
1326 | | #ifdef HAVE_FONTCONFIG |
1327 | | static const char help_fontconfig[] = "Ghostscript is also using fontconfig to search for font files\n"; |
1328 | | #else |
1329 | | static const char help_fontconfig[] = ""; |
1330 | | #endif |
1331 | | |
1332 | | extern_gx_io_device_table(); |
1333 | | |
1334 | | /* Print the standard help message. */ |
1335 | | static void |
1336 | | print_help(gs_main_instance * minst) |
1337 | 0 | { |
1338 | 0 | int i, have_rom_device = 0; |
1339 | |
|
1340 | 0 | print_revision(minst); |
1341 | 0 | print_usage(minst); |
1342 | 0 | print_emulators(minst); |
1343 | 0 | print_devices(minst); |
1344 | 0 | print_paths(minst); |
1345 | | /* Check if we have the %rom device */ |
1346 | 0 | for (i = 0; i < gx_io_device_table_count; i++) { |
1347 | 0 | const gx_io_device *iodev = gx_io_device_table[i]; |
1348 | 0 | const char *dname = iodev->dname; |
1349 | |
|
1350 | 0 | if (dname && strlen(dname) == 5 && !memcmp("%rom%", dname, 5)) { |
1351 | 0 | struct stat pstat; |
1352 | | /* gs_error_unregistered means no usable romfs is available */ |
1353 | 0 | int code = iodev->procs.file_status((gx_io_device *)iodev, dname, &pstat); |
1354 | 0 | if (code != gs_error_unregistered){ |
1355 | 0 | have_rom_device = 1; |
1356 | 0 | } |
1357 | 0 | break; |
1358 | 0 | } |
1359 | 0 | } |
1360 | 0 | if (have_rom_device) { |
1361 | 0 | outprintf(minst->heap, "Initialization files are compiled into the executable.\n"); |
1362 | 0 | } |
1363 | 0 | print_help_trailer(minst); |
1364 | 0 | } |
1365 | | |
1366 | | /* Print the revision, revision date, and copyright. */ |
1367 | | static void |
1368 | | print_revision(const gs_main_instance *minst) |
1369 | 0 | { |
1370 | 0 | printf_program_ident(minst->heap, gs_product, gs_revision); |
1371 | 0 | outprintf(minst->heap, " (%d-%02d-%02d)\n%s\n", |
1372 | 0 | (int)(gs_revisiondate / 10000), |
1373 | 0 | (int)(gs_revisiondate / 100 % 100), |
1374 | 0 | (int)(gs_revisiondate % 100), |
1375 | 0 | gs_copyright); |
1376 | 0 | } |
1377 | | |
1378 | | /* Print the version number. */ |
1379 | | static void |
1380 | | print_version(const gs_main_instance *minst) |
1381 | 0 | { |
1382 | 0 | printf_program_ident(minst->heap, NULL, gs_revision); |
1383 | 0 | } |
1384 | | |
1385 | | /* Print usage information. */ |
1386 | | static void |
1387 | | print_usage(const gs_main_instance *minst) |
1388 | 0 | { |
1389 | 0 | outprintf(minst->heap, "%s", help_usage1); |
1390 | 0 | outprintf(minst->heap, "%s", help_usage2); |
1391 | | #ifdef DEBUG |
1392 | | outprintf(minst->heap, "%s", help_debug); |
1393 | | #endif |
1394 | 0 | } |
1395 | | |
1396 | | /* compare function for qsort */ |
1397 | | static int |
1398 | | cmpstr(const void *v1, const void *v2) |
1399 | 0 | { |
1400 | 0 | return strcmp( *(char * const *)v1, *(char * const *)v2 ); |
1401 | 0 | } |
1402 | | |
1403 | | /* Print the list of available devices. */ |
1404 | | static void |
1405 | | print_devices(const gs_main_instance *minst) |
1406 | 0 | { |
1407 | 0 | outprintf(minst->heap, "%s", help_default_device); |
1408 | 0 | outprintf(minst->heap, " %s\n", gs_devicename(gs_getdefaultdevice())); |
1409 | 0 | outprintf(minst->heap, "%s", help_devices); |
1410 | 0 | { |
1411 | 0 | int i; |
1412 | 0 | int pos = 100; |
1413 | 0 | const gx_device *pdev; |
1414 | 0 | const char **names; |
1415 | 0 | size_t ndev = 0; |
1416 | |
|
1417 | 0 | for (i = 0; gs_getdevice(i) != 0; i++) |
1418 | 0 | ; |
1419 | 0 | ndev = (size_t)i; |
1420 | 0 | names = (const char **)gs_alloc_bytes(minst->heap, (size_t)ndev * sizeof(const char*), "print_devices"); |
1421 | 0 | if (names == (const char **)NULL) { /* old-style unsorted device list */ |
1422 | 0 | for (i = 0; (pdev = gs_getdevice(i)) != 0; i++) { |
1423 | 0 | const char *dname = gs_devicename(pdev); |
1424 | 0 | int len = strlen(dname); |
1425 | |
|
1426 | 0 | if (pos + 1 + len > 76) |
1427 | 0 | outprintf(minst->heap, "\n "), pos = 2; |
1428 | 0 | outprintf(minst->heap, " %s", dname); |
1429 | 0 | pos += 1 + len; |
1430 | 0 | } |
1431 | 0 | } |
1432 | 0 | else { /* new-style sorted device list */ |
1433 | 0 | for (i = 0; (pdev = gs_getdevice(i)) != 0; i++) |
1434 | 0 | names[i] = gs_devicename(pdev); |
1435 | 0 | qsort((void*)names, ndev, sizeof(const char*), cmpstr); |
1436 | 0 | for (i = 0; i < ndev; i++) { |
1437 | 0 | int len = strlen(names[i]); |
1438 | |
|
1439 | 0 | if (pos + 1 + len > 76) |
1440 | 0 | outprintf(minst->heap, "\n "), pos = 2; |
1441 | 0 | outprintf(minst->heap, " %s", names[i]); |
1442 | 0 | pos += 1 + len; |
1443 | 0 | } |
1444 | 0 | gs_free(minst->heap, (char *)names, ndev * sizeof(const char*), 1, "print_devices"); |
1445 | 0 | } |
1446 | 0 | } |
1447 | 0 | outprintf(minst->heap, "\n"); |
1448 | 0 | } |
1449 | | |
1450 | | /* Print the list of language emulators. */ |
1451 | | static void |
1452 | | print_emulators(const gs_main_instance *minst) |
1453 | 0 | { |
1454 | 0 | outprintf(minst->heap, "%s", help_emulators); |
1455 | 0 | { |
1456 | 0 | const byte *s; |
1457 | |
|
1458 | 0 | for (s = gs_emulators; s[0] != 0; s += strlen((const char *)s) + 1) |
1459 | 0 | outprintf(minst->heap, " %s", s); |
1460 | 0 | } |
1461 | 0 | outprintf(minst->heap, "\n"); |
1462 | 0 | } |
1463 | | |
1464 | | /* Print the search paths. */ |
1465 | | static void |
1466 | | print_paths(gs_main_instance * minst) |
1467 | 0 | { |
1468 | 0 | outprintf(minst->heap, "%s", help_paths); |
1469 | 0 | gs_main_set_lib_paths(minst); |
1470 | 0 | { |
1471 | 0 | uint count = r_size(&minst->lib_path.list); |
1472 | 0 | uint i; |
1473 | 0 | int pos = 100; |
1474 | 0 | char fsepr[3]; |
1475 | |
|
1476 | 0 | fsepr[0] = ' ', fsepr[1] = gp_file_name_list_separator, |
1477 | 0 | fsepr[2] = 0; |
1478 | 0 | for (i = 0; i < count; ++i) { |
1479 | 0 | const ref *prdir = |
1480 | 0 | minst->lib_path.list.value.refs + i; |
1481 | 0 | uint len = r_size(prdir); |
1482 | 0 | const char *sepr = (i == count - 1 ? "" : fsepr); |
1483 | |
|
1484 | 0 | if (1 + pos + strlen(sepr) + len > 76) |
1485 | 0 | outprintf(minst->heap, "\n "), pos = 2; |
1486 | 0 | outprintf(minst->heap, " "); |
1487 | | /* |
1488 | | * This is really ugly, but it's necessary because some |
1489 | | * platforms rely on all console output being funneled through |
1490 | | * outprintf. We wish we could just do: |
1491 | | fwrite(prdir->value.bytes, 1, len, minst->fstdout); |
1492 | | */ |
1493 | 0 | { |
1494 | 0 | const char *p = (const char *)prdir->value.bytes; |
1495 | 0 | uint j; |
1496 | |
|
1497 | 0 | for (j = len; j; j--) |
1498 | 0 | outprintf(minst->heap, "%c", *p++); |
1499 | 0 | } |
1500 | 0 | outprintf(minst->heap, "%s", sepr); |
1501 | 0 | pos += 1 + len + strlen(sepr); |
1502 | 0 | } |
1503 | 0 | } |
1504 | 0 | outprintf(minst->heap, "\n"); |
1505 | 0 | outprintf(minst->heap, "%s", help_fontconfig); |
1506 | 0 | } |
1507 | | |
1508 | | /* Print the help trailer. */ |
1509 | | static void |
1510 | | print_help_trailer(const gs_main_instance *minst) |
1511 | 0 | { |
1512 | 0 | char buffer[gp_file_name_sizeof]; |
1513 | 0 | const char *use_htm = "Use.html", *p = buffer; |
1514 | 0 | const char *rtd_url = "https://ghostscript.readthedocs.io/en"; |
1515 | 0 | const char *latest_ver="latest"; |
1516 | 0 | const char *vers = latest_ver; |
1517 | 0 | const char *gs_str = "gs"; |
1518 | 0 | const char *gs = ""; |
1519 | 0 | uint blen = sizeof(buffer); |
1520 | |
|
1521 | 0 | if (strlen(GS_PRODUCT) == strlen(GS_PRODUCTFAMILY)) { |
1522 | 0 | vers = GS_STRINGIZE(GS_DOT_VERSION); |
1523 | 0 | gs = gs_str; |
1524 | 0 | } |
1525 | |
|
1526 | 0 | snprintf(buffer, blen, "%s/%s%s/%s", rtd_url, gs, vers, use_htm); |
1527 | 0 | outprintf(minst->heap, help_trailer, p); |
1528 | 0 | } |