/* GNU m4 -- A simple macro processor Copyright (C) 1989-1994, 1999-2000, 2003-2010, 2013-2014, 2017 Free Software Foundation, Inc. This file is part of GNU M4. GNU M4 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNU M4 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "m4.h" #include "closein.h" #include "configmake.h" #include "getopt.h" #include "propername.h" #include "quotearg.h" #include "version-etc.h" #include "xstrtol.h" #define AUTHORS \ proper_name_utf8 ("Rene' Seindal", "Ren\xc3\xa9 Seindal"), \ proper_name ("Gary V. Vaughan"), \ proper_name ("Eric Blake") typedef struct deferred { struct deferred *next; int code; /* deferred optchar */ const char *value; } deferred; /* Error handling functions. */ #ifdef USE_STACKOVF /* Tell user stack overflowed and abort. */ static void stackovf_handler (void) { /* FIXME - calling gettext and error inside a signal handler is dangerous, since these functions invoke functions that are not signal-safe. We are sort of justified by the fact that we will exit and never return, but this should really be fixed. */ error (EXIT_FAILURE, 0, _("stack overflow (infinite define recursion?)")); } #endif /* USE_STACKOVF */ /* Print a usage message and exit with STATUS. */ static void usage (int status) { if (status != EXIT_SUCCESS) xfprintf (stderr, _("Try `%s --help' for more information.\n"), m4_get_program_name ()); else { xprintf (_("Usage: %s [OPTION]... [FILE]...\n"), m4_get_program_name ()); fputs (_("\ Process macros in FILEs.\n\ If no FILE or if FILE is `-', standard input is read. If no FILE, and both\n\ standard input and standard error are terminals, -i is implied.\n\ "), stdout); puts (""); fputs (_("\ Mandatory or optional arguments to long options are mandatory or optional\n\ for short options too.\n\ \n\ Operation modes:\n\ --help display this help and exit\n\ --version output version information and exit\n\ "), stdout); fputs (_("\ -b, --batch buffer output, process interrupts\n\ -c, --discard-comments do not copy comments to the output\n\ -E, --fatal-warnings once: warnings become errors, twice: stop\n\ execution at first error\n\ -i, --interactive unbuffer output, ignore interrupts\n\ -P, --prefix-builtins force a `m4_' prefix to all builtins\n\ -Q, --quiet, --silent suppress some warnings for builtins\n\ -r, --regexp-syntax[=SPEC] set default regexp syntax to SPEC [GNU_M4]\n\ --safer disable potentially unsafe builtins\n\ -W, --warnings enable all warnings\n\ "), stdout); puts (""); fputs (_("\ SPEC is any one of:\n\ AWK, BASIC, BSD_M4, ED, EMACS, EXTENDED, GNU_AWK, GNU_EGREP, GNU_M4,\n\ GREP, POSIX_AWK, POSIX_EGREP, MINIMAL, MINIMAL_BASIC, SED.\n\ "), stdout); puts (""); fputs (_("\ Preprocessor features:\n\ -B, --prepend-include=DIR add DIR to include path before `.'\n\ -D, --define=NAME[=VALUE] define NAME as having VALUE, or empty\n\ --import-environment import all environment variables as macros\n\ -I, --include=DIR add DIR to include path after `.'\n\ "), stdout); fputs (_("\ --popdef=NAME popdef NAME\n\ -p, --pushdef=NAME[=VALUE] pushdef NAME as having VALUE, or empty\n\ -s, --synclines short for --syncoutput=1\n\ --syncoutput[=STATE] set generation of `#line NUM \"FILE\"' lines\n\ to STATE (0=off, 1=on, default 0)\n\ -U, --undefine=NAME undefine NAME\n\ "), stdout); puts (""); fputs (_("\ Limits control:\n\ -g, --gnu override -G to re-enable GNU extensions\n\ -G, --traditional, --posix suppress all GNU extensions\n\ -L, --nesting-limit=NUMBER change artificial nesting limit [1024]\n\ "), stdout); puts (""); fputs (_("\ Frozen state files:\n\ -F, --freeze-state=FILE produce a frozen state on FILE at end\n\ -R, --reload-state=FILE reload a frozen state from FILE at start\n\ "), stdout); puts (""); fputs (_("\ Debugging:\n\ -d, --debug[=[-|+]FLAGS], --debugmode[=[-|+]FLAGS]\n\ set debug level (no FLAGS implies `+adeq')\n\ --debugfile[=FILE] redirect debug and trace output to FILE\n\ (default stderr, discard if empty string)\n\ -l, --debuglen=NUM restrict macro tracing size\n\ -t, --trace=NAME, --traceon=NAME\n\ trace NAME when it is defined\n\ --traceoff=NAME no longer trace NAME\n\ "), stdout); puts (""); fputs (_("\ FLAGS is any of:\n\ a show actual arguments in trace\n\ c show collection line in trace\n\ d warn when dereferencing undefined macros (default on unless -E)\n\ e show expansion in trace\n\ f include current input file name in trace and debug\n\ i show changes in input files in debug\n\ l include current input line number in trace and debug\n\ "), stdout); fputs (_("\ m show module information in trace, debug, and dumpdef\n\ o output dumpdef to stderr rather than debug file\n\ p show results of path searches in debug\n\ q quote values in dumpdef and trace, useful with a or e\n\ s show full stack of pushdef values in dumpdef\n\ t trace all macro calls, regardless of per-macro traceon state\n\ x include unique macro call id in trace, useful with c\n\ V shorthand for all of the above flags\n\ "), stdout); puts (""); fputs (_("\ If defined, the environment variable `M4PATH' is a colon-separated list\n\ of directories included after any specified by `-I' or `-B'. The\n\ environment variable `POSIXLY_CORRECT' implies -G -Q; otherwise GNU\n\ extensions are enabled by default.\n\ "), stdout); puts (""); fputs (_("\ Exit status is 0 for success, 1 for failure, 63 for frozen file version\n\ mismatch, or whatever value was passed to the m4exit macro.\n\ "), stdout); emit_bug_reporting_address (); } exit (status); } /* For long options that have no equivalent short option, use a non-character as a pseudo short option, starting with CHAR_MAX + 1. */ enum { ARGLENGTH_OPTION = CHAR_MAX + 1, /* not quite -l, because of message */ DEBUGFILE_OPTION, /* no short opt */ ERROR_OUTPUT_OPTION, /* not quite -o, because of message */ HASHSIZE_OPTION, /* not quite -H, because of message */ IMPORT_ENVIRONMENT_OPTION, /* no short opt */ POPDEF_OPTION, /* no short opt */ PREPEND_INCLUDE_OPTION, /* not quite -B, because of message */ SAFER_OPTION, /* -S still has old no-op semantics */ SYNCOUTPUT_OPTION, /* not quite -s, because of opt arg */ TRACEOFF_OPTION, /* no short opt */ WORD_REGEXP_OPTION, /* deprecated, used to be -W */ HELP_OPTION, /* no short opt */ VERSION_OPTION /* no short opt */ }; /* Decode options and launch execution. */ static const struct option long_options[] = { {"batch", no_argument, NULL, 'b'}, {"debug", optional_argument, NULL, 'd'}, {"debuglen", required_argument, NULL, 'l'}, {"debugmode", optional_argument, NULL, 'd'}, {"define", required_argument, NULL, 'D'}, {"discard-comments", no_argument, NULL, 'c'}, {"fatal-warnings", no_argument, NULL, 'E'}, {"freeze-state", required_argument, NULL, 'F'}, {"gnu", no_argument, NULL, 'g'}, {"include", required_argument, NULL, 'I'}, {"interactive", no_argument, NULL, 'i'}, {"nesting-limit", required_argument, NULL, 'L'}, {"posix", no_argument, NULL, 'G'}, {"prefix-builtins", no_argument, NULL, 'P'}, {"pushdef", required_argument, NULL, 'p'}, {"quiet", no_argument, NULL, 'Q'}, {"regexp-syntax", optional_argument, NULL, 'r'}, {"reload-state", required_argument, NULL, 'R'}, {"silent", no_argument, NULL, 'Q'}, {"synclines", no_argument, NULL, 's'}, {"trace", required_argument, NULL, 't'}, {"traceon", required_argument, NULL, 't'}, {"traditional", no_argument, NULL, 'G'}, {"undefine", required_argument, NULL, 'U'}, {"warnings", no_argument, NULL, 'W'}, {"arglength", required_argument, NULL, ARGLENGTH_OPTION}, {"debugfile", optional_argument, NULL, DEBUGFILE_OPTION}, {"hashsize", required_argument, NULL, HASHSIZE_OPTION}, {"error-output", required_argument, NULL, ERROR_OUTPUT_OPTION}, {"import-environment", no_argument, NULL, IMPORT_ENVIRONMENT_OPTION}, {"popdef", required_argument, NULL, POPDEF_OPTION}, {"prepend-include", required_argument, NULL, PREPEND_INCLUDE_OPTION}, {"safer", no_argument, NULL, SAFER_OPTION}, {"syncoutput", optional_argument, NULL, SYNCOUTPUT_OPTION}, {"traceoff", required_argument, NULL, TRACEOFF_OPTION}, {"word-regexp", required_argument, NULL, WORD_REGEXP_OPTION}, {"help", no_argument, NULL, HELP_OPTION}, {"version", no_argument, NULL, VERSION_OPTION}, { NULL, 0, NULL, 0 }, }; /* POSIX requires only -D, -U, and -s; and says that the first two must be recognized when interspersed with file names. Traditional behavior also handles -s between files. Starting OPTSTRING with '-' forces getopt_long to hand back file names as arguments to opt '\1', rather than reordering the command line. */ #define OPTSTRING "-B:D:EF:GH:I:L:PQR:S:T:U:Wbcd::egil:o:p:r::st:" /* For determining whether to be interactive. */ enum interactive_choice { INTERACTIVE_UNKNOWN, /* Still processing arguments, no -b or -i yet */ INTERACTIVE_YES, /* -i specified last */ INTERACTIVE_NO /* -b specified last */ }; /* Convert OPT to size_t, reporting an error using long option index OI or short option character OPTCHAR if it does not fit. */ static size_t size_opt (char const *opt, int oi, int optchar) { unsigned long int size; strtol_error status = xstrtoul (opt, NULL, 10, &size, "kKmMgGtTPEZY0"); if (SIZE_MAX < size && status == LONGINT_OK) status = LONGINT_OVERFLOW; if (status != LONGINT_OK) xstrtol_fatal (status, oi, optchar, long_options, opt); return size; } /* Process a command line file NAME. */ static bool process_file (m4 *context, const char *name) { bool new_input = true; if (STREQ (name, "-")) /* TRANSLATORS: This is a short name for `standard input', used when a command line file was given as `-'. */ m4_push_file (context, stdin, _("stdin"), false); else new_input = m4_load_filename (context, NULL, name, NULL, false); if (new_input) m4_macro_expand_input (context); return new_input; } /* Main entry point. Parse arguments, load modules, then parse input. */ int main (int argc, char *const *argv, char *const *envp) { deferred *head = NULL; /* head of deferred argument list */ deferred *tail = NULL; deferred *defn; size_t size; /* for parsing numeric option arguments */ bool import_environment = false; /* true to import environment */ bool seen_file = false; const char *debugfile = NULL; const char *frozen_file_to_read = NULL; const char *frozen_file_to_write = NULL; enum interactive_choice interactive = INTERACTIVE_UNKNOWN; m4 *context; int exit_status; /* Initialize gnulib error module. */ m4_set_program_name (argv[0]); atexit (close_stdin); setlocale (LC_ALL, ""); #ifdef ENABLE_NLS textdomain (PACKAGE); #endif context = m4_create (); #ifdef USE_STACKOVF setup_stackovf_trap (argv, envp, stackovf_handler); #endif if (getenv ("POSIXLY_CORRECT")) { m4_set_posixly_correct_opt (context, true); m4_set_suppress_warnings_opt (context, true); } set_quoting_style (NULL, escape_quoting_style); set_char_quoting (NULL, ':', 1); /* First, we decode the arguments, to size up tables and stuff. Avoid lasting side effects; for example 'm4 --debugfile=oops --help' must not create the file `oops'. */ while (1) { int oi = -1; int optchar = getopt_long (argc, (char **) argv, OPTSTRING, long_options, &oi); if (optchar == -1) break; switch (optchar) { default: usage (EXIT_FAILURE); case 'H': case HASHSIZE_OPTION: /* -H was supported in 1.4.x, but is a no-op now. FIXME - remove support for -H after 2.0. */ error (0, 0, _("warning: `%s' is deprecated"), optchar == 'H' ? "-H" : "--hashsize"); break; case 'S': case 'T': /* Compatibility junk: options that other implementations support, but which we ignore as no-ops and don't list in --help. */ error (0, 0, _("warning: `-%c' is deprecated"), optchar); break; case WORD_REGEXP_OPTION: /* Supported in 1.4.x as -W, but no longer present. */ error (0, 0, _("warning: `%s' is deprecated"), "--word-regexp"); break; case 's': optchar = SYNCOUTPUT_OPTION; optarg = "1"; /* fall through */ case 'D': case 'U': case 'p': case 'r': case 't': case POPDEF_OPTION: case SYNCOUTPUT_OPTION: case TRACEOFF_OPTION: defer: /* Arguments that cannot be handled until later are accumulated. */ defn = (deferred *) xmalloc (sizeof *defn); defn->code = optchar; defn->value = optarg; defn->next = NULL; if (head == NULL) head = defn; else tail->next = defn; tail = defn; break; case '\1': seen_file = true; goto defer; case 'B': /* In 1.4.x, -B was a no-op option for compatibility with Solaris m4. Warn if optarg is all numeric. FIXME - silence this warning after 2.0. */ if (isdigit (to_uchar (*optarg))) { char *end; errno = 0; strtol (optarg, &end, 10); if (*end == '\0' && errno == 0) error (0, 0, _("warning: recommend using `-B ./%s' instead"), optarg); } /* fall through */ case PREPEND_INCLUDE_OPTION: m4_add_include_directory (context, optarg, true); break; case 'E': m4_debug_decode (context, "-d", SIZE_MAX); if (m4_get_fatal_warnings_opt (context)) m4_set_warnings_exit_opt (context, true); else m4_set_fatal_warnings_opt (context, true); break; case 'F': frozen_file_to_write = optarg; break; case 'G': m4_set_posixly_correct_opt (context, true); break; case 'I': m4_add_include_directory (context, optarg, false); break; case 'L': size = size_opt (optarg, oi, optchar); if (!size) size = SIZE_MAX; m4_set_nesting_limit_opt (context, size); break; case 'P': m4_set_prefix_builtins_opt (context, true); break; case 'Q': m4_set_suppress_warnings_opt (context, true); break; case 'R': frozen_file_to_read = optarg; break; case 'W': /* FIXME - should W take an optional argument, to allow -Wall, -Wnone, -Werror, -Wcategory, -Wno-category? If so, then have -W == -Wall. */ m4_set_suppress_warnings_opt (context, false); break; case 'b': interactive = INTERACTIVE_NO; break; case 'c': m4_set_discard_comments_opt (context, true); break; case 'd': /* Staggered handling of 'd', since -dm is useful prior to first file and prior to reloading, but other -d must also have effect between files. */ if (seen_file || frozen_file_to_read) goto defer; if (m4_debug_decode (context, optarg, SIZE_MAX) < 0) error (0, 0, _("bad debug flags: %s"), quotearg_style (locale_quoting_style, optarg)); break; case 'e': error (0, 0, _("warning: `%s' is deprecated, use `%s' instead"), "-e", "-i"); /* fall through */ case 'i': interactive = INTERACTIVE_YES; break; case 'g': m4_set_posixly_correct_opt (context, false); break; case ARGLENGTH_OPTION: error (0, 0, _("warning: `%s' is deprecated, use `%s' instead"), "--arglength", "--debuglen"); /* fall through */ case 'l': size = size_opt (optarg, oi, optchar); if (!size) size = SIZE_MAX; m4_set_max_debug_arg_length_opt (context, size); break; case DEBUGFILE_OPTION: /* Staggered handling of '--debugfile', since it is useful prior to first file and prior to reloading, but other uses must also have effect between files. */ if (seen_file || frozen_file_to_read) goto defer; debugfile = optarg; break; case 'o': case ERROR_OUTPUT_OPTION: /* FIXME: -o is inconsistent with other tools' use of -o/--output for creating an output file instead of using stdout, and --error-output is misnamed since it does not affect error messages to stderr. Change the meaning of -o after 2.1. */ error (0, 0, _("warning: `%s' is deprecated, use `%s' instead"), optchar == 'o' ? "-o" : "--error-output", "--debugfile"); /* Don't call m4_debug_set_output here, as it has side effects. */ debugfile = optarg; break; case IMPORT_ENVIRONMENT_OPTION: import_environment = true; break; case SAFER_OPTION: m4_set_safer_opt (context, true); break; case VERSION_OPTION: version_etc (stdout, PACKAGE, PACKAGE_NAME, VERSION, AUTHORS, NULL); exit (EXIT_SUCCESS); break; case HELP_OPTION: usage (EXIT_SUCCESS); break; } } /* Do the basic initializations. */ if (debugfile && !m4_debug_set_output (context, NULL, debugfile)) m4_error (context, 0, errno, NULL, _("cannot set debug file %s"), quotearg_style (locale_quoting_style, debugfile)); m4_input_init (context); m4_output_init (context); if (frozen_file_to_read) reload_frozen_state (context, frozen_file_to_read); else { m4_module_load (context, "m4", NULL); if (m4_get_posixly_correct_opt (context)) m4_module_load (context, "traditional", NULL); else m4_module_load (context, "gnu", NULL); } /* Import environment variables as macros. The definition are prepended to the macro definition list, so -U can override environment variables. */ if (import_environment) { char *const *env; for (env = envp; *env != NULL; env++) { defn = (deferred *) xmalloc (sizeof *defn); defn->code = 'D'; defn->value = *env; defn->next = head; head = defn; } } /* Handle deferred command line macro definitions. Must come after initialization of the symbol table. */ defn = head; while (defn != NULL) { deferred *next; const char *arg = defn->value; switch (defn->code) { case 'D': case 'p': { m4_symbol_value *value = m4_symbol_value_create (); const char *str = strchr (arg, '='); size_t len = str ? str - arg : strlen (arg); m4_set_symbol_value_text (value, xstrdup (str ? str + 1 : ""), str ? strlen (str + 1) : 0, 0); if (defn->code == 'D') m4_symbol_define (M4SYMTAB, arg, len, value); else m4_symbol_pushdef (M4SYMTAB, arg, len, value); } break; case 'U': m4_symbol_delete (M4SYMTAB, arg, strlen (arg)); break; case 'd': if (m4_debug_decode (context, arg, SIZE_MAX) < 0) error (0, 0, _("bad debug flags: %s"), quotearg_style (locale_quoting_style, arg)); break; case 'r': m4_set_regexp_syntax_opt (context, m4_regexp_syntax_encode (arg)); if (m4_get_regexp_syntax_opt (context) < 0) m4_error (context, EXIT_FAILURE, 0, NULL, _("bad syntax-spec: %s"), quotearg_style (locale_quoting_style, arg)); break; case 't': m4_set_symbol_name_traced (M4SYMTAB, arg, strlen (arg), true); break; case '\1': if (process_file (context, arg)) seen_file = true; break; case DEBUGFILE_OPTION: if (!m4_debug_set_output (context, NULL, arg)) m4_error (context, 0, errno, NULL, _("cannot set debug file %s"), quotearg_style (locale_quoting_style, arg ? arg : _("stderr"))); break; case POPDEF_OPTION: { size_t len = strlen (arg); if (m4_symbol_lookup (M4SYMTAB, arg, len)) m4_symbol_popdef (M4SYMTAB, arg, len); } break; case SYNCOUTPUT_OPTION: { bool previous = m4_get_syncoutput_opt (context); m4_call_info info = {0}; info.name = "--syncoutput"; info.name_len = strlen (info.name); m4_set_syncoutput_opt (context, m4_parse_truth_arg (context, &info, arg, SIZE_MAX, previous)); } break; case TRACEOFF_OPTION: m4_set_symbol_name_traced (M4SYMTAB, arg, strlen (arg), false); break; default: assert (!"INTERNAL ERROR: bad code in deferred arguments"); abort (); } next = defn->next; free (defn); defn = next; } /* Interactive if specified, or if no input files and stdin and stderr are terminals, to match sh behavior. Interactive mode means unbuffered output, and interrupts ignored. */ m4_set_interactive_opt (context, (interactive == INTERACTIVE_YES || (interactive == INTERACTIVE_UNKNOWN && optind == argc && !seen_file && isatty (STDIN_FILENO) && isatty (STDERR_FILENO)))); if (m4_get_interactive_opt (context)) { signal (SIGINT, SIG_IGN); setbuf (stdout, NULL); } else signal (SIGPIPE, SIG_DFL); /* Handle remaining input files. Each file is pushed on the input, and the input read. */ if (optind == argc && !seen_file) process_file (context, "-"); else for (; optind < argc; optind++) process_file (context, argv[optind]); /* Now handle wrapup text. FIXME - when -F is in effect, should wrapped text be frozen? */ while (m4_pop_wrapup (context)) m4_macro_expand_input (context); if (frozen_file_to_write) produce_frozen_state (context, frozen_file_to_write); else { m4_make_diversion (context, 0); m4_undivert_all (context); } /* The remaining cleanup functions systematically free all of the memory we still have pointers to. By definition, if there is anything left when we're done: it was caused by a memory leak. Strictly, we don't need to do this, but it makes leak detection a whole lot easier! */ m4_output_exit (); m4_input_exit (); /* Change debug stream back to stderr, to force flushing the debug stream and detect any errors it might have encountered. The three standard streams are closed by close_stdin. */ m4_debug_set_output (context, NULL, NULL); exit_status = m4_get_exit_status (context); m4_delete (context); m4_hash_exit (); quotearg_free (); #ifdef USE_STACKOVF stackovf_exit (); #endif exit (exit_status); }