diff options
Diffstat (limited to 'psi/imainarg.c')
-rw-r--r-- | psi/imainarg.c | 1413 |
1 files changed, 1413 insertions, 0 deletions
diff --git a/psi/imainarg.c b/psi/imainarg.c new file mode 100644 index 00000000..76653f32 --- /dev/null +++ b/psi/imainarg.c @@ -0,0 +1,1413 @@ +/* Copyright (C) 2001-2019 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, + CA 94945, U.S.A., +1(415)492-9861, for further information. +*/ + + +/* Command line parsing and dispatching */ + +#include "ctype_.h" +#include "memory_.h" +#include "string_.h" +#include <stdlib.h> /* for qsort */ + +#include "ghost.h" +#include "gp.h" +#include "gsargs.h" +#include "gscdefs.h" +#include "gsmalloc.h" /* for gs_malloc_limit */ +#include "gsmdebug.h" +#include "gspaint.h" /* for gs_erasepage */ +#include "gxdevice.h" +#include "gxdevmem.h" +#include "gsdevice.h" +#include "gxdevsop.h" /* for gxdso_* enums */ +#include "gxclpage.h" +#include "gdevprn.h" +#include "stream.h" +#include "ierrors.h" +#include "estack.h" +#include "ialloc.h" +#include "strimpl.h" /* for sfilter.h */ +#include "sfilter.h" /* for iscan.h */ +#include "ostack.h" /* must precede iscan.h */ +#include "iscan.h" +#include "iconf.h" +#include "imain.h" +#include "imainarg.h" +#include "iapi.h" +#include "iminst.h" +#include "iname.h" +#include "store.h" +#include "files.h" /* requires stream.h */ +#include "interp.h" +#include "iutil.h" +#include "ivmspace.h" + +/* Import operator procedures */ +extern int zflush(i_ctx_t *); +extern int zflushpage(i_ctx_t *); + +#ifndef GS_LIB +# define GS_LIB "GS_LIB" +#endif + +#ifndef GS_OPTIONS +# define GS_OPTIONS "GS_OPTIONS" +#endif + +/* This is now the default number of entries we initially + * allocate for the search path array objects. The arrays + * will now enlarge as required - + * see imain.c: file_path_add()/extend_path_list_container() + */ + +#ifndef GS_MAX_LIB_DIRS +# define GS_MAX_LIB_DIRS 25 +#endif + +#define MAX_BUFFERED_SIZE 1024 + +/* Note: sscanf incorrectly defines its first argument as char * */ +/* rather than const char *. This accounts for the ugly casts below. */ + +/* Redefine puts to use outprintf, */ +/* so it will work even without stdio. */ +#undef puts +#define puts(mem, str) outprintf(mem, "%s\n", str) + +/* Forward references */ +#define runInit 1 +#define runFlush 2 +#define runBuffer 4 +static int swproc(gs_main_instance *, const char *, arg_list *); +static int argproc(gs_main_instance *, const char *); +static int run_buffered(gs_main_instance *, const char *); +static int esc_strlen(const char *); +static void esc_strcat(char *, const char *); +static int runarg(gs_main_instance *, const char *, const char *, const char *, int, int, int *, ref *); +static int run_string(gs_main_instance *, const char *, int, int, int *, ref *); +static int run_finish(gs_main_instance *, int, int, ref *); +static int try_stdout_redirect(gs_main_instance * minst, + const char *command, const char *filename); + +/* Forward references for help printout */ +static void print_help(gs_main_instance *); +static void print_revision(const gs_main_instance *); +static void print_version(const gs_main_instance *); +static void print_usage(const gs_main_instance *); +static void print_devices(const gs_main_instance *); +static void print_emulators(const gs_main_instance *); +static void print_paths(gs_main_instance *); +static void print_help_trailer(const gs_main_instance *); + +/* ------ Main program ------ */ + +/* Process the command line with a given instance. */ +static gp_file * +gs_main_arg_fopen(const char *fname, void *vminst) +{ + gs_main_set_lib_paths((gs_main_instance *) vminst); + return lib_fopen(&((gs_main_instance *)vminst)->lib_path, + ((gs_main_instance *)vminst)->heap, fname); +} +static void +set_debug_flags(const char *arg, char *flags) +{ + byte value = (*arg == '-' ? (++arg, 0) : 0xff); + + while (*arg) + flags[*arg++ & 127] = value; +} + +int +gs_main_init_with_args01(gs_main_instance * minst, int argc, char *argv[]) +{ + const char *arg; + arg_list args; + int code; + int have_dumped_args = 0; + + /* Now we actually process them */ + code = arg_init(&args, (const char **)argv, argc, + gs_main_arg_fopen, (void *)minst, + minst->get_codepoint, + minst->heap); + if (code < 0) + return code; + code = gs_main_init0(minst, 0, 0, 0, GS_MAX_LIB_DIRS); + if (code < 0) + return code; +/* This first check is not needed on VMS since GS_LIB evaluates to the same + value as that returned by gs_lib_default_path. Also, since GS_LIB is + defined as a searchlist logical and getenv only returns the first entry + in the searchlist, it really doesn't make sense to search that particular + directory twice. +*/ +#ifndef __VMS + { + int len = 0; + int code = gp_getenv(GS_LIB, (char *)0, &len); + + if (code < 0) { /* key present, value doesn't fit */ + char *path = (char *)gs_alloc_bytes(minst->heap, len, "GS_LIB"); + + gp_getenv(GS_LIB, path, &len); /* can't fail */ + minst->lib_path.env = path; + } + } +#endif /* __VMS */ + minst->lib_path.final = gs_lib_default_path; + code = gs_main_set_lib_paths(minst); + if (code < 0) + return code; + /* Prescan the command line for --help and --version. */ + { + int i; + bool helping = false; + + for (i = 1; i < argc; ++i) + if (!arg_strcmp(&args, argv[i], "--")) { + /* A PostScript program will be interpreting all the */ + /* remaining switches, so stop scanning. */ + helping = false; + break; + } else if (!arg_strcmp(&args, argv[i], "--help")) { + print_help(minst); + helping = true; + } else if (!arg_strcmp(&args, argv[i], "--debug")) { + gs_debug_flags_list(minst->heap); + helping = true; + } else if (!arg_strcmp(&args, argv[i], "--version")) { + print_version(minst); + puts(minst->heap, ""); /* \n */ + helping = true; + } + if (helping) + return gs_error_Info; + } + /* Execute files named in the command line, */ + /* processing options along the way. */ + /* Wait until the first file name (or the end */ + /* of the line) to finish initialization. */ + minst->run_start = true; + + { + int len = 0; + int code = gp_getenv(GS_OPTIONS, (char *)0, &len); + + if (code < 0) { /* key present, value doesn't fit */ + char *opts = + (char *)gs_alloc_bytes(minst->heap, len, "GS_OPTIONS"); + + gp_getenv(GS_OPTIONS, opts, &len); /* can't fail */ + if (arg_push_decoded_memory_string(&args, opts, false, true, minst->heap)) + return gs_error_Fatal; + } + } + while ((code = arg_next(&args, (const char **)&arg, minst->heap)) > 0) { + code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, arg); + if (code < 0) + return code; + switch (*arg) { + case '-': + code = swproc(minst, arg, &args); + if (code < 0) + return code; + if (code > 0) + outprintf(minst->heap, "Unknown switch %s - ignoring\n", arg); + if (gs_debug[':'] && !have_dumped_args) { + int i; + + dmprintf1(minst->heap, "%% Args passed to instance 0x%p: ", minst); + for (i=1; i<argc; i++) + dmprintf1(minst->heap, "%s ", argv[i]); + dmprintf(minst->heap, "\n"); + have_dumped_args = 1; + } + break; + default: + /* default is to treat this as a file name to be run */ + code = argproc(minst, arg); + if (code < 0) + return code; + if (minst->saved_pages_test_mode) { + gx_device *pdev; + int ret; + gxdso_device_child_request child_dev_data; + + /* get the real target (printer) device */ + pdev = gs_currentdevice(minst->i_ctx_p->pgs); + do { + child_dev_data.target = pdev; + ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, + sizeof(child_dev_data)); + if (ret > 0) + pdev = child_dev_data.target; + } while ((ret > 0) && (child_dev_data.n != 0)); + if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, + (byte *)"print normal flush", 18)) < 0) + return code; + if (code > 0) + if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) + return code; + } + } + } + + return code; +} + +int +gs_main_init_with_args2(gs_main_instance * minst) +{ + int code; + + code = gs_main_init2(minst); + if (code < 0) + return code; + + if (!minst->run_start) + return gs_error_Quit; + return code; +} + +int +gs_main_init_with_args(gs_main_instance * minst, int argc, char *argv[]) +{ + int code = gs_main_init_with_args01(minst, argc, argv); + + if (code < 0) + return code; + return gs_main_init_with_args2(minst); +} + +/* + * Specify a decoding function + */ +void gs_main_inst_arg_decode(gs_main_instance * minst, + gs_arg_get_codepoint *get_codepoint) +{ + minst->get_codepoint = get_codepoint; +} + +gs_arg_get_codepoint *gs_main_inst_get_arg_decode(gs_main_instance * minst) +{ + return minst->get_codepoint; +} + +/* + * Run the 'start' procedure (after processing the command line). + */ +int +gs_main_run_start(gs_main_instance * minst) +{ + return run_string(minst, "systemdict /start get exec", runFlush, minst->user_errors, NULL, NULL); +} + +static int +do_arg_match(const char **arg, const char *match, size_t match_len) +{ + const char *s = *arg; + if (strncmp(s, match, match_len) != 0) + return 0; + s += match_len; + if (*s == '=') + *arg = ++s; + else if (*s != 0) + return 0; + else + *arg = NULL; + return 1; +} + +#define arg_match(A, B) do_arg_match(A, B, sizeof(B)-1) + +/* Process switches. Return 0 if processed, 1 for unknown switch, */ +/* <0 if error. */ +static int +swproc(gs_main_instance * minst, const char *arg, arg_list * pal) +{ + char sw = arg[1]; + ref vtrue; + int code = 0; + + make_true(&vtrue); + arg += 2; /* skip - and letter */ + switch (sw) { + default: + return 1; + case 0: /* read stdin as a file char-by-char */ + /* This is a ******HACK****** for Ghostview. */ + minst->heap->gs_lib_ctx->core->stdin_is_interactive = true; + goto run_stdin; + case '_': /* read stdin with normal buffering */ + minst->heap->gs_lib_ctx->core->stdin_is_interactive = false; +run_stdin: + minst->run_start = false; /* don't run 'start' */ + /* Set NOPAUSE so showpage won't try to read from stdin. */ + code = swproc(minst, "-dNOPAUSE", pal); + if (code) + return code; + code = gs_main_init2(minst); /* Finish initialization */ + if (code < 0) + return code; + + code = run_string(minst, ".runstdin", runFlush, minst->user_errors, NULL, NULL); + if (code < 0) + return code; + /* If in saved_pages_test_mode, print and flush previous job before the next file */ + if (minst->saved_pages_test_mode) { + gx_device *pdev; + int ret; + gxdso_device_child_request child_dev_data; + + /* get the real target (printer) device */ + pdev = gs_currentdevice(minst->i_ctx_p->pgs); + do { + child_dev_data.target = pdev; + ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, + sizeof(child_dev_data)); + if (ret > 0) + pdev = child_dev_data.target; + } while ((ret > 0) && (child_dev_data.n != 0)); + if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, + (byte *)"print normal flush", 18)) < 0) + return code; + if (code > 0) + if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) + return code; + } + break; + case '-': /* run with command line args */ + if (strncmp(arg, "debug=", 6) == 0) { + code = gs_debug_flags_parse(minst->heap, arg+6); + if (code < 0) + return code; + break; + } else if (strncmp(arg, "saved-pages=", 12) == 0) { + gx_device *pdev; + + /* If init2 not yet done, just save the argument for processing then */ + if (minst->init_done < 2) { + if (minst->saved_pages_initial_arg == NULL) { + /* Tuck the parameters away for later when init2 is done (usually "begin") */ + minst->saved_pages_initial_arg = (char *)gs_alloc_bytes(minst->heap, + 1+strlen((char *)arg+12), + "GS_OPTIONS"); + if (minst->saved_pages_initial_arg != NULL) { + strcpy(minst->saved_pages_initial_arg,(char *)arg+12); + } else { + outprintf(minst->heap, + " saved_pages_initial_arg larger than expected\n"); + arg_finit(pal); + return gs_error_Fatal; + } + } else { + outprintf(minst->heap, + " Only one --saved-pages=... command allowed before processing input\n"); + arg_finit(pal); + return gs_error_Fatal; + } + } else { + int ret; + gxdso_device_child_request child_dev_data; + + /* get the current device */ + pdev = gs_currentdevice(minst->i_ctx_p->pgs); + if (dev_proc(pdev, dev_spec_op)(pdev, gxdso_supports_saved_pages, NULL, 0) <= 0) { + outprintf(minst->heap, + " --saved-pages not supported by the '%s' device.\n", + pdev->dname); + arg_finit(pal); + return gs_error_Fatal; + } + /* get the real target (printer) device */ + do { + child_dev_data.target = pdev; + ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, + sizeof(child_dev_data)); + if (ret > 0) + pdev = child_dev_data.target; + } while ((ret > 0) && (child_dev_data.n != 0)); + if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, + (byte *)arg+12, strlen(arg+12))) < 0) + return code; + if (code > 0) + if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) + return code; + } + break; + /* The following code is only to allow regression testing of saved-pages */ + } else if (strncmp(arg, "saved-pages-test", 16) == 0) { + minst->saved_pages_test_mode = true; + break; + /* Now handle the explicitly added paths to the file control lists */ + } else if (arg_match(&arg, "permit-file-read")) { + code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_reading); + if (code < 0) return code; + break; + } else if (arg_match(&arg, "permit-file-write")) { + code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_writing); + if (code < 0) return code; + break; + } else if (arg_match(&arg, "permit-file-control")) { + code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_control); + if (code < 0) return code; + break; + } else if (arg_match(&arg, "permit-file-all")) { + code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_reading); + if (code < 0) return code; + code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_writing); + if (code < 0) return code; + code = gs_add_explicit_control_path(minst->heap, arg, gs_permit_file_control); + if (code < 0) return code; + break; + } + /* FALLTHROUGH */ + case '+': + pal->expand_ats = false; + /* FALLTHROUGH */ + case '@': /* ditto with @-expansion */ + { + char *psarg; + + code = arg_next(pal, (const char **)&psarg, minst->heap); + /* Don't stash the @ file name */ + + if (code < 0) + return gs_error_Fatal; + if (psarg == NULL) { + outprintf(minst->heap, "Usage: gs ... -%c file.ps arg1 ... argn\n", sw); + arg_finit(pal); + return gs_error_Fatal; + } + psarg = arg_copy(psarg, minst->heap); + if (psarg == NULL) + code = gs_error_Fatal; + else + code = gs_main_init2(minst); + if (code >= 0) + code = run_string(minst, "userdict/ARGUMENTS[", 0, minst->user_errors, NULL, NULL); + if (code >= 0) + while ((code = arg_next(pal, (const char **)&arg, minst->heap)) > 0) { + code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, arg); + if (code < 0) + break; + code = runarg(minst, "", arg, "", runInit, minst->user_errors, NULL, NULL); + if (code < 0) + break; + } + if (code >= 0) + code = runarg(minst, "]put", psarg, ".runfile", runInit | runFlush, minst->user_errors, NULL, NULL); + arg_free((char *)psarg, minst->heap); + if (code >= 0) + code = gs_error_Quit; + + return code; + } + case 'B': /* set run_string buffer size */ + if (*arg == '-') + minst->run_buffer_size = 0; + else { + uint bsize; + + if (sscanf((const char *)arg, "%u", &bsize) != 1 || + bsize <= 0 || bsize > MAX_BUFFERED_SIZE + ) { + outprintf(minst->heap, + "-B must be followed by - or size between 1 and %u\n", + MAX_BUFFERED_SIZE); + return gs_error_Fatal; + } + minst->run_buffer_size = bsize; + } + break; + case 'c': /* code follows */ + { + bool ats = pal->expand_ats; + + code = gs_main_init2(minst); + if (code < 0) + return code; + pal->expand_ats = false; + while ((code = arg_next(pal, (const char **)&arg, minst->heap)) > 0) { + if (arg[0] == '@' || + (arg[0] == '-' && !isdigit((unsigned char)arg[1])) + ) + break; + code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, "?"); + if (code < 0) + return code; + code = runarg(minst, "", arg, ".runstring", 0, minst->user_errors, NULL, NULL); + if (code < 0) + return code; + } + if (code < 0) + return gs_error_Fatal; + if (arg != NULL) { + char *p = arg_copy(arg, minst->heap); + if (p == NULL) + return gs_error_Fatal; + arg_push_string(pal, p, true); + } + pal->expand_ats = ats; + break; + } + case 'f': /* run file of arbitrary name */ + if (*arg != 0) { + code = argproc(minst, arg); + if (code < 0) + return code; + /* If in saved_pages_test_mode, print and flush previous job before the next file */ + if (minst->saved_pages_test_mode) { + gx_device *pdev; + int ret; + gxdso_device_child_request child_dev_data; + + /* get the real target (printer) device */ + pdev = gs_currentdevice(minst->i_ctx_p->pgs); + do { + child_dev_data.target = pdev; + ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, + sizeof(child_dev_data)); + if (ret > 0) + pdev = child_dev_data.target; + } while ((ret > 0) && (child_dev_data.n != 0)); + if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, + (byte *)"print normal flush", 18)) < 0) + return code; + if (code > 0) + if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) + return code; + } + } + break; + case 'F': /* run file with buffer_size = 1 */ + if (!*arg) { + puts(minst->heap, "-F requires a file name"); + return gs_error_Fatal; + } { + uint bsize = minst->run_buffer_size; + + minst->run_buffer_size = 1; + code = argproc(minst, arg); + minst->run_buffer_size = bsize; + if (code < 0) + return code; + /* If in saved_pages_test_mode, print and flush previous job before the next file */ + if (minst->saved_pages_test_mode) { + gx_device *pdev; + int ret; + gxdso_device_child_request child_dev_data; + + /* get the real target (printer) device */ + pdev = gs_currentdevice(minst->i_ctx_p->pgs); + do { + child_dev_data.target = pdev; + ret = dev_proc(pdev, dev_spec_op)(pdev, gxdso_device_child, &child_dev_data, + sizeof(child_dev_data)); + if (ret > 0) + pdev = child_dev_data.target; + } while ((ret > 0) && (child_dev_data.n != 0)); + if ((code = gx_saved_pages_param_process((gx_device_printer *)pdev, + (byte *)"print normal flush", 18)) < 0) + return code; + if (code > 0) + if ((code = gs_erasepage(minst->i_ctx_p->pgs)) < 0) + return code; + } + } + break; + case 'g': /* define device geometry */ + { + long dimensions[2]; + + if ((code = gs_main_init1(minst)) < 0) + return code; + if (sscanf((const char *)arg, "%ldx%ld", &dimensions[0], &dimensions[1]) != 2) { + puts(minst->heap, "-g must be followed by <width>x<height>"); + return gs_error_Fatal; + } + gs_main_force_dimensions(minst, dimensions); + break; + } + case 'h': /* print help */ + case '?': /* ditto */ + print_help(minst); + return gs_error_Info; /* show usage info on exit */ + case 'I': /* specify search path */ + { + const char *path; + + if (arg[0] == 0) { + code = arg_next(pal, (const char **)&path, minst->heap); + if (code < 0) + return code; + code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, "?"); + if (code < 0) + return code; + } else + path = arg; + if (path == NULL) + return gs_error_Fatal; + gs_main_add_lib_path(minst, path); + } + break; + case 'K': /* set malloc limit */ + { + long msize = 0; + gs_malloc_memory_t *rawheap = gs_malloc_wrapped_contents(minst->heap); + + (void)sscanf((const char *)arg, "%ld", &msize); + if (msize <= 0 || msize > max_long >> 10) { + outprintf(minst->heap, "-K<numK> must have 1 <= numK <= %ld\n", + max_long >> 10); + return gs_error_Fatal; + } + rawheap->limit = msize << 10; + } + break; + case 'M': /* set memory allocation increment */ + { + unsigned msize = 0; + + (void)sscanf((const char *)arg, "%u", &msize); + if (msize <= 0 || msize >= (max_uint >> 10)) { + outprintf(minst->heap, "-M must be between 1 and %d\n", (int)((max_uint >> 10) - 1)); + return gs_error_Fatal; + } + minst->memory_clump_size = msize << 10; + } + break; + case 'N': /* set size of name table */ + { + unsigned nsize = 0; + + (void)sscanf((const char *)arg, "%d", &nsize); + if (nsize < 2 || nsize > (max_uint >> 10)) { + outprintf(minst->heap, "-N must be between 2 and %d\n", (int)(max_uint >> 10)); + return gs_error_Fatal; + } + minst->name_table_size = (ulong) nsize << 10; + } + break; + case 'o': /* set output file name and batch mode */ + { + i_ctx_t *i_ctx_p; + uint space; + const char *adef; + byte *str; + ref value; + int len; + + if ((code = gs_main_init1(minst)) < 0) + return code; + + i_ctx_p = minst->i_ctx_p; + space = icurrent_space; + if (arg[0] == 0) { + code = arg_next(pal, (const char **)&adef, minst->heap); + if (code < 0) + return code; + if (code == 0) + return gs_error_undefinedfilename; + code = gs_lib_ctx_stash_sanitized_arg(minst->heap->gs_lib_ctx, "?"); + if (code < 0) + return code; + } else + adef = arg; + if ((code = gs_main_init1(minst)) < 0) + return code; + + if (strlen(adef) > 0) { + code = gs_add_outputfile_control_path(minst->heap, adef); + if (code < 0) return code; + } + + ialloc_set_space(idmemory, avm_system); + len = strlen(adef); + str = ialloc_string(len, "-o"); + if (str == NULL) + return gs_error_VMerror; + memcpy(str, adef, len); + make_const_string(&value, a_readonly | avm_system, len, str); + ialloc_set_space(idmemory, space); + i_initial_enter_name(minst->i_ctx_p, "OutputFile", &value); + i_initial_enter_name(minst->i_ctx_p, "NOPAUSE", &vtrue); + i_initial_enter_name(minst->i_ctx_p, "BATCH", &vtrue); + } + break; + case 'P': /* choose whether search '.' first */ + if (!strcmp(arg, "")) + minst->search_here_first = true; + else if (!strcmp(arg, "-")) + minst->search_here_first = false; + else { + puts(minst->heap, "Only -P or -P- is allowed."); + return gs_error_Fatal; + } + break; + case 'q': /* quiet startup */ + if ((code = gs_main_init1(minst)) < 0) + return code; + i_initial_enter_name(minst->i_ctx_p, "QUIET", &vtrue); + break; + case 'r': /* define device resolution */ + { + float res[2]; + + if ((code = gs_main_init1(minst)) < 0) + return code; + switch (sscanf((const char *)arg, "%fx%f", &res[0], &res[1])) { + default: + puts(minst->heap, "-r must be followed by <res> or <xres>x<yres>"); + return gs_error_Fatal; + case 1: /* -r<res> */ + res[1] = res[0]; + /* fall through */ + case 2: /* -r<xres>x<yres> */ + gs_main_force_resolutions(minst, res); + } + break; + } + case 'D': /* define name */ + case 'd': + case 'S': /* define name as string */ + case 's': + { + char *adef = arg_copy(arg, minst->heap); + char *eqp; + bool isd = (sw == 'D' || sw == 'd'); + ref value; + + if (adef == NULL) + return gs_error_Fatal; + eqp = strchr(adef, '='); + + if (eqp == NULL) + eqp = strchr(adef, '#'); + /* Initialize the object memory, scanner, and */ + /* name table now if needed. */ + if ((code = gs_main_init1(minst)) < 0) { + arg_free((char *)adef, minst->heap); + return code; + } + if (eqp == adef) { + puts(minst->heap, "Usage: -dNAME, -dNAME=TOKEN, -sNAME=STRING"); + arg_free((char *)adef, minst->heap); + return gs_error_Fatal; + } + if (eqp == NULL) { + if (isd) + make_true(&value); + else + make_empty_string(&value, a_readonly); + } else { + int code; + i_ctx_t *i_ctx_p = minst->i_ctx_p; + uint space = icurrent_space; + + *eqp++ = 0; + + if (strlen(adef) == 10 && strncmp(adef, "OutputFile", 10) == 0 && strlen(eqp) > 0) { + code = gs_add_outputfile_control_path(minst->heap, eqp); + if (code < 0) return code; + } + + ialloc_set_space(idmemory, avm_system); + if (isd) { + int num, i; + + /* Check for numbers so we can provide for suffix scalers */ + /* Note the check for '#' is for PS "radix" numbers such as 16#ff */ + /* and check for '.' and 'e' or 'E' which are 'real' numbers */ + if ((strchr(eqp, '#') == NULL) && (strchr(eqp, '.') == NULL) && + (strchr(eqp, 'e') == NULL) && (strchr(eqp, 'E') == NULL) && + ((i = sscanf((const char *)eqp, "%d", &num)) == 1)) { + char suffix = eqp[strlen(eqp) - 1]; + + switch (suffix) { + case 'k': + case 'K': + num *= 1024; + break; + case 'm': + case 'M': + num *= 1024 * 1024; + break; + case 'g': + case 'G': + /* caveat emptor: more than 2g will overflow */ + /* and really should produce a 'real', so don't do this */ + num *= 1024 * 1024 * 1024; + break; + default: + break; /* not a valid suffix or last char was digit */ + } + make_int(&value, num); + } else { + /* use the PS scanner to capture other valid token types */ + stream astream; + scanner_state state; + + s_init(&astream, NULL); + sread_string(&astream, + (const byte *)eqp, strlen(eqp)); + gs_scanner_init_stream(&state, &astream); + code = gs_scan_token(minst->i_ctx_p, &value, &state); + if (code) { + outprintf(minst->heap, "Invalid value for option -d%s, -dNAME= must be followed by a valid token\n", arg); + arg_free((char *)adef, minst->heap); + return gs_error_Fatal; + } + if (r_has_type_attrs(&value, t_name, + a_executable)) { + ref nsref; + + name_string_ref(minst->heap, &value, &nsref); +#undef string_is +#define string_is(nsref, str, len)\ + (r_size(&(nsref)) == (len) &&\ + !strncmp((const char *)(nsref).value.const_bytes, str, (len))) + if (string_is(nsref, "null", 4)) + make_null(&value); + else if (string_is(nsref, "true", 4)) + make_true(&value); + else if (string_is(nsref, "false", 5)) + make_false(&value); + else { + outprintf(minst->heap, "Invalid value for option -d%s, use -sNAME= to define string constants\n", arg); + arg_free((char *)adef, minst->heap); + return gs_error_Fatal; + } + } + } + } else { + int len = strlen(eqp); + byte *body = ialloc_string(len, "-s"); + + if (body == NULL) { + lprintf("Out of memory!\n"); + arg_free((char *)adef, minst->heap); + return gs_error_Fatal; + } + memcpy(body, eqp, len); + make_const_string(&value, a_readonly | avm_system, len, body); + if ((code = try_stdout_redirect(minst, adef, eqp)) < 0) { + arg_free((char *)adef, minst->heap); + return code; + } + } + ialloc_set_space(idmemory, space); + } + /* Enter the name in systemdict. */ + i_initial_enter_name_copy(minst->i_ctx_p, adef, &value); + arg_free((char *)adef, minst->heap); + break; + } + case 'u': /* undefine name */ + if (!*arg) { + puts(minst->heap, "-u requires a name to undefine."); + return gs_error_Fatal; + } + if ((code = gs_main_init1(minst)) < 0) + return code; + i_initial_remove_name(minst->i_ctx_p, arg); + break; + case 'v': /* print revision */ + print_revision(minst); + return gs_error_Info; +/*#ifdef DEBUG */ + /* + * Here we provide a place for inserting debugging code that can be + * run in place of the normal interpreter code. + */ + case 'X': + code = gs_main_init2(minst); + if (code < 0) + return code; + { + int xec; /* exit_code */ + ref xeo; /* error_object */ + +#define start_x()\ + gs_main_run_string_begin(minst, 1, &xec, &xeo) +#define run_x(str)\ + gs_main_run_string_continue(minst, str, strlen(str), 1, &xec, &xeo) +#define stop_x()\ + gs_main_run_string_end(minst, 1, &xec, &xeo) + start_x(); + run_x("\216\003abc"); + run_x("== flush\n"); + stop_x(); + } + return gs_error_Quit; +/*#endif */ + case 'Z': + set_debug_flags(arg, gs_debug); + break; + } + return 0; +} + +/* Define versions of strlen and strcat that encode strings in hex. */ +/* This is so we can enter escaped characters regardless of whether */ +/* the Level 1 convention of ignoring \s in strings-within-strings */ +/* is being observed (sigh). */ +static int +esc_strlen(const char *str) +{ + return strlen(str) * 2 + 2; +} +static void +esc_strcat(char *dest, const char *src) +{ + char *d = dest + strlen(dest); + const char *p; + static const char *const hex = "0123456789abcdef"; + + *d++ = '<'; + for (p = src; *p; p++) { + byte c = (byte) * p; + + *d++ = hex[c >> 4]; + *d++ = hex[c & 0xf]; + } + *d++ = '>'; + *d = 0; +} + +/* Process file names */ +static int +argproc(gs_main_instance * minst, const char *arg) +{ + int code1, code = gs_main_init1(minst); /* need i_ctx_p to proceed */ + + if (code < 0) + return code; + + code = gs_add_control_path(minst->heap, gs_permit_file_reading, arg); + if (code < 0) return code; + + if (minst->run_buffer_size) { + /* Run file with run_string. */ + code = run_buffered(minst, arg); + } else { + /* Run file directly in the normal way. */ + code = runarg(minst, "", arg, ".runfile", runInit | runFlush, minst->user_errors, NULL, NULL); + } + + code1 = gs_remove_control_path(minst->heap, gs_permit_file_reading, arg); + if (code >= 0 && code1 < 0) code = code1; + + return code; +} +static int +run_buffered(gs_main_instance * minst, const char *arg) +{ + gp_file *in = gp_fopen(minst->heap, arg, gp_fmode_rb); + int exit_code; + ref error_object; + int code; + + if (in == 0) { + outprintf(minst->heap, "Unable to open %s for reading", arg); + return_error(gs_error_invalidfileaccess); + } + code = gs_main_init2(minst); + if (code < 0) { + gp_fclose(in); + return code; + } + code = gs_main_run_string_begin(minst, minst->user_errors, + &exit_code, &error_object); + if (!code) { + char buf[MAX_BUFFERED_SIZE]; + int count; + + code = gs_error_NeedInput; + while ((count = gp_fread(buf, 1, minst->run_buffer_size, in)) > 0) { + code = gs_main_run_string_continue(minst, buf, count, + minst->user_errors, + &exit_code, &error_object); + if (code != gs_error_NeedInput) + break; + } + if (code == gs_error_NeedInput) { + code = gs_main_run_string_end(minst, minst->user_errors, + &exit_code, &error_object); + } + } + gp_fclose(in); + zflush(minst->i_ctx_p); + zflushpage(minst->i_ctx_p); + return run_finish(minst, code, exit_code, &error_object); +} +static int +runarg(gs_main_instance *minst, + const char *pre, + const char *arg, + const char *post, + int options, + int user_errors, + int *pexit_code, + ref *perror_object) +{ + int len = strlen(pre) + esc_strlen(arg) + strlen(post) + 1; + int code; + char *line; + + if (options & runInit) { + code = gs_main_init2(minst); /* Finish initialization */ + + if (code < 0) + return code; + } + line = (char *)gs_alloc_bytes(minst->heap, len, "runarg"); + if (line == 0) { + lprintf("Out of memory!\n"); + return_error(gs_error_VMerror); + } + strcpy(line, pre); + esc_strcat(line, arg); + strcat(line, post); + minst->i_ctx_p->starting_arg_file = true; + code = run_string(minst, line, options, user_errors, pexit_code, perror_object); + minst->i_ctx_p->starting_arg_file = false; + gs_free_object(minst->heap, line, "runarg"); + return code; +} +int +gs_main_run_file2(gs_main_instance *minst, + const char *filename, + int user_errors, + int *pexit_code, + ref *perror_object) +{ + return runarg(minst, "", filename, ".runfile", runFlush, user_errors, pexit_code, perror_object); +} +static int +run_string(gs_main_instance *minst, + const char *str, + int options, + int user_errors, + int *pexit_code, + ref *perror_object) +{ + int exit_code; + ref error_object; + int code; + + if (pexit_code == NULL) + pexit_code = &exit_code; + if (perror_object == NULL) + perror_object = &error_object; + + code = gs_main_run_string(minst, str, user_errors, + pexit_code, perror_object); + + if ((options & runFlush) || code != 0) { + zflush(minst->i_ctx_p); /* flush stdout */ + zflushpage(minst->i_ctx_p); /* force display update */ + } + return run_finish(minst, code, *pexit_code, perror_object); +} +static int +run_finish(gs_main_instance *minst, int code, int exit_code, + ref * perror_object) +{ + switch (code) { + case gs_error_Quit: + case 0: + break; + case gs_error_Fatal: + if (exit_code == gs_error_InterpreterExit) + code = exit_code; + else + emprintf1(minst->heap, + "Unrecoverable error, exit code %d\n", + exit_code); + break; + default: + gs_main_dump_stack(minst, code, perror_object); + } + return code; +} + +/* Redirect stdout to a file: + * -sstdout=filename + * -sstdout=- + * -sstdout=%stdout + * -sstdout=%stderr + * -sOutputFile=- is not affected. + * File is closed at program exit (if not stdout/err) + * or when -sstdout is used again. + */ +static int +try_stdout_redirect(gs_main_instance * minst, + const char *command, const char *filename) +{ + gs_lib_ctx_core_t *core = minst->heap->gs_lib_ctx->core; + if (strcmp(command, "stdout") == 0) { + core->stdout_to_stderr = 0; + core->stdout_is_redirected = 0; + /* If stdout already being redirected and it is not stdout + * or stderr, close it + */ + if (core->fstdout2 + && (gp_get_file(core->fstdout2) != core->fstdout) + && (gp_get_file(core->fstdout2) != core->fstderr)) { + gp_fclose(core->fstdout2); + core->fstdout2 = NULL; + } + /* If stdout is being redirected, set minst->fstdout2 */ + if ( (filename != 0) && strlen(filename) && + strcmp(filename, "-") && strcmp(filename, "%stdout") ) { + if (strcmp(filename, "%stderr") == 0) { + core->stdout_to_stderr = 1; + } else { + core->fstdout2 = gp_fopen(minst->heap, filename, "w"); + if (core->fstdout2 == NULL) + return_error(gs_error_invalidfileaccess); + } + core->stdout_is_redirected = 1; + } + return 0; + } + return 1; +} + +/* ---------------- Print information ---------------- */ + +/* + * Help strings. We have to break them up into parts, because + * the Watcom compiler has a limit of 510 characters for a single token. + * For PC displays, we want to limit the strings to 24 lines. + */ +static const char help_usage1[] = "\ +Usage: gs [switches] [file1.ps file2.ps ...]\n\ +Most frequently used switches: (you can use # in place of =)\n\ + -dNOPAUSE no pause after page | -q `quiet', fewer messages\n\ + -g<width>x<height> page size in pixels | -r<res> pixels/inch resolution\n"; +static const char help_usage2[] = "\ + -sDEVICE=<devname> select device | -dBATCH exit after last file\n\ + -sOutputFile=<file> select output file: - for stdout, |command for pipe,\n\ + embed %d or %ld for page #\n"; +#ifdef DEBUG +static const char help_debug[] = "\ + --debug=<option>[,<option>]* select debugging options\n\ + --debug list debugging options\n"; +#endif +static const char help_trailer[] = "\ +For more information, see %s.\n\ +Please report bugs to bugs.ghostscript.com.\n"; +static const char help_devices[] = "Available devices:"; +static const char help_default_device[] = "Default output device:"; +static const char help_emulators[] = "Input formats:"; +static const char help_paths[] = "Search path:"; +#ifdef HAVE_FONTCONFIG +static const char help_fontconfig[] = "Ghostscript is also using fontconfig to search for font files\n"; +#else +static const char help_fontconfig[] = ""; +#endif + +extern_gx_io_device_table(); + +/* Print the standard help message. */ +static void +print_help(gs_main_instance * minst) +{ + int i, have_rom_device = 0; + + print_revision(minst); + print_usage(minst); + print_emulators(minst); + print_devices(minst); + print_paths(minst); + /* Check if we have the %rom device */ + for (i = 0; i < gx_io_device_table_count; i++) { + const gx_io_device *iodev = gx_io_device_table[i]; + const char *dname = iodev->dname; + + if (dname && strlen(dname) == 5 && !memcmp("%rom%", dname, 5)) { + struct stat pstat; + /* gs_error_unregistered means no usable romfs is available */ + int code = iodev->procs.file_status((gx_io_device *)iodev, dname, &pstat); + if (code != gs_error_unregistered){ + have_rom_device = 1; + } + break; + } + } + if (have_rom_device) { + outprintf(minst->heap, "Initialization files are compiled into the executable.\n"); + } + print_help_trailer(minst); +} + +/* Print the revision, revision date, and copyright. */ +static void +print_revision(const gs_main_instance *minst) +{ + printf_program_ident(minst->heap, gs_product, gs_revision); + outprintf(minst->heap, " (%d-%02d-%02d)\n%s\n", + (int)(gs_revisiondate / 10000), + (int)(gs_revisiondate / 100 % 100), + (int)(gs_revisiondate % 100), + gs_copyright); +} + +/* Print the version number. */ +static void +print_version(const gs_main_instance *minst) +{ + printf_program_ident(minst->heap, NULL, gs_revision); +} + +/* Print usage information. */ +static void +print_usage(const gs_main_instance *minst) +{ + outprintf(minst->heap, "%s", help_usage1); + outprintf(minst->heap, "%s", help_usage2); +#ifdef DEBUG + outprintf(minst->heap, "%s", help_debug); +#endif +} + +/* compare function for qsort */ +static int +cmpstr(const void *v1, const void *v2) +{ + return strcmp( *(char * const *)v1, *(char * const *)v2 ); +} + +/* Print the list of available devices. */ +static void +print_devices(const gs_main_instance *minst) +{ + outprintf(minst->heap, "%s", help_default_device); + outprintf(minst->heap, " %s\n", gs_devicename(gs_getdefaultdevice())); + outprintf(minst->heap, "%s", help_devices); + { + int i; + int pos = 100; + const gx_device *pdev; + const char **names; + size_t ndev = 0; + + for (i = 0; gs_getdevice(i) != 0; i++) + ; + ndev = (size_t)i; + names = (const char **)gs_alloc_bytes(minst->heap, ndev * sizeof(const char*), "print_devices"); + if (names == (const char **)NULL) { /* old-style unsorted device list */ + for (i = 0; (pdev = gs_getdevice(i)) != 0; i++) { + const char *dname = gs_devicename(pdev); + int len = strlen(dname); + + if (pos + 1 + len > 76) + outprintf(minst->heap, "\n "), pos = 2; + outprintf(minst->heap, " %s", dname); + pos += 1 + len; + } + } + else { /* new-style sorted device list */ + for (i = 0; (pdev = gs_getdevice(i)) != 0; i++) + names[i] = gs_devicename(pdev); + qsort((void*)names, ndev, sizeof(const char*), cmpstr); + for (i = 0; i < ndev; i++) { + int len = strlen(names[i]); + + if (pos + 1 + len > 76) + outprintf(minst->heap, "\n "), pos = 2; + outprintf(minst->heap, " %s", names[i]); + pos += 1 + len; + } + gs_free(minst->heap, (char *)names, ndev * sizeof(const char*), 1, "print_devices"); + } + } + outprintf(minst->heap, "\n"); +} + +/* Print the list of language emulators. */ +static void +print_emulators(const gs_main_instance *minst) +{ + outprintf(minst->heap, "%s", help_emulators); + { + const byte *s; + + for (s = gs_emulators; s[0] != 0; s += strlen((const char *)s) + 1) + outprintf(minst->heap, " %s", s); + } + outprintf(minst->heap, "\n"); +} + +/* Print the search paths. */ +static void +print_paths(gs_main_instance * minst) +{ + outprintf(minst->heap, "%s", help_paths); + gs_main_set_lib_paths(minst); + { + uint count = r_size(&minst->lib_path.list); + uint i; + int pos = 100; + char fsepr[3]; + + fsepr[0] = ' ', fsepr[1] = gp_file_name_list_separator, + fsepr[2] = 0; + for (i = 0; i < count; ++i) { + const ref *prdir = + minst->lib_path.list.value.refs + i; + uint len = r_size(prdir); + const char *sepr = (i == count - 1 ? "" : fsepr); + + if (1 + pos + strlen(sepr) + len > 76) + outprintf(minst->heap, "\n "), pos = 2; + outprintf(minst->heap, " "); + /* + * This is really ugly, but it's necessary because some + * platforms rely on all console output being funneled through + * outprintf. We wish we could just do: + fwrite(prdir->value.bytes, 1, len, minst->fstdout); + */ + { + const char *p = (const char *)prdir->value.bytes; + uint j; + + for (j = len; j; j--) + outprintf(minst->heap, "%c", *p++); + } + outprintf(minst->heap, "%s", sepr); + pos += 1 + len + strlen(sepr); + } + } + outprintf(minst->heap, "\n"); + outprintf(minst->heap, "%s", help_fontconfig); +} + +/* Print the help trailer. */ +static void +print_help_trailer(const gs_main_instance *minst) +{ + char buffer[gp_file_name_sizeof]; + const char *use_htm = "Use.htm", *p = buffer; + uint blen = sizeof(buffer); + + if (gp_file_name_combine(gs_doc_directory, strlen(gs_doc_directory), + use_htm, strlen(use_htm), false, buffer, &blen) != gp_combine_success) + p = use_htm; + outprintf(minst->heap, help_trailer, p); +} |