(root)/
coreutils-9.4/
src/
env.c
       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
     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
     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
     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 *
     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 *
     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
     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
     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
     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 **
     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 **
     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
     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
     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
     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
     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
     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
     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
     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
     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  }