1 /* env - run a program in a modified environment
2 Copyright (C) 1986-2023 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* Richard Mlynarik and David MacKenzie */
21 #include <sys/types.h>
28 #include "operand2sig.h"
32 /* The official name of this program (e.g., no 'g' prefix). */
33 #define PROGRAM_NAME "env"
36 proper_name ("Richard Mlynarik"), \
37 proper_name ("David MacKenzie"), \
38 proper_name ("Assaf Gordon")
40 /* Array of envvars to unset. */
41 static char const **usvars
;
42 static size_t usvars_alloc
;
43 static idx_t usvars_used
;
45 /* Annotate the output with extra info to aid the user. */
46 static bool dev_debug
;
48 /* Buffer and length of extracted envvars in -S strings. */
52 /* Possible actions on each signal. */
55 DEFAULT
, /* Set to default handler (SIG_DFL). */
56 DEFAULT_NOERR
, /* Ditto, but ignore sigaction(2) errors. */
57 IGNORE
, /* Set to ignore (SIG_IGN). */
58 IGNORE_NOERR
/* Ditto, but ignore sigaction(2) errors. */
60 static enum SIGNAL_MODE
*signals
;
62 /* Set of signals to block. */
63 static sigset_t block_signals
;
65 /* Set of signals to unblock. */
66 static sigset_t unblock_signals
;
68 /* Whether signal mask adjustment requested. */
69 static bool sig_mask_changed
;
71 /* Whether to list non default handling. */
72 static bool report_signal_handling
;
74 /* The isspace characters in the C locale. */
75 #define C_ISSPACE_CHARS " \t\n\v\f\r"
77 static char const shortopts
[] = "+C:iS:u:v0" C_ISSPACE_CHARS
;
79 /* For long options that have no equivalent short option, use a
80 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
83 DEFAULT_SIGNAL_OPTION
= CHAR_MAX
+ 1,
86 LIST_SIGNAL_HANDLING_OPTION
,
89 static struct option
const longopts
[] =
91 {"ignore-environment", no_argument
, nullptr, 'i'},
92 {"null", no_argument
, nullptr, '0'},
93 {"unset", required_argument
, nullptr, 'u'},
94 {"chdir", required_argument
, nullptr, 'C'},
95 {"default-signal", optional_argument
, nullptr, DEFAULT_SIGNAL_OPTION
},
96 {"ignore-signal", optional_argument
, nullptr, IGNORE_SIGNAL_OPTION
},
97 {"block-signal", optional_argument
, nullptr, BLOCK_SIGNAL_OPTION
},
98 {"list-signal-handling", no_argument
, nullptr, LIST_SIGNAL_HANDLING_OPTION
},
99 {"debug", no_argument
, nullptr, 'v'},
100 {"split-string", required_argument
, nullptr, 'S'},
101 {GETOPT_HELP_OPTION_DECL
},
102 {GETOPT_VERSION_OPTION_DECL
},
103 {nullptr, 0, nullptr, 0}
109 if (status
!= EXIT_SUCCESS
)
114 Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
117 Set each NAME to VALUE in the environment and run COMMAND.\n\
120 emit_mandatory_arg_note ();
123 -i, --ignore-environment start with an empty environment\n\
124 -0, --null end each output line with NUL, not newline\n\
125 -u, --unset=NAME remove variable from the environment\n\
128 -C, --chdir=DIR change working directory to DIR\n\
131 -S, --split-string=S process and split S into separate arguments;\n\
132 used to pass multiple arguments on shebang lines\n\
135 --block-signal[=SIG] block delivery of SIG signal(s) to COMMAND\n\
138 --default-signal[=SIG] reset handling of SIG signal(s) to the default\n\
141 --ignore-signal[=SIG] set handling of SIG signal(s) to do nothing\n\
144 --list-signal-handling list non default signal handling to stderr\n\
147 -v, --debug print verbose information for each processing step\n\
149 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
150 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
153 A mere - implies -i. If no COMMAND, print the resulting environment.\n\
157 SIG may be a signal name like 'PIPE', or a signal number like '13'.\n\
158 Without SIG, all known signals are included. Multiple signals can be\n\
159 comma-separated. An empty SIG argument is a no-op.\n\
161 emit_exec_status (PROGRAM_NAME
);
162 emit_ancillary_info (PROGRAM_NAME
);
168 append_unset_var (char const *var
)
170 if (usvars_used
== usvars_alloc
)
171 usvars
= x2nrealloc (usvars
, &usvars_alloc
, sizeof *usvars
);
172 usvars
[usvars_used
++] = var
;
178 for (idx_t i
= 0; i
< usvars_used
; ++i
)
180 devmsg ("unset: %s\n", usvars
[i
]);
182 if (unsetenv (usvars
[i
]))
183 error (EXIT_CANCELED
, errno
, _("cannot unset %s"),
188 /* Return a pointer to the end of a valid ${VARNAME} string, or nullptr.
189 'str' should point to the '$' character.
190 First letter in VARNAME must be alpha or underscore,
191 rest of letters are alnum or underscore.
192 Any other character is an error. */
195 scan_varname (char const *str
)
197 if (str
[1] == '{' && (c_isalpha (str
[2]) || str
[2] == '_'))
199 char const *end
= str
+ 3;
200 while (c_isalnum (*end
) || *end
== '_')
209 /* Return a pointer to a static buffer containing the VARNAME as
210 extracted from a '${VARNAME}' string.
211 The returned string will be NUL terminated.
212 The returned pointer should not be freed.
213 Return nullptr if not a valid ${VARNAME} syntax. */
215 extract_varname (char const *str
)
220 p
= scan_varname (str
);
224 /* -2 and +2 (below) account for the '${' prefix. */
230 varname
= xrealloc (varname
, vnlen
);
233 memcpy (varname
, str
+ 2, i
);
239 /* Temporary buffer used by --split-string processing. */
242 /* Buffer address, arg count, and half the number of elements in the buffer.
243 ARGC and ARGV are as in 'main', and ARGC + 1 <= HALF_ALLOC so
244 that the upper half of ARGV can be used for string contents.
245 This may waste up to half the space but keeps the code simple,
246 which is better for this rarely-used but security-sensitive code.
248 ARGV[0] is not initialized; that is the caller's responsibility
251 During assembly, ARGV[I] (where 0 < I < ARGC) contains the offset
252 of the Ith string (relative to ARGV + HALF_ALLOC), so that
253 reallocating ARGV does not change the validity of its contents.
254 The integer offset is cast to char * during assembly, and is
255 converted to a true char * pointer on finalization.
257 During assembly, ARGV[ARGC] contains the offset of the first
258 unused string byte (relative to ARGV + HALF_ALLOC). */
263 /* The number of extra argv slots to keep room for. */
266 /* Whether processing should act as if the most recent character
267 seen was a separator. */
271 /* Expand SS so that it has at least one more argv slot and at least
272 one more string byte. */
274 splitbuf_grow (struct splitbuf
*ss
)
276 idx_t old_half_alloc
= ss
->half_alloc
;
277 idx_t string_bytes
= (intptr_t) ss
->argv
[ss
->argc
];
278 ss
->argv
= xpalloc (ss
->argv
, &ss
->half_alloc
, 1,
279 MIN (INT_MAX
, IDX_MAX
), 2 * sizeof *ss
->argv
);
280 memmove (ss
->argv
+ ss
->half_alloc
, ss
->argv
+ old_half_alloc
, string_bytes
);
283 /* In SS, append C to the last string. */
285 splitbuf_append_byte (struct splitbuf
*ss
, char c
)
287 idx_t string_bytes
= (intptr_t) ss
->argv
[ss
->argc
];
288 if (ss
->half_alloc
* sizeof *ss
->argv
<= string_bytes
)
290 ((char *) (ss
->argv
+ ss
->half_alloc
))[string_bytes
] = c
;
291 ss
->argv
[ss
->argc
] = (char *) (intptr_t) (string_bytes
+ 1);
294 /* If SS's most recent character was a separator, finish off its
295 previous argument and start a new one. */
297 check_start_new_arg (struct splitbuf
*ss
)
301 splitbuf_append_byte (ss
, '\0');
303 if (ss
->half_alloc
<= argc
+ ss
->extra_argc
+ 1)
305 ss
->argv
[argc
+ 1] = ss
->argv
[argc
];
311 /* All additions to SS have been made. Convert its offsets to pointers,
312 and return the resulting argument vector. */
314 splitbuf_finishup (struct splitbuf
*ss
)
317 char **argv
= ss
->argv
;
318 char *stringbase
= (char *) (ss
->argv
+ ss
->half_alloc
);
319 for (int i
= 1; i
< argc
; i
++)
320 argv
[i
] = stringbase
+ (intptr_t) argv
[i
];
324 /* Return a newly-allocated argv-like array,
325 by parsing and splitting the input 'str'.
327 'extra_argc' is the number of additional elements to allocate
328 in the array (on top of the number of args required to split 'str').
330 Store into *argc the number of arguments found (plus 1 for
335 char **argv = build_argv ("A=B uname -k', 3, &argc);
338 argv[0] = [not initialized]
342 argv[4,5,6,7] = [allocated due to extra_argc + 1, but not initialized]
344 To free allocated memory:
346 However, 'env' does not free since it's about to exec or exit anyway
347 and the complexity of keeping track of the storage that may have been
348 allocated via multiple calls to build_argv is not worth the hassle. */
350 build_argv (char const *str
, int extra_argc
, int *argc
)
352 bool dq
= false, sq
= false;
354 ss
.argv
= xnmalloc (extra_argc
+ 2, 2 * sizeof *ss
.argv
);
356 ss
.half_alloc
= extra_argc
+ 2;
357 ss
.extra_argc
= extra_argc
;
359 ss
.argv
[ss
.argc
] = 0;
361 /* In the following loop,
362 'break' causes the character 'newc' to be added to *dest,
363 'continue' skips the character. */
366 char newc
= *str
; /* Default: add the next character. */
374 check_start_new_arg (&ss
);
382 check_start_new_arg (&ss
);
386 case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
387 /* Start a new argument if outside quotes. */
391 str
+= strspn (str
, C_ISSPACE_CHARS
);
397 goto eos
; /* '#' as first char terminates the string. */
400 /* Backslash inside single-quotes is not special, except \\
402 if (sq
&& str
[1] != '\\' && str
[1] != '\'')
405 /* Skip the backslash and examine the next character. */
409 case '"': case '#': case '$': case '\'': case '\\':
410 /* Pass escaped character as-is. */
416 ++str
; /* '\_' outside double-quotes is arg separator. */
420 newc
= ' '; /* '\_' inside double-quotes is space. */
425 error (EXIT_CANCELED
, 0,
426 _("'\\c' must not appear in double-quoted -S string"));
427 goto eos
; /* '\c' terminates the string. */
429 case 'f': newc
= '\f'; break;
430 case 'n': newc
= '\n'; break;
431 case 'r': newc
= '\r'; break;
432 case 't': newc
= '\t'; break;
433 case 'v': newc
= '\v'; break;
436 error (EXIT_CANCELED
, 0,
437 _("invalid backslash at end of string in -S"));
440 error (EXIT_CANCELED
, 0,
441 _("invalid sequence '\\%c' in -S"), newc
);
446 /* ${VARNAME} are not expanded inside single-quotes. */
450 /* Store the ${VARNAME} value. */
452 char *n
= extract_varname (str
);
454 error (EXIT_CANCELED
, 0,
455 _("only ${VARNAME} expansion is supported, error at: %s"),
458 char *v
= getenv (n
);
461 check_start_new_arg (&ss
);
462 devmsg ("expanding ${%s} into %s\n", n
, quote (v
));
464 splitbuf_append_byte (&ss
, *v
);
467 devmsg ("replacing ${%s} with null string\n", n
);
469 str
= strchr (str
, '}') + 1;
474 check_start_new_arg (&ss
);
475 splitbuf_append_byte (&ss
, newc
);
480 error (EXIT_CANCELED
, 0, _("no terminating quote in -S string"));
483 splitbuf_append_byte (&ss
, '\0');
485 return splitbuf_finishup (&ss
);
488 /* Process an "-S" string and create the corresponding argv array.
489 Update the given argc/argv parameters with the new argv.
491 Example: if executed as:
492 $ env -S"-i -C/tmp A=B" foo bar
495 argv[1] = "-S-i -C/tmp A=B"
499 This function will modify argv to be:
507 argc will be updated from 4 to 6.
508 optind will be reset to 0 to force getopt_long to rescan all arguments. */
510 parse_split_string (char const *str
, int *orig_optind
,
511 int *orig_argc
, char ***orig_argv
)
513 int extra_argc
= *orig_argc
- *orig_optind
, newargc
;
514 char **newargv
= build_argv (str
, extra_argc
, &newargc
);
516 /* Restore argv[0] - the 'env' executable name. */
517 *newargv
= (*orig_argv
)[0];
519 /* Print parsed arguments. */
520 if (dev_debug
&& 1 < newargc
)
522 devmsg ("split -S: %s\n", quote (str
));
523 devmsg (" into: %s\n", quote (newargv
[1]));
524 for (int i
= 2; i
< newargc
; i
++)
525 devmsg (" & %s\n", quote (newargv
[i
]));
528 /* Add remaining arguments and terminating null from the original
530 memcpy (newargv
+ newargc
, *orig_argv
+ *orig_optind
,
531 (extra_argc
+ 1) * sizeof *newargv
);
533 /* Set new values for original getopt variables. */
534 *orig_argc
= newargc
+ extra_argc
;
535 *orig_argv
= newargv
;
536 *orig_optind
= 0; /* Tell getopt to restart from first argument. */
540 parse_signal_action_params (char const *optarg
, bool set_default
)
542 char signame
[SIG2STR_MAX
];
544 char *optarg_writable
;
548 /* Without an argument, reset all signals.
549 Some signals cannot be set to ignore or default (e.g., SIGKILL,
550 SIGSTOP on most OSes, and SIGCONT on AIX.) - so ignore errors. */
551 for (int i
= 1 ; i
<= SIGNUM_BOUND
; i
++)
552 if (sig2str (i
, signame
) == 0)
553 signals
[i
] = set_default
? DEFAULT_NOERR
: IGNORE_NOERR
;
557 optarg_writable
= xstrdup (optarg
);
559 opt_sig
= strtok (optarg_writable
, ",");
562 int signum
= operand2sig (opt_sig
, signame
);
563 /* operand2sig accepts signal 0 (EXIT) - but we reject it. */
565 error (0, 0, _("%s: invalid signal"), quote (opt_sig
));
567 usage (exit_failure
);
569 signals
[signum
] = set_default
? DEFAULT
: IGNORE
;
571 opt_sig
= strtok (nullptr, ",");
574 free (optarg_writable
);
578 reset_signal_handlers (void)
580 for (int i
= 1; i
<= SIGNUM_BOUND
; i
++)
582 struct sigaction act
;
584 if (signals
[i
] == UNCHANGED
)
587 bool ignore_errors
= (signals
[i
] == DEFAULT_NOERR
588 || signals
[i
] == IGNORE_NOERR
);
590 bool set_to_default
= (signals
[i
] == DEFAULT
591 || signals
[i
] == DEFAULT_NOERR
);
593 int sig_err
= sigaction (i
, nullptr, &act
);
595 if (sig_err
&& !ignore_errors
)
596 error (EXIT_CANCELED
, errno
,
597 _("failed to get signal action for signal %d"), i
);
601 act
.sa_handler
= set_to_default
? SIG_DFL
: SIG_IGN
;
602 sig_err
= sigaction (i
, &act
, nullptr);
603 if (sig_err
&& !ignore_errors
)
604 error (EXIT_CANCELED
, errno
,
605 _("failed to set signal action for signal %d"), i
);
610 char signame
[SIG2STR_MAX
];
611 sig2str (i
, signame
);
612 devmsg ("Reset signal %s (%d) to %s%s\n",
614 set_to_default
? "DEFAULT" : "IGNORE",
615 sig_err
? " (failure ignored)" : "");
622 parse_block_signal_params (char const *optarg
, bool block
)
624 char signame
[SIG2STR_MAX
];
626 char *optarg_writable
;
630 /* Without an argument, reset all signals. */
631 sigfillset (block
? &block_signals
: &unblock_signals
);
632 sigemptyset (block
? &unblock_signals
: &block_signals
);
634 else if (! sig_mask_changed
)
636 /* Initialize the sets. */
637 sigemptyset (&block_signals
);
638 sigemptyset (&unblock_signals
);
641 sig_mask_changed
= true;
646 optarg_writable
= xstrdup (optarg
);
648 opt_sig
= strtok (optarg_writable
, ",");
651 int signum
= operand2sig (opt_sig
, signame
);
652 /* operand2sig accepts signal 0 (EXIT) - but we reject it. */
654 error (0, 0, _("%s: invalid signal"), quote (opt_sig
));
656 usage (exit_failure
);
658 sigaddset (block
? &block_signals
: &unblock_signals
, signum
);
659 sigdelset (block
? &unblock_signals
: &block_signals
, signum
);
661 opt_sig
= strtok (nullptr, ",");
664 free (optarg_writable
);
668 set_signal_proc_mask (void)
670 /* Get the existing signal mask */
672 char const *debug_act
;
676 if (sigprocmask (0, nullptr, &set
))
677 error (EXIT_CANCELED
, errno
, _("failed to get signal process mask"));
679 for (int i
= 1; i
<= SIGNUM_BOUND
; i
++)
681 if (sigismember (&block_signals
, i
))
686 else if (sigismember (&unblock_signals
, i
))
689 debug_act
= "UNBLOCK";
696 if (dev_debug
&& debug_act
)
698 char signame
[SIG2STR_MAX
];
699 sig2str (i
, signame
);
700 devmsg ("signal %s (%d) mask set to %s\n",
701 signame
, i
, debug_act
);
705 if (sigprocmask (SIG_SETMASK
, &set
, nullptr))
706 error (EXIT_CANCELED
, errno
, _("failed to set signal process mask"));
710 list_signal_handling (void)
713 char signame
[SIG2STR_MAX
];
716 if (sigprocmask (0, nullptr, &set
))
717 error (EXIT_CANCELED
, errno
, _("failed to get signal process mask"));
719 for (int i
= 1; i
<= SIGNUM_BOUND
; i
++)
721 struct sigaction act
;
722 if (sigaction (i
, nullptr, &act
))
725 char const *ignored
= act
.sa_handler
== SIG_IGN
? "IGNORE" : "";
726 char const *blocked
= sigismember (&set
, i
) ? "BLOCK" : "";
727 char const *connect
= *ignored
&& *blocked
? "," : "";
729 if (! *ignored
&& ! *blocked
)
732 sig2str (i
, signame
);
733 fprintf (stderr
, "%-10s (%2d): %s%s%s\n", signame
, i
,
734 blocked
, connect
, ignored
);
739 initialize_signals (void)
741 signals
= xmalloc ((sizeof *signals
) * (SIGNUM_BOUND
+ 1));
743 for (int i
= 0 ; i
<= SIGNUM_BOUND
; i
++)
744 signals
[i
] = UNCHANGED
;
750 main (int argc
, char **argv
)
753 bool ignore_environment
= false;
754 bool opt_nul_terminate_output
= false;
755 char const *newdir
= nullptr;
757 initialize_main (&argc
, &argv
);
758 set_program_name (argv
[0]);
759 setlocale (LC_ALL
, "");
760 bindtextdomain (PACKAGE
, LOCALEDIR
);
761 textdomain (PACKAGE
);
763 initialize_exit_failure (EXIT_CANCELED
);
764 atexit (close_stdout
);
766 initialize_signals ();
768 while ((optc
= getopt_long (argc
, argv
, shortopts
, longopts
, nullptr)) != -1)
773 ignore_environment
= true;
776 append_unset_var (optarg
);
782 opt_nul_terminate_output
= true;
784 case DEFAULT_SIGNAL_OPTION
:
785 parse_signal_action_params (optarg
, true);
786 parse_block_signal_params (optarg
, false);
788 case IGNORE_SIGNAL_OPTION
:
789 parse_signal_action_params (optarg
, false);
791 case BLOCK_SIGNAL_OPTION
:
792 parse_block_signal_params (optarg
, true);
794 case LIST_SIGNAL_HANDLING_OPTION
:
795 report_signal_handling
= true;
801 parse_split_string (optarg
, &optind
, &argc
, &argv
);
803 case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
804 /* These are undocumented options. Attempt to detect
805 incorrect shebang usage with extraneous space, e.g.:
806 #!/usr/bin/env -i command
807 In which case argv[1] == "-i command". */
808 error (0, 0, _("invalid option -- '%c'"), optc
);
809 error (0, 0, _("use -[v]S to pass options in shebang lines"));
810 usage (EXIT_CANCELED
);
812 case_GETOPT_HELP_CHAR
;
813 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
815 usage (EXIT_CANCELED
);
819 if (optind
< argc
&& STREQ (argv
[optind
], "-"))
821 ignore_environment
= true;
825 if (ignore_environment
)
827 devmsg ("cleaning environ\n");
828 static char *dummy_environ
[] = { nullptr };
829 environ
= dummy_environ
;
835 while (optind
< argc
&& (eq
= strchr (argv
[optind
], '=')))
837 devmsg ("setenv: %s\n", argv
[optind
]);
839 if (putenv (argv
[optind
]))
842 error (EXIT_CANCELED
, errno
, _("cannot set %s"),
843 quote (argv
[optind
]));
848 bool program_specified
= optind
< argc
;
850 if (opt_nul_terminate_output
&& program_specified
)
852 error (0, 0, _("cannot specify --null (-0) with command"));
853 usage (EXIT_CANCELED
);
856 if (newdir
&& ! program_specified
)
858 error (0, 0, _("must specify command with --chdir (-C)"));
859 usage (EXIT_CANCELED
);
862 if (! program_specified
)
864 /* Print the environment and exit. */
865 char *const *e
= environ
;
867 printf ("%s%c", *e
++, opt_nul_terminate_output
? '\0' : '\n');
871 reset_signal_handlers ();
872 if (sig_mask_changed
)
873 set_signal_proc_mask ();
875 if (report_signal_handling
)
876 list_signal_handling ();
880 devmsg ("chdir: %s\n", quoteaf (newdir
));
882 if (chdir (newdir
) != 0)
883 error (EXIT_CANCELED
, errno
, _("cannot change directory to %s"),
889 devmsg ("executing: %s\n", argv
[optind
]);
890 for (int i
=optind
; i
<argc
; ++i
)
891 devmsg (" arg[%d]= %s\n", i
-optind
, quote (argv
[i
]));
894 execvp (argv
[optind
], &argv
[optind
]);
896 int exit_status
= errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
;
897 error (0, errno
, "%s", quote (argv
[optind
]));
899 if (exit_status
== EXIT_ENOENT
&& strpbrk (argv
[optind
], C_ISSPACE_CHARS
))
900 error (0, 0, _("use -[v]S to pass options in shebang lines"));
902 main_exit (exit_status
);