1 /* env - run a program in a modified environment
2    Copyright (C) 1986-2023 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16 
17 /* Richard Mlynarik and David MacKenzie */
18 
19 #include <config.h>
20 #include <stdio.h>
21 #include <sys/types.h>
22 #include <getopt.h>
23 #include <c-ctype.h>
24 #include <signal.h>
25 
26 #include "system.h"
27 #include "operand2sig.h"
28 #include "quote.h"
29 #include "sig2str.h"
30 
31 /* The official name of this program (e.g., no 'g' prefix).  */
32 #define PROGRAM_NAME "env"
33 
34 #define AUTHORS \
35   proper_name ("Richard Mlynarik"), \
36   proper_name ("David MacKenzie"), \
37   proper_name ("Assaf Gordon")
38 
39 /* Array of envvars to unset.  */
40 static char const **usvars;
41 static size_t usvars_alloc;
42 static idx_t usvars_used;
43 
44 /* Annotate the output with extra info to aid the user.  */
45 static bool dev_debug;
46 
47 /* Buffer and length of extracted envvars in -S strings.  */
48 static char *varname;
49 static idx_t vnlen;
50 
51 /* Possible actions on each signal.  */
52 enum SIGNAL_MODE {
53   UNCHANGED = 0,
54   DEFAULT,       /* Set to default handler (SIG_DFL).  */
55   DEFAULT_NOERR, /* Ditto, but ignore sigaction(2) errors.  */
56   IGNORE,        /* Set to ignore (SIG_IGN).  */
57   IGNORE_NOERR   /* Ditto, but ignore sigaction(2) errors.  */
58 };
59 static enum SIGNAL_MODE *signals;
60 
61 /* Set of signals to block.  */
62 static sigset_t block_signals;
63 
64 /* Set of signals to unblock.  */
65 static sigset_t unblock_signals;
66 
67 /* Whether signal mask adjustment requested.  */
68 static bool sig_mask_changed;
69 
70 /* Whether to list non default handling.  */
71 static bool report_signal_handling;
72 
73 /* The isspace characters in the C locale.  */
74 #define C_ISSPACE_CHARS " \t\n\v\f\r"
75 
76 static char const shortopts[] = "+C:iS:u:v0" C_ISSPACE_CHARS;
77 
78 /* For long options that have no equivalent short option, use a
79    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
80 enum
81 {
82   DEFAULT_SIGNAL_OPTION = CHAR_MAX + 1,
83   IGNORE_SIGNAL_OPTION,
84   BLOCK_SIGNAL_OPTION,
85   LIST_SIGNAL_HANDLING_OPTION,
86 };
87 
88 static struct option const longopts[] =
89 {
90   {"ignore-environment", no_argument, nullptr, 'i'},
91   {"null", no_argument, nullptr, '0'},
92   {"unset", required_argument, nullptr, 'u'},
93   {"chdir", required_argument, nullptr, 'C'},
94   {"default-signal", optional_argument, nullptr, DEFAULT_SIGNAL_OPTION},
95   {"ignore-signal",  optional_argument, nullptr, IGNORE_SIGNAL_OPTION},
96   {"block-signal",   optional_argument, nullptr, BLOCK_SIGNAL_OPTION},
97   {"list-signal-handling", no_argument, nullptr,  LIST_SIGNAL_HANDLING_OPTION},
98   {"debug", no_argument, nullptr, 'v'},
99   {"split-string", required_argument, nullptr, 'S'},
100   {GETOPT_HELP_OPTION_DECL},
101   {GETOPT_VERSION_OPTION_DECL},
102   {nullptr, 0, nullptr, 0}
103 };
104 
105 void
usage(int status)106 usage (int status)
107 {
108   if (status != EXIT_SUCCESS)
109     emit_try_help ();
110   else
111     {
112       printf (_("\
113 Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
114               program_name);
115       fputs (_("\
116 Set each NAME to VALUE in the environment and run COMMAND.\n\
117 "), stdout);
118 
119       emit_mandatory_arg_note ();
120 
121       fputs (_("\
122   -i, --ignore-environment  start with an empty environment\n\
123   -0, --null           end each output line with NUL, not newline\n\
124   -u, --unset=NAME     remove variable from the environment\n\
125 "), stdout);
126       fputs (_("\
127   -C, --chdir=DIR      change working directory to DIR\n\
128 "), stdout);
129       fputs (_("\
130   -S, --split-string=S  process and split S into separate arguments;\n\
131                         used to pass multiple arguments on shebang lines\n\
132 "), stdout);
133       fputs (_("\
134       --block-signal[=SIG]    block delivery of SIG signal(s) to COMMAND\n\
135 "), stdout);
136       fputs (_("\
137       --default-signal[=SIG]  reset handling of SIG signal(s) to the default\n\
138 "), stdout);
139       fputs (_("\
140       --ignore-signal[=SIG]   set handling of SIG signal(s) to do nothing\n\
141 "), stdout);
142       fputs (_("\
143       --list-signal-handling  list non default signal handling to stderr\n\
144 "), stdout);
145       fputs (_("\
146   -v, --debug          print verbose information for each processing step\n\
147 "), stdout);
148       fputs (HELP_OPTION_DESCRIPTION, stdout);
149       fputs (VERSION_OPTION_DESCRIPTION, stdout);
150       fputs (_("\
151 \n\
152 A mere - implies -i.  If no COMMAND, print the resulting environment.\n\
153 "), stdout);
154       fputs (_("\
155 \n\
156 SIG may be a signal name like 'PIPE', or a signal number like '13'.\n\
157 Without SIG, all known signals are included.  Multiple signals can be\n\
158 comma-separated.  An empty SIG argument is a no-op.\n\
159 "), stdout);
160       emit_exec_status (PROGRAM_NAME);
161       emit_ancillary_info (PROGRAM_NAME);
162     }
163   exit (status);
164 }
165 
166 static void
append_unset_var(char const * var)167 append_unset_var (char const *var)
168 {
169   if (usvars_used == usvars_alloc)
170     usvars = x2nrealloc (usvars, &usvars_alloc, sizeof *usvars);
171   usvars[usvars_used++] = var;
172 }
173 
174 static void
unset_envvars(void)175 unset_envvars (void)
176 {
177   for (idx_t i = 0; i < usvars_used; ++i)
178     {
179       devmsg ("unset:    %s\n", usvars[i]);
180 
181       if (unsetenv (usvars[i]))
182         error (EXIT_CANCELED, errno, _("cannot unset %s"),
183                quote (usvars[i]));
184     }
185 }
186 
187 /* Return a pointer to the end of a valid ${VARNAME} string, or nullptr.
188    'str' should point to the '$' character.
189    First letter in VARNAME must be alpha or underscore,
190    rest of letters are alnum or underscore.
191    Any other character is an error.  */
192 ATTRIBUTE_PURE
193 static char const *
scan_varname(char const * str)194 scan_varname (char const *str)
195 {
196   if (str[1] == '{' && (c_isalpha (str[2]) || str[2] == '_'))
197     {
198       char const *end = str + 3;
199       while (c_isalnum (*end) || *end == '_')
200         ++end;
201       if (*end == '}')
202         return end;
203     }
204 
205   return nullptr;
206 }
207 
208 /* Return a pointer to a static buffer containing the VARNAME as
209    extracted from a '${VARNAME}' string.
210    The returned string will be NUL terminated.
211    The returned pointer should not be freed.
212    Return nullptr if not a valid ${VARNAME} syntax.  */
213 static char *
extract_varname(char const * str)214 extract_varname (char const *str)
215 {
216   idx_t i;
217   char const *p;
218 
219   p = scan_varname (str);
220   if (!p)
221     return nullptr;
222 
223   /* -2 and +2 (below) account for the '${' prefix.  */
224   i = p - str - 2;
225 
226   if (i >= vnlen)
227     {
228       vnlen = i + 1;
229       varname = xrealloc (varname, vnlen);
230     }
231 
232   memcpy (varname, str + 2, i);
233   varname[i] = 0;
234 
235   return varname;
236 }
237 
238 /* Temporary buffer used by --split-string processing.  */
239 struct splitbuf
240 {
241   /* Buffer address, arg count, and half the number of elements in the buffer.
242      ARGC and ARGV are as in 'main', and ARGC + 1 <= HALF_ALLOC so
243      that the upper half of ARGV can be used for string contents.
244      This may waste up to half the space but keeps the code simple,
245      which is better for this rarely-used but security-sensitive code.
246 
247      ARGV[0] is not initialized; that is the caller's responsibility
248      after finalization.
249 
250      During assembly, ARGV[I] (where 0 < I < ARGC) contains the offset
251      of the Ith string (relative to ARGV + HALF_ALLOC), so that
252      reallocating ARGV does not change the validity of its contents.
253      The integer offset is cast to char * during assembly, and is
254      converted to a true char * pointer on finalization.
255 
256      During assembly, ARGV[ARGC] contains the offset of the first
257      unused string byte (relative to ARGV + HALF_ALLOC).  */
258   char **argv;
259   int argc;
260   idx_t half_alloc;
261 
262   /* The number of extra argv slots to keep room for.  */
263   int extra_argc;
264 
265   /* Whether processing should act as if the most recent character
266      seen was a separator.  */
267   bool sep;
268 };
269 
270 /* Expand SS so that it has at least one more argv slot and at least
271    one more string byte.  */
272 static void
splitbuf_grow(struct splitbuf * ss)273 splitbuf_grow (struct splitbuf *ss)
274 {
275   idx_t old_half_alloc = ss->half_alloc;
276   idx_t string_bytes = (intptr_t) ss->argv[ss->argc];
277   ss->argv = xpalloc (ss->argv, &ss->half_alloc, 1,
278                       MIN (INT_MAX, IDX_MAX), 2 * sizeof *ss->argv);
279   memmove (ss->argv + ss->half_alloc, ss->argv + old_half_alloc, string_bytes);
280 }
281 
282 /* In SS, append C to the last string.  */
283 static void
splitbuf_append_byte(struct splitbuf * ss,char c)284 splitbuf_append_byte (struct splitbuf *ss, char c)
285 {
286   idx_t string_bytes = (intptr_t) ss->argv[ss->argc];
287   if (ss->half_alloc * sizeof *ss->argv <= string_bytes)
288     splitbuf_grow (ss);
289   ((char *) (ss->argv + ss->half_alloc))[string_bytes] = c;
290   ss->argv[ss->argc] = (char *) (intptr_t) (string_bytes + 1);
291 }
292 
293 /* If SS's most recent character was a separator, finish off its
294    previous argument and start a new one.  */
295 static void
check_start_new_arg(struct splitbuf * ss)296 check_start_new_arg (struct splitbuf *ss)
297 {
298   if (ss->sep)
299     {
300       splitbuf_append_byte (ss, '\0');
301       int argc = ss->argc;
302       if (ss->half_alloc <= argc + ss->extra_argc + 1)
303         splitbuf_grow (ss);
304       ss->argv[argc + 1] = ss->argv[argc];
305       ss->argc = argc + 1;
306       ss->sep = false;
307     }
308 }
309 
310 /* All additions to SS have been made.  Convert its offsets to pointers,
311    and return the resulting argument vector.  */
312 static char **
splitbuf_finishup(struct splitbuf * ss)313 splitbuf_finishup (struct splitbuf *ss)
314 {
315   int argc = ss->argc;
316   char **argv = ss->argv;
317   char *stringbase = (char *) (ss->argv + ss->half_alloc);
318   for (int i = 1; i < argc; i++)
319     argv[i] = stringbase + (intptr_t) argv[i];
320   return argv;
321 }
322 
323 /* Return a newly-allocated argv-like array,
324    by parsing and splitting the input 'str'.
325 
326    'extra_argc' is the number of additional elements to allocate
327    in the array (on top of the number of args required to split 'str').
328 
329    Store into *argc the number of arguments found (plus 1 for
330    the program name).
331 
332    Example:
333      int argc;
334      char **argv = build_argv ("A=B uname -k', 3, &argc);
335    Results in:
336      argc = 4
337      argv[0] = [not initialized]
338      argv[1] = "A=B"
339      argv[2] = "uname"
340      argv[3] = "-k"
341      argv[4,5,6,7] = [allocated due to extra_argc + 1, but not initialized]
342 
343    To free allocated memory:
344      free (argv);
345    However, 'env' does not free since it's about to exec or exit anyway
346    and the complexity of keeping track of the storage that may have been
347    allocated via multiple calls to build_argv is not worth the hassle.  */
348 static char **
build_argv(char const * str,int extra_argc,int * argc)349 build_argv (char const *str, int extra_argc, int *argc)
350 {
351   bool dq = false, sq = false;
352   struct splitbuf ss;
353   ss.argv = xnmalloc (extra_argc + 2, 2 * sizeof *ss.argv);
354   ss.argc = 1;
355   ss.half_alloc = extra_argc + 2;
356   ss.extra_argc = extra_argc;
357   ss.sep = true;
358   ss.argv[ss.argc] = 0;
359 
360   /* In the following loop,
361      'break' causes the character 'newc' to be added to *dest,
362      'continue' skips the character.  */
363   while (*str)
364     {
365       char newc = *str; /* Default: add the next character.  */
366 
367       switch (*str)
368         {
369         case '\'':
370           if (dq)
371             break;
372           sq = !sq;
373           check_start_new_arg (&ss);
374           ++str;
375           continue;
376 
377         case '"':
378           if (sq)
379             break;
380           dq = !dq;
381           check_start_new_arg (&ss);
382           ++str;
383           continue;
384 
385         case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
386           /* Start a new argument if outside quotes.  */
387           if (sq || dq)
388             break;
389           ss.sep = true;
390           str += strspn (str, C_ISSPACE_CHARS);
391           continue;
392 
393         case '#':
394           if (!ss.sep)
395             break;
396           goto eos; /* '#' as first char terminates the string.  */
397 
398         case '\\':
399           /* Backslash inside single-quotes is not special, except \\
400              and \'.  */
401           if (sq && str[1] != '\\' && str[1] != '\'')
402             break;
403 
404           /* Skip the backslash and examine the next character.  */
405           newc = *++str;
406           switch (newc)
407             {
408             case '"': case '#': case '$': case '\'': case '\\':
409               /* Pass escaped character as-is.  */
410               break;
411 
412             case '_':
413               if (!dq)
414                 {
415                   ++str;  /* '\_' outside double-quotes is arg separator.  */
416                   ss.sep = true;
417                   continue;
418                 }
419               newc = ' ';  /* '\_' inside double-quotes is space.  */
420               break;
421 
422             case 'c':
423               if (dq)
424                 error (EXIT_CANCELED, 0,
425                        _("'\\c' must not appear in double-quoted -S string"));
426               goto eos; /* '\c' terminates the string.  */
427 
428             case 'f': newc = '\f'; break;
429             case 'n': newc = '\n'; break;
430             case 'r': newc = '\r'; break;
431             case 't': newc = '\t'; break;
432             case 'v': newc = '\v'; break;
433 
434             case '\0':
435               error (EXIT_CANCELED, 0,
436                      _("invalid backslash at end of string in -S"));
437 
438             default:
439               error (EXIT_CANCELED, 0,
440                      _("invalid sequence '\\%c' in -S"), newc);
441             }
442           break;
443 
444         case '$':
445           /* ${VARNAME} are not expanded inside single-quotes.  */
446           if (sq)
447             break;
448 
449           /* Store the ${VARNAME} value.  */
450           {
451             char *n = extract_varname (str);
452             if (!n)
453               error (EXIT_CANCELED, 0,
454                      _("only ${VARNAME} expansion is supported, error at: %s"),
455                      str);
456 
457             char *v = getenv (n);
458             if (v)
459               {
460                 check_start_new_arg (&ss);
461                 devmsg ("expanding ${%s} into %s\n", n, quote (v));
462                 for (; *v; v++)
463                   splitbuf_append_byte (&ss, *v);
464               }
465             else
466               devmsg ("replacing ${%s} with null string\n", n);
467 
468             str = strchr (str, '}') + 1;
469             continue;
470           }
471         }
472 
473       check_start_new_arg (&ss);
474       splitbuf_append_byte (&ss, newc);
475       ++str;
476     }
477 
478   if (dq || sq)
479     error (EXIT_CANCELED, 0, _("no terminating quote in -S string"));
480 
481  eos:
482   splitbuf_append_byte (&ss, '\0');
483   *argc = ss.argc;
484   return splitbuf_finishup (&ss);
485 }
486 
487 /* Process an "-S" string and create the corresponding argv array.
488    Update the given argc/argv parameters with the new argv.
489 
490    Example: if executed as:
491       $ env -S"-i -C/tmp A=B" foo bar
492    The input argv is:
493       argv[0] = "env"
494       argv[1] = "-S-i -C/tmp A=B"
495       argv[2] = "foo"
496       argv[3] = "bar"
497       argv[4] = nullptr
498    This function will modify argv to be:
499       argv[0] = "env"
500       argv[1] = "-i"
501       argv[2] = "-C/tmp"
502       argv[3] = "A=B"
503       argv[4] = "foo"
504       argv[5] = "bar"
505       argv[6] = nullptr
506    argc will be updated from 4 to 6.
507    optind will be reset to 0 to force getopt_long to rescan all arguments.  */
508 static void
parse_split_string(char const * str,int * orig_optind,int * orig_argc,char *** orig_argv)509 parse_split_string (char const *str, int *orig_optind,
510                     int *orig_argc, char ***orig_argv)
511 {
512   int extra_argc = *orig_argc - *orig_optind, newargc;
513   char **newargv = build_argv (str, extra_argc, &newargc);
514 
515   /* Restore argv[0] - the 'env' executable name.  */
516   *newargv = (*orig_argv)[0];
517 
518   /* Print parsed arguments.  */
519   if (dev_debug && 1 < newargc)
520     {
521       devmsg ("split -S:  %s\n", quote (str));
522       devmsg (" into:    %s\n", quote (newargv[1]));
523       for (int i = 2; i < newargc; i++)
524         devmsg ("     &    %s\n", quote (newargv[i]));
525     }
526 
527   /* Add remaining arguments and terminating null from the original
528      command line.  */
529   memcpy (newargv + newargc, *orig_argv + *orig_optind,
530           (extra_argc + 1) * sizeof *newargv);
531 
532   /* Set new values for original getopt variables.  */
533   *orig_argc = newargc + extra_argc;
534   *orig_argv = newargv;
535   *orig_optind = 0; /* Tell getopt to restart from first argument.  */
536 }
537 
538 static void
parse_signal_action_params(char const * optarg,bool set_default)539 parse_signal_action_params (char const *optarg, bool set_default)
540 {
541   char signame[SIG2STR_MAX];
542   char *opt_sig;
543   char *optarg_writable;
544 
545   if (! optarg)
546     {
547       /* Without an argument, reset all signals.
548          Some signals cannot be set to ignore or default (e.g., SIGKILL,
549          SIGSTOP on most OSes, and SIGCONT on AIX.) - so ignore errors.  */
550       for (int i = 1 ; i <= SIGNUM_BOUND; i++)
551         if (sig2str (i, signame) == 0)
552           signals[i] = set_default ? DEFAULT_NOERR : IGNORE_NOERR;
553       return;
554     }
555 
556   optarg_writable = xstrdup (optarg);
557 
558   opt_sig = strtok (optarg_writable, ",");
559   while (opt_sig)
560     {
561       int signum = operand2sig (opt_sig, signame);
562       /* operand2sig accepts signal 0 (EXIT) - but we reject it.  */
563       if (signum == 0)
564         error (0, 0, _("%s: invalid signal"), quote (opt_sig));
565       if (signum <= 0)
566         usage (exit_failure);
567 
568       signals[signum] = set_default ? DEFAULT : IGNORE;
569 
570       opt_sig = strtok (nullptr, ",");
571     }
572 
573   free (optarg_writable);
574 }
575 
576 static void
reset_signal_handlers(void)577 reset_signal_handlers (void)
578 {
579   for (int i = 1; i <= SIGNUM_BOUND; i++)
580     {
581       struct sigaction act;
582 
583       if (signals[i] == UNCHANGED)
584         continue;
585 
586       bool ignore_errors = (signals[i] == DEFAULT_NOERR
587                             || signals[i] == IGNORE_NOERR);
588 
589       bool set_to_default = (signals[i] == DEFAULT
590                              || signals[i] == DEFAULT_NOERR);
591 
592       int sig_err = sigaction (i, nullptr, &act);
593 
594       if (sig_err && !ignore_errors)
595         error (EXIT_CANCELED, errno,
596                _("failed to get signal action for signal %d"), i);
597 
598       if (! sig_err)
599         {
600           act.sa_handler = set_to_default ? SIG_DFL : SIG_IGN;
601           sig_err = sigaction (i, &act, nullptr);
602           if (sig_err && !ignore_errors)
603             error (EXIT_CANCELED, errno,
604                    _("failed to set signal action for signal %d"), i);
605         }
606 
607       if (dev_debug)
608         {
609           char signame[SIG2STR_MAX];
610           sig2str (i, signame);
611           devmsg ("Reset signal %s (%d) to %s%s\n",
612                   signame, i,
613                   set_to_default ? "DEFAULT" : "IGNORE",
614                   sig_err ? " (failure ignored)" : "");
615         }
616     }
617 }
618 
619 
620 static void
parse_block_signal_params(char const * optarg,bool block)621 parse_block_signal_params (char const *optarg, bool block)
622 {
623   char signame[SIG2STR_MAX];
624   char *opt_sig;
625   char *optarg_writable;
626 
627   if (! optarg)
628     {
629       /* Without an argument, reset all signals.  */
630       sigfillset (block ? &block_signals : &unblock_signals);
631       sigemptyset (block ? &unblock_signals : &block_signals);
632     }
633   else if (! sig_mask_changed)
634     {
635       /* Initialize the sets.  */
636       sigemptyset (&block_signals);
637       sigemptyset (&unblock_signals);
638     }
639 
640   sig_mask_changed = true;
641 
642   if (! optarg)
643     return;
644 
645   optarg_writable = xstrdup (optarg);
646 
647   opt_sig = strtok (optarg_writable, ",");
648   while (opt_sig)
649     {
650       int signum = operand2sig (opt_sig, signame);
651       /* operand2sig accepts signal 0 (EXIT) - but we reject it.  */
652       if (signum == 0)
653         error (0, 0, _("%s: invalid signal"), quote (opt_sig));
654       if (signum <= 0)
655         usage (exit_failure);
656 
657       sigaddset (block ? &block_signals : &unblock_signals, signum);
658       sigdelset (block ? &unblock_signals : &block_signals, signum);
659 
660       opt_sig = strtok (nullptr, ",");
661     }
662 
663   free (optarg_writable);
664 }
665 
666 static void
set_signal_proc_mask(void)667 set_signal_proc_mask (void)
668 {
669   /* Get the existing signal mask */
670   sigset_t set;
671   char const *debug_act;
672 
673   sigemptyset (&set);
674 
675   if (sigprocmask (0, nullptr, &set))
676     error (EXIT_CANCELED, errno, _("failed to get signal process mask"));
677 
678   for (int i = 1; i <= SIGNUM_BOUND; i++)
679     {
680       if (sigismember (&block_signals, i))
681         {
682           sigaddset (&set, i);
683           debug_act = "BLOCK";
684         }
685       else if (sigismember (&unblock_signals, i))
686         {
687           sigdelset (&set, i);
688           debug_act = "UNBLOCK";
689         }
690       else
691         {
692           debug_act = nullptr;
693         }
694 
695       if (dev_debug && debug_act)
696         {
697           char signame[SIG2STR_MAX];
698           sig2str (i, signame);
699           devmsg ("signal %s (%d) mask set to %s\n",
700                   signame, i, debug_act);
701         }
702     }
703 
704   if (sigprocmask (SIG_SETMASK, &set, nullptr))
705     error (EXIT_CANCELED, errno, _("failed to set signal process mask"));
706 }
707 
708 static void
list_signal_handling(void)709 list_signal_handling (void)
710 {
711   sigset_t set;
712   char signame[SIG2STR_MAX];
713 
714   sigemptyset (&set);
715   if (sigprocmask (0, nullptr, &set))
716     error (EXIT_CANCELED, errno, _("failed to get signal process mask"));
717 
718   for (int i = 1; i <= SIGNUM_BOUND; i++)
719     {
720       struct sigaction act;
721       if (sigaction (i, nullptr, &act))
722         continue;
723 
724       char const *ignored = act.sa_handler == SIG_IGN ? "IGNORE" : "";
725       char const *blocked = sigismember (&set, i) ? "BLOCK" : "";
726       char const *connect = *ignored && *blocked ? "," : "";
727 
728       if (! *ignored && ! *blocked)
729         continue;
730 
731       sig2str (i, signame);
732       fprintf (stderr, "%-10s (%2d): %s%s%s\n", signame, i,
733                blocked, connect, ignored);
734     }
735 }
736 
737 static void
initialize_signals(void)738 initialize_signals (void)
739 {
740   signals = xmalloc ((sizeof *signals) * (SIGNUM_BOUND + 1));
741 
742   for (int i = 0 ; i <= SIGNUM_BOUND; i++)
743     signals[i] = UNCHANGED;
744 
745   return;
746 }
747 
748 int
main(int argc,char ** argv)749 main (int argc, char **argv)
750 {
751   int optc;
752   bool ignore_environment = false;
753   bool opt_nul_terminate_output = false;
754   char const *newdir = nullptr;
755 
756   initialize_main (&argc, &argv);
757   set_program_name (argv[0]);
758   setlocale (LC_ALL, "");
759   bindtextdomain (PACKAGE, LOCALEDIR);
760   textdomain (PACKAGE);
761 
762   initialize_exit_failure (EXIT_CANCELED);
763   atexit (close_stdout);
764 
765   initialize_signals ();
766 
767   while ((optc = getopt_long (argc, argv, shortopts, longopts, nullptr)) != -1)
768     {
769       switch (optc)
770         {
771         case 'i':
772           ignore_environment = true;
773           break;
774         case 'u':
775           append_unset_var (optarg);
776           break;
777         case 'v':
778           dev_debug = true;
779           break;
780         case '0':
781           opt_nul_terminate_output = true;
782           break;
783         case DEFAULT_SIGNAL_OPTION:
784           parse_signal_action_params (optarg, true);
785           parse_block_signal_params (optarg, false);
786           break;
787         case IGNORE_SIGNAL_OPTION:
788           parse_signal_action_params (optarg, false);
789           break;
790         case BLOCK_SIGNAL_OPTION:
791           parse_block_signal_params (optarg, true);
792           break;
793         case LIST_SIGNAL_HANDLING_OPTION:
794           report_signal_handling = true;
795           break;
796         case 'C':
797           newdir = optarg;
798           break;
799         case 'S':
800           parse_split_string (optarg, &optind, &argc, &argv);
801           break;
802         case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
803           /* These are undocumented options.  Attempt to detect
804              incorrect shebang usage with extraneous space, e.g.:
805                 #!/usr/bin/env -i command
806              In which case argv[1] == "-i command".  */
807           error (0, 0, _("invalid option -- '%c'"), optc);
808           error (0, 0, _("use -[v]S to pass options in shebang lines"));
809           usage (EXIT_CANCELED);
810 
811         case_GETOPT_HELP_CHAR;
812         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
813         default:
814           usage (EXIT_CANCELED);
815         }
816     }
817 
818   if (optind < argc && STREQ (argv[optind], "-"))
819     {
820       ignore_environment = true;
821       ++optind;
822     }
823 
824   if (ignore_environment)
825     {
826       devmsg ("cleaning environ\n");
827       static char *dummy_environ[] = { nullptr };
828       environ = dummy_environ;
829     }
830   else
831     unset_envvars ();
832 
833   char *eq;
834   while (optind < argc && (eq = strchr (argv[optind], '=')))
835     {
836       devmsg ("setenv:   %s\n", argv[optind]);
837 
838       if (putenv (argv[optind]))
839         {
840           *eq = '\0';
841           error (EXIT_CANCELED, errno, _("cannot set %s"),
842                  quote (argv[optind]));
843         }
844       optind++;
845     }
846 
847   bool program_specified = optind < argc;
848 
849   if (opt_nul_terminate_output && program_specified)
850     {
851       error (0, 0, _("cannot specify --null (-0) with command"));
852       usage (EXIT_CANCELED);
853     }
854 
855   if (newdir && ! program_specified)
856     {
857       error (0, 0, _("must specify command with --chdir (-C)"));
858       usage (EXIT_CANCELED);
859     }
860 
861   if (! program_specified)
862     {
863       /* Print the environment and exit.  */
864       char *const *e = environ;
865       while (*e)
866         printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
867       return EXIT_SUCCESS;
868     }
869 
870   reset_signal_handlers ();
871   if (sig_mask_changed)
872     set_signal_proc_mask ();
873 
874   if (report_signal_handling)
875     list_signal_handling ();
876 
877   if (newdir)
878     {
879       devmsg ("chdir:    %s\n", quoteaf (newdir));
880 
881       if (chdir (newdir) != 0)
882         error (EXIT_CANCELED, errno, _("cannot change directory to %s"),
883                quoteaf (newdir));
884     }
885 
886   if (dev_debug)
887     {
888       devmsg ("executing: %s\n", argv[optind]);
889       for (int i=optind; i<argc; ++i)
890         devmsg ("   arg[%d]= %s\n", i-optind, quote (argv[i]));
891     }
892 
893   execvp (argv[optind], &argv[optind]);
894 
895   int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
896   error (0, errno, "%s", quote (argv[optind]));
897 
898   if (exit_status == EXIT_ENOENT && strpbrk (argv[optind], C_ISSPACE_CHARS))
899     error (0, 0, _("use -[v]S to pass options in shebang lines"));
900 
901   main_exit (exit_status);
902 }
903