summaryrefslogtreecommitdiffstats
path: root/src/env.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/env.c')
-rw-r--r--src/env.c903
1 files changed, 903 insertions, 0 deletions
diff --git a/src/env.c b/src/env.c
new file mode 100644
index 0000000..f7ef297
--- /dev/null
+++ b/src/env.c
@@ -0,0 +1,903 @@
+/* env - run a program in a modified environment
+ Copyright (C) 1986-2022 Free Software Foundation, Inc.
+
+ This program 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.
+
+ This program 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 <https://www.gnu.org/licenses/>. */
+
+/* Richard Mlynarik and David MacKenzie */
+
+#include <config.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <getopt.h>
+#include <c-ctype.h>
+#include <signal.h>
+
+#include "system.h"
+#include "die.h"
+#include "error.h"
+#include "idx.h"
+#include "operand2sig.h"
+#include "quote.h"
+#include "sig2str.h"
+
+/* The official name of this program (e.g., no 'g' prefix). */
+#define PROGRAM_NAME "env"
+
+#define AUTHORS \
+ proper_name ("Richard Mlynarik"), \
+ proper_name ("David MacKenzie"), \
+ proper_name ("Assaf Gordon")
+
+/* Array of envvars to unset. */
+static char const **usvars;
+static size_t usvars_alloc;
+static idx_t usvars_used;
+
+/* Annotate the output with extra info to aid the user. */
+static bool dev_debug;
+
+/* Buffer and length of extracted envvars in -S strings. */
+static char *varname;
+static idx_t vnlen;
+
+/* Possible actions on each signal. */
+enum SIGNAL_MODE {
+ UNCHANGED = 0,
+ DEFAULT, /* Set to default handler (SIG_DFL). */
+ DEFAULT_NOERR, /* Ditto, but ignore sigaction(2) errors. */
+ IGNORE, /* Set to ignore (SIG_IGN). */
+ IGNORE_NOERR /* Ditto, but ignore sigaction(2) errors. */
+};
+static enum SIGNAL_MODE *signals;
+
+/* Set of signals to block. */
+static sigset_t block_signals;
+
+/* Set of signals to unblock. */
+static sigset_t unblock_signals;
+
+/* Whether signal mask adjustment requested. */
+static bool sig_mask_changed;
+
+/* Whether to list non default handling. */
+static bool report_signal_handling;
+
+/* The isspace characters in the C locale. */
+#define C_ISSPACE_CHARS " \t\n\v\f\r"
+
+static char const shortopts[] = "+C:iS:u:v0" C_ISSPACE_CHARS;
+
+/* For long options that have no equivalent short option, use a
+ non-character as a pseudo short option, starting with CHAR_MAX + 1. */
+enum
+{
+ DEFAULT_SIGNAL_OPTION = CHAR_MAX + 1,
+ IGNORE_SIGNAL_OPTION,
+ BLOCK_SIGNAL_OPTION,
+ LIST_SIGNAL_HANDLING_OPTION,
+};
+
+static struct option const longopts[] =
+{
+ {"ignore-environment", no_argument, NULL, 'i'},
+ {"null", no_argument, NULL, '0'},
+ {"unset", required_argument, NULL, 'u'},
+ {"chdir", required_argument, NULL, 'C'},
+ {"default-signal", optional_argument, NULL, DEFAULT_SIGNAL_OPTION},
+ {"ignore-signal", optional_argument, NULL, IGNORE_SIGNAL_OPTION},
+ {"block-signal", optional_argument, NULL, BLOCK_SIGNAL_OPTION},
+ {"list-signal-handling", no_argument, NULL, LIST_SIGNAL_HANDLING_OPTION},
+ {"debug", no_argument, NULL, 'v'},
+ {"split-string", required_argument, NULL, 'S'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+void
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ emit_try_help ();
+ else
+ {
+ printf (_("\
+Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
+ program_name);
+ fputs (_("\
+Set each NAME to VALUE in the environment and run COMMAND.\n\
+"), stdout);
+
+ emit_mandatory_arg_note ();
+
+ fputs (_("\
+ -i, --ignore-environment start with an empty environment\n\
+ -0, --null end each output line with NUL, not newline\n\
+ -u, --unset=NAME remove variable from the environment\n\
+"), stdout);
+ fputs (_("\
+ -C, --chdir=DIR change working directory to DIR\n\
+"), stdout);
+ fputs (_("\
+ -S, --split-string=S process and split S into separate arguments;\n\
+ used to pass multiple arguments on shebang lines\n\
+"), stdout);
+ fputs (_("\
+ --block-signal[=SIG] block delivery of SIG signal(s) to COMMAND\n\
+"), stdout);
+ fputs (_("\
+ --default-signal[=SIG] reset handling of SIG signal(s) to the default\n\
+"), stdout);
+ fputs (_("\
+ --ignore-signal[=SIG] set handling of SIG signal(s) to do nothing\n\
+"), stdout);
+ fputs (_("\
+ --list-signal-handling list non default signal handling to stderr\n\
+"), stdout);
+ fputs (_("\
+ -v, --debug print verbose information for each processing step\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
+\n\
+A mere - implies -i. If no COMMAND, print the resulting environment.\n\
+"), stdout);
+ fputs (_("\
+\n\
+SIG may be a signal name like 'PIPE', or a signal number like '13'.\n\
+Without SIG, all known signals are included. Multiple signals can be\n\
+comma-separated.\n\
+"), stdout);
+ emit_ancillary_info (PROGRAM_NAME);
+ }
+ exit (status);
+}
+
+static void
+append_unset_var (char const *var)
+{
+ if (usvars_used == usvars_alloc)
+ usvars = x2nrealloc (usvars, &usvars_alloc, sizeof *usvars);
+ usvars[usvars_used++] = var;
+}
+
+static void
+unset_envvars (void)
+{
+ for (idx_t i = 0; i < usvars_used; ++i)
+ {
+ devmsg ("unset: %s\n", usvars[i]);
+
+ if (unsetenv (usvars[i]))
+ die (EXIT_CANCELED, errno, _("cannot unset %s"),
+ quote (usvars[i]));
+ }
+}
+
+/* Return a pointer to the end of a valid ${VARNAME} string, or NULL.
+ 'str' should point to the '$' character.
+ First letter in VARNAME must be alpha or underscore,
+ rest of letters are alnum or underscore.
+ Any other character is an error. */
+ATTRIBUTE_PURE
+static char const *
+scan_varname (char const *str)
+{
+ if (str[1] == '{' && (c_isalpha (str[2]) || str[2] == '_'))
+ {
+ char const *end = str + 3;
+ while (c_isalnum (*end) || *end == '_')
+ ++end;
+ if (*end == '}')
+ return end;
+ }
+
+ return NULL;
+}
+
+/* Return a pointer to a static buffer containing the VARNAME as
+ extracted from a '${VARNAME}' string.
+ The returned string will be NUL terminated.
+ The returned pointer should not be freed.
+ Return NULL if not a valid ${VARNAME} syntax. */
+static char *
+extract_varname (char const *str)
+{
+ idx_t i;
+ char const *p;
+
+ p = scan_varname (str);
+ if (!p)
+ return NULL;
+
+ /* -2 and +2 (below) account for the '${' prefix. */
+ i = p - str - 2;
+
+ if (i >= vnlen)
+ {
+ vnlen = i + 1;
+ varname = xrealloc (varname, vnlen);
+ }
+
+ memcpy (varname, str + 2, i);
+ varname[i] = 0;
+
+ return varname;
+}
+
+/* Temporary buffer used by --split-string processing. */
+struct splitbuf
+{
+ /* Buffer address, arg count, and half the number of elements in the buffer.
+ ARGC and ARGV are as in 'main', and ARGC + 1 <= HALF_ALLOC so
+ that the upper half of ARGV can be used for string contents.
+ This may waste up to half the space but keeps the code simple,
+ which is better for this rarely-used but security-sensitive code.
+
+ ARGV[0] is not initialized; that is the caller's responsibility
+ after finalization.
+
+ During assembly, ARGV[I] (where 0 < I < ARGC) contains the offset
+ of the Ith string (relative to ARGV + HALF_ALLOC), so that
+ reallocating ARGV does not change the validity of its contents.
+ The integer offset is cast to char * during assembly, and is
+ converted to a true char * pointer on finalization.
+
+ During assembly, ARGV[ARGC] contains the offset of the first
+ unused string byte (relative to ARGV + HALF_ALLOC). */
+ char **argv;
+ int argc;
+ idx_t half_alloc;
+
+ /* The number of extra argv slots to keep room for. */
+ int extra_argc;
+
+ /* Whether processing should act as if the most recent character
+ seen was a separator. */
+ bool sep;
+};
+
+/* Expand SS so that it has at least one more argv slot and at least
+ one more string byte. */
+static void
+splitbuf_grow (struct splitbuf *ss)
+{
+ idx_t old_half_alloc = ss->half_alloc;
+ idx_t string_bytes = (intptr_t) ss->argv[ss->argc];
+ ss->argv = xpalloc (ss->argv, &ss->half_alloc, 1,
+ MIN (INT_MAX, IDX_MAX), 2 * sizeof *ss->argv);
+ memmove (ss->argv + ss->half_alloc, ss->argv + old_half_alloc, string_bytes);
+}
+
+/* In SS, append C to the last string. */
+static void
+splitbuf_append_byte (struct splitbuf *ss, char c)
+{
+ idx_t string_bytes = (intptr_t) ss->argv[ss->argc];
+ if (ss->half_alloc * sizeof *ss->argv <= string_bytes)
+ splitbuf_grow (ss);
+ ((char *) (ss->argv + ss->half_alloc))[string_bytes] = c;
+ ss->argv[ss->argc] = (char *) (intptr_t) (string_bytes + 1);
+}
+
+/* If SS's most recent character was a separator, finish off its
+ previous argument and start a new one. */
+static void
+check_start_new_arg (struct splitbuf *ss)
+{
+ if (ss->sep)
+ {
+ splitbuf_append_byte (ss, '\0');
+ int argc = ss->argc;
+ if (ss->half_alloc <= argc + ss->extra_argc + 1)
+ splitbuf_grow (ss);
+ ss->argv[argc + 1] = ss->argv[argc];
+ ss->argc = argc + 1;
+ ss->sep = false;
+ }
+}
+
+/* All additions to SS have been made. Convert its offsets to pointers,
+ and return the resulting argument vector. */
+static char **
+splitbuf_finishup (struct splitbuf *ss)
+{
+ int argc = ss->argc;
+ char **argv = ss->argv;
+ char *stringbase = (char *) (ss->argv + ss->half_alloc);
+ for (int i = 1; i < argc; i++)
+ argv[i] = stringbase + (intptr_t) argv[i];
+ return argv;
+}
+
+/* Return a newly-allocated argv-like array,
+ by parsing and splitting the input 'str'.
+
+ 'extra_argc' is the number of additional elements to allocate
+ in the array (on top of the number of args required to split 'str').
+
+ Store into *argc the number of arguments found (plus 1 for
+ the program name).
+
+ Example:
+ int argc;
+ char **argv = build_argv ("A=B uname -k', 3, &argc);
+ Results in:
+ argc = 4
+ argv[0] = [not initialized]
+ argv[1] = "A=B"
+ argv[2] = "uname"
+ argv[3] = "-k"
+ argv[4,5,6,7] = [allocated due to extra_argc + 1, but not initialized]
+
+ To free allocated memory:
+ free (argv);
+ However, 'env' does not free since it's about to exec or exit anyway
+ and the complexity of keeping track of the storage that may have been
+ allocated via multiple calls to build_argv is not worth the hassle. */
+static char **
+build_argv (char const *str, int extra_argc, int *argc)
+{
+ bool dq = false, sq = false;
+ struct splitbuf ss;
+ ss.argv = xnmalloc (extra_argc + 2, 2 * sizeof *ss.argv);
+ ss.argc = 1;
+ ss.half_alloc = extra_argc + 2;
+ ss.extra_argc = extra_argc;
+ ss.sep = true;
+ ss.argv[ss.argc] = 0;
+
+ /* In the following loop,
+ 'break' causes the character 'newc' to be added to *dest,
+ 'continue' skips the character. */
+ while (*str)
+ {
+ char newc = *str; /* Default: add the next character. */
+
+ switch (*str)
+ {
+ case '\'':
+ if (dq)
+ break;
+ sq = !sq;
+ check_start_new_arg (&ss);
+ ++str;
+ continue;
+
+ case '"':
+ if (sq)
+ break;
+ dq = !dq;
+ check_start_new_arg (&ss);
+ ++str;
+ continue;
+
+ case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
+ /* Start a new argument if outside quotes. */
+ if (sq || dq)
+ break;
+ ss.sep = true;
+ str += strspn (str, C_ISSPACE_CHARS);
+ continue;
+
+ case '#':
+ if (!ss.sep)
+ break;
+ goto eos; /* '#' as first char terminates the string. */
+
+ case '\\':
+ /* Backslash inside single-quotes is not special, except \\
+ and \'. */
+ if (sq && str[1] != '\\' && str[1] != '\'')
+ break;
+
+ /* Skip the backslash and examine the next character. */
+ newc = *++str;
+ switch (newc)
+ {
+ case '"': case '#': case '$': case '\'': case '\\':
+ /* Pass escaped character as-is. */
+ break;
+
+ case '_':
+ if (!dq)
+ {
+ ++str; /* '\_' outside double-quotes is arg separator. */
+ ss.sep = true;
+ continue;
+ }
+ newc = ' '; /* '\_' inside double-quotes is space. */
+ break;
+
+ case 'c':
+ if (dq)
+ die (EXIT_CANCELED, 0,
+ _("'\\c' must not appear in double-quoted -S string"));
+ goto eos; /* '\c' terminates the string. */
+
+ case 'f': newc = '\f'; break;
+ case 'n': newc = '\n'; break;
+ case 'r': newc = '\r'; break;
+ case 't': newc = '\t'; break;
+ case 'v': newc = '\v'; break;
+
+ case '\0':
+ die (EXIT_CANCELED, 0,
+ _("invalid backslash at end of string in -S"));
+
+ default:
+ die (EXIT_CANCELED, 0, _("invalid sequence '\\%c' in -S"), newc);
+ }
+ break;
+
+ case '$':
+ /* ${VARNAME} are not expanded inside single-quotes. */
+ if (sq)
+ break;
+
+ /* Store the ${VARNAME} value. */
+ {
+ char *n = extract_varname (str);
+ if (!n)
+ die (EXIT_CANCELED, 0,
+ _("only ${VARNAME} expansion is supported, error at: %s"),
+ str);
+
+ char *v = getenv (n);
+ if (v)
+ {
+ check_start_new_arg (&ss);
+ devmsg ("expanding ${%s} into %s\n", n, quote (v));
+ for (; *v; v++)
+ splitbuf_append_byte (&ss, *v);
+ }
+ else
+ devmsg ("replacing ${%s} with null string\n", n);
+
+ str = strchr (str, '}') + 1;
+ continue;
+ }
+ }
+
+ check_start_new_arg (&ss);
+ splitbuf_append_byte (&ss, newc);
+ ++str;
+ }
+
+ if (dq || sq)
+ die (EXIT_CANCELED, 0, _("no terminating quote in -S string"));
+
+ eos:
+ splitbuf_append_byte (&ss, '\0');
+ *argc = ss.argc;
+ return splitbuf_finishup (&ss);
+}
+
+/* Process an "-S" string and create the corresponding argv array.
+ Update the given argc/argv parameters with the new argv.
+
+ Example: if executed as:
+ $ env -S"-i -C/tmp A=B" foo bar
+ The input argv is:
+ argv[0] = "env"
+ argv[1] = "-S-i -C/tmp A=B"
+ argv[2] = "foo"
+ argv[3] = "bar"
+ argv[4] = NULL
+ This function will modify argv to be:
+ argv[0] = "env"
+ argv[1] = "-i"
+ argv[2] = "-C/tmp"
+ argv[3] = "A=B"
+ argv[4] = "foo"
+ argv[5] = "bar"
+ argv[6] = NULL
+ argc will be updated from 4 to 6.
+ optind will be reset to 0 to force getopt_long to rescan all arguments. */
+static void
+parse_split_string (char const *str, int *orig_optind,
+ int *orig_argc, char ***orig_argv)
+{
+ int extra_argc = *orig_argc - *orig_optind, newargc;
+ char **newargv = build_argv (str, extra_argc, &newargc);
+
+ /* Restore argv[0] - the 'env' executable name. */
+ *newargv = (*orig_argv)[0];
+
+ /* Print parsed arguments. */
+ if (dev_debug && 1 < newargc)
+ {
+ devmsg ("split -S: %s\n", quote (str));
+ devmsg (" into: %s\n", quote (newargv[1]));
+ for (int i = 2; i < newargc; i++)
+ devmsg (" & %s\n", quote (newargv[i]));
+ }
+
+ /* Add remaining arguments and terminating null from the original
+ command line. */
+ memcpy (newargv + newargc, *orig_argv + *orig_optind,
+ (extra_argc + 1) * sizeof *newargv);
+
+ /* Set new values for original getopt variables. */
+ *orig_argc = newargc + extra_argc;
+ *orig_argv = newargv;
+ *orig_optind = 0; /* Tell getopt to restart from first argument. */
+}
+
+static void
+parse_signal_action_params (char const *optarg, bool set_default)
+{
+ char signame[SIG2STR_MAX];
+ char *opt_sig;
+ char *optarg_writable;
+
+ if (! optarg)
+ {
+ /* Without an argument, reset all signals.
+ Some signals cannot be set to ignore or default (e.g., SIGKILL,
+ SIGSTOP on most OSes, and SIGCONT on AIX.) - so ignore errors. */
+ for (int i = 1 ; i <= SIGNUM_BOUND; i++)
+ if (sig2str (i, signame) == 0)
+ signals[i] = set_default ? DEFAULT_NOERR : IGNORE_NOERR;
+ return;
+ }
+
+ optarg_writable = xstrdup (optarg);
+
+ opt_sig = strtok (optarg_writable, ",");
+ while (opt_sig)
+ {
+ int signum = operand2sig (opt_sig, signame);
+ /* operand2sig accepts signal 0 (EXIT) - but we reject it. */
+ if (signum == 0)
+ error (0, 0, _("%s: invalid signal"), quote (opt_sig));
+ if (signum <= 0)
+ usage (exit_failure);
+
+ signals[signum] = set_default ? DEFAULT : IGNORE;
+
+ opt_sig = strtok (NULL, ",");
+ }
+
+ free (optarg_writable);
+}
+
+static void
+reset_signal_handlers (void)
+{
+ for (int i = 1; i <= SIGNUM_BOUND; i++)
+ {
+ struct sigaction act;
+
+ if (signals[i] == UNCHANGED)
+ continue;
+
+ bool ignore_errors = (signals[i] == DEFAULT_NOERR
+ || signals[i] == IGNORE_NOERR);
+
+ bool set_to_default = (signals[i] == DEFAULT
+ || signals[i] == DEFAULT_NOERR);
+
+ int sig_err = sigaction (i, NULL, &act);
+
+ if (sig_err && !ignore_errors)
+ die (EXIT_CANCELED, errno,
+ _("failed to get signal action for signal %d"), i);
+
+ if (! sig_err)
+ {
+ act.sa_handler = set_to_default ? SIG_DFL : SIG_IGN;
+ sig_err = sigaction (i, &act, NULL);
+ if (sig_err && !ignore_errors)
+ die (EXIT_CANCELED, errno,
+ _("failed to set signal action for signal %d"), i);
+ }
+
+ if (dev_debug)
+ {
+ char signame[SIG2STR_MAX];
+ sig2str (i, signame);
+ devmsg ("Reset signal %s (%d) to %s%s\n",
+ signame, i,
+ set_to_default ? "DEFAULT" : "IGNORE",
+ sig_err ? " (failure ignored)" : "");
+ }
+ }
+}
+
+
+static void
+parse_block_signal_params (char const *optarg, bool block)
+{
+ char signame[SIG2STR_MAX];
+ char *opt_sig;
+ char *optarg_writable;
+
+ if (! optarg)
+ {
+ /* Without an argument, reset all signals. */
+ sigfillset (block ? &block_signals : &unblock_signals);
+ sigemptyset (block ? &unblock_signals : &block_signals);
+ }
+ else if (! sig_mask_changed)
+ {
+ /* Initialize the sets. */
+ sigemptyset (&block_signals);
+ sigemptyset (&unblock_signals);
+ }
+
+ sig_mask_changed = true;
+
+ if (! optarg)
+ return;
+
+ optarg_writable = xstrdup (optarg);
+
+ opt_sig = strtok (optarg_writable, ",");
+ while (opt_sig)
+ {
+ int signum = operand2sig (opt_sig, signame);
+ /* operand2sig accepts signal 0 (EXIT) - but we reject it. */
+ if (signum == 0)
+ error (0, 0, _("%s: invalid signal"), quote (opt_sig));
+ if (signum <= 0)
+ usage (exit_failure);
+
+ sigaddset (block ? &block_signals : &unblock_signals, signum);
+ sigdelset (block ? &unblock_signals : &block_signals, signum);
+
+ opt_sig = strtok (NULL, ",");
+ }
+
+ free (optarg_writable);
+}
+
+static void
+set_signal_proc_mask (void)
+{
+ /* Get the existing signal mask */
+ sigset_t set;
+ char const *debug_act;
+
+ sigemptyset (&set);
+
+ if (sigprocmask (0, NULL, &set))
+ die (EXIT_CANCELED, errno, _("failed to get signal process mask"));
+
+ for (int i = 1; i <= SIGNUM_BOUND; i++)
+ {
+ if (sigismember (&block_signals, i))
+ {
+ sigaddset (&set, i);
+ debug_act = "BLOCK";
+ }
+ else if (sigismember (&unblock_signals, i))
+ {
+ sigdelset (&set, i);
+ debug_act = "UNBLOCK";
+ }
+ else
+ {
+ debug_act = NULL;
+ }
+
+ if (dev_debug && debug_act)
+ {
+ char signame[SIG2STR_MAX];
+ sig2str (i, signame);
+ devmsg ("signal %s (%d) mask set to %s\n",
+ signame, i, debug_act);
+ }
+ }
+
+ if (sigprocmask (SIG_SETMASK, &set, NULL))
+ die (EXIT_CANCELED, errno, _("failed to set signal process mask"));
+}
+
+static void
+list_signal_handling (void)
+{
+ sigset_t set;
+ char signame[SIG2STR_MAX];
+
+ sigemptyset (&set);
+ if (sigprocmask (0, NULL, &set))
+ die (EXIT_CANCELED, errno, _("failed to get signal process mask"));
+
+ for (int i = 1; i <= SIGNUM_BOUND; i++)
+ {
+ struct sigaction act;
+ if (sigaction (i, NULL, &act))
+ continue;
+
+ char const *ignored = act.sa_handler == SIG_IGN ? "IGNORE" : "";
+ char const *blocked = sigismember (&set, i) ? "BLOCK" : "";
+ char const *connect = *ignored && *blocked ? "," : "";
+
+ if (! *ignored && ! *blocked)
+ continue;
+
+ sig2str (i, signame);
+ fprintf (stderr, "%-10s (%2d): %s%s%s\n", signame, i,
+ blocked, connect, ignored);
+ }
+}
+
+static void
+initialize_signals (void)
+{
+ signals = xmalloc ((sizeof *signals) * (SIGNUM_BOUND + 1));
+
+ for (int i = 0 ; i <= SIGNUM_BOUND; i++)
+ signals[i] = UNCHANGED;
+
+ return;
+}
+
+int
+main (int argc, char **argv)
+{
+ int optc;
+ bool ignore_environment = false;
+ bool opt_nul_terminate_output = false;
+ char const *newdir = NULL;
+
+ initialize_main (&argc, &argv);
+ set_program_name (argv[0]);
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+
+ initialize_exit_failure (EXIT_CANCELED);
+ atexit (close_stdout);
+
+ initialize_signals ();
+
+ while ((optc = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1)
+ {
+ switch (optc)
+ {
+ case 'i':
+ ignore_environment = true;
+ break;
+ case 'u':
+ append_unset_var (optarg);
+ break;
+ case 'v':
+ dev_debug = true;
+ break;
+ case '0':
+ opt_nul_terminate_output = true;
+ break;
+ case DEFAULT_SIGNAL_OPTION:
+ parse_signal_action_params (optarg, true);
+ parse_block_signal_params (optarg, false);
+ break;
+ case IGNORE_SIGNAL_OPTION:
+ parse_signal_action_params (optarg, false);
+ break;
+ case BLOCK_SIGNAL_OPTION:
+ parse_block_signal_params (optarg, true);
+ break;
+ case LIST_SIGNAL_HANDLING_OPTION:
+ report_signal_handling = true;
+ break;
+ case 'C':
+ newdir = optarg;
+ break;
+ case 'S':
+ parse_split_string (optarg, &optind, &argc, &argv);
+ break;
+ case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
+ /* These are undocumented options. Attempt to detect
+ incorrect shebang usage with extraneous space, e.g.:
+ #!/usr/bin/env -i command
+ In which case argv[1] == "-i command". */
+ error (0, 0, _("invalid option -- '%c'"), optc);
+ error (0, 0, _("use -[v]S to pass options in shebang lines"));
+ usage (EXIT_CANCELED);
+
+ case_GETOPT_HELP_CHAR;
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+ default:
+ usage (EXIT_CANCELED);
+ }
+ }
+
+ if (optind < argc && STREQ (argv[optind], "-"))
+ {
+ ignore_environment = true;
+ ++optind;
+ }
+
+ if (ignore_environment)
+ {
+ devmsg ("cleaning environ\n");
+ static char *dummy_environ[] = { NULL };
+ environ = dummy_environ;
+ }
+ else
+ unset_envvars ();
+
+ char *eq;
+ while (optind < argc && (eq = strchr (argv[optind], '=')))
+ {
+ devmsg ("setenv: %s\n", argv[optind]);
+
+ if (putenv (argv[optind]))
+ {
+ *eq = '\0';
+ die (EXIT_CANCELED, errno, _("cannot set %s"),
+ quote (argv[optind]));
+ }
+ optind++;
+ }
+
+ bool program_specified = optind < argc;
+
+ if (opt_nul_terminate_output && program_specified)
+ {
+ error (0, 0, _("cannot specify --null (-0) with command"));
+ usage (EXIT_CANCELED);
+ }
+
+ if (newdir && ! program_specified)
+ {
+ error (0, 0, _("must specify command with --chdir (-C)"));
+ usage (EXIT_CANCELED);
+ }
+
+ if (! program_specified)
+ {
+ /* Print the environment and exit. */
+ char *const *e = environ;
+ while (*e)
+ printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
+ return EXIT_SUCCESS;
+ }
+
+ reset_signal_handlers ();
+ if (sig_mask_changed)
+ set_signal_proc_mask ();
+
+ if (report_signal_handling)
+ list_signal_handling ();
+
+ if (newdir)
+ {
+ devmsg ("chdir: %s\n", quoteaf (newdir));
+
+ if (chdir (newdir) != 0)
+ die (EXIT_CANCELED, errno, _("cannot change directory to %s"),
+ quoteaf (newdir));
+ }
+
+ if (dev_debug)
+ {
+ devmsg ("executing: %s\n", argv[optind]);
+ for (int i=optind; i<argc; ++i)
+ devmsg (" arg[%d]= %s\n", i-optind, quote (argv[i]));
+ }
+
+ execvp (argv[optind], &argv[optind]);
+
+ int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
+ error (0, errno, "%s", quote (argv[optind]));
+
+ if (exit_status == EXIT_ENOENT && strpbrk (argv[optind], C_ISSPACE_CHARS))
+ error (0, 0, _("use -[v]S to pass options in shebang lines"));
+
+ main_exit (exit_status);
+}