1 /* env - run a program in a modified environment
2 Copyright (C) 1986-2024 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>
27 #include "operand2sig.h"
31 /* The official name of this program (e.g., no 'g' prefix). */
32 #define PROGRAM_NAME "env"
35 proper_name ("Richard Mlynarik"), \
36 proper_name ("David MacKenzie"), \
37 proper_name ("Assaf Gordon")
39 /* Array of envvars to unset. */
40 static char const **usvars
;
41 static size_t usvars_alloc
;
42 static idx_t usvars_used
;
44 /* Annotate the output with extra info to aid the user. */
45 static bool dev_debug
;
47 /* Buffer and length of extracted envvars in -S strings. */
51 /* Possible actions on each signal. */
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. */
59 static enum SIGNAL_MODE
*signals
;
61 /* Set of signals to block. */
62 static sigset_t block_signals
;
64 /* Set of signals to unblock. */
65 static sigset_t unblock_signals
;
67 /* Whether signal mask adjustment requested. */
68 static bool sig_mask_changed
;
70 /* Whether to list non default handling. */
71 static bool report_signal_handling
;
73 /* The isspace characters in the C locale. */
74 #define C_ISSPACE_CHARS " \t\n\v\f\r"
76 static char const shortopts
[] = "+a:C:iS:u:v0" C_ISSPACE_CHARS
;
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. */
82 DEFAULT_SIGNAL_OPTION
= CHAR_MAX
+ 1,
85 LIST_SIGNAL_HANDLING_OPTION
,
88 static struct option
const longopts
[] =
90 {"argv0", required_argument
, nullptr, 'a'},
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 -a, --argv0=ARG pass ARG as the zeroth argument of COMMAND\n\
126 -i, --ignore-environment start with an empty environment\n\
127 -0, --null end each output line with NUL, not newline\n\
128 -u, --unset=NAME remove variable from the environment\n\
131 -C, --chdir=DIR change working directory to DIR\n\
134 -S, --split-string=S process and split S into separate arguments;\n\
135 used to pass multiple arguments on shebang lines\n\
138 --block-signal[=SIG] block delivery of SIG signal(s) to COMMAND\n\
141 --default-signal[=SIG] reset handling of SIG signal(s) to the default\n\
144 --ignore-signal[=SIG] set handling of SIG signal(s) to do nothing\n\
147 --list-signal-handling list non default signal handling to stderr\n\
150 -v, --debug print verbose information for each processing step\n\
152 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
153 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
156 A mere - implies -i. If no COMMAND, print the resulting environment.\n\
160 SIG may be a signal name like 'PIPE', or a signal number like '13'.\n\
161 Without SIG, all known signals are included. Multiple signals can be\n\
162 comma-separated. An empty SIG argument is a no-op.\n\
164 emit_exec_status (PROGRAM_NAME
);
165 emit_ancillary_info (PROGRAM_NAME
);
171 append_unset_var (char const *var
)
173 if (usvars_used
== usvars_alloc
)
174 usvars
= x2nrealloc (usvars
, &usvars_alloc
, sizeof *usvars
);
175 usvars
[usvars_used
++] = var
;
181 for (idx_t i
= 0; i
< usvars_used
; ++i
)
183 devmsg ("unset: %s\n", usvars
[i
]);
185 if (unsetenv (usvars
[i
]))
186 error (EXIT_CANCELED
, errno
, _("cannot unset %s"),
191 /* Return a pointer to the end of a valid ${VARNAME} string, or nullptr.
192 'str' should point to the '$' character.
193 First letter in VARNAME must be alpha or underscore,
194 rest of letters are alnum or underscore.
195 Any other character is an error. */
198 scan_varname (char const *str
)
200 if (str
[1] == '{' && (c_isalpha (str
[2]) || str
[2] == '_'))
202 char const *end
= str
+ 3;
203 while (c_isalnum (*end
) || *end
== '_')
212 /* Return a pointer to a static buffer containing the VARNAME as
213 extracted from a '${VARNAME}' string.
214 The returned string will be NUL terminated.
215 The returned pointer should not be freed.
216 Return nullptr if not a valid ${VARNAME} syntax. */
218 extract_varname (char const *str
)
223 p
= scan_varname (str
);
227 /* -2 and +2 (below) account for the '${' prefix. */
233 varname
= xrealloc (varname
, vnlen
);
236 memcpy (varname
, str
+ 2, i
);
242 /* Temporary buffer used by --split-string processing. */
245 /* Buffer address, arg count, and half the number of elements in the buffer.
246 ARGC and ARGV are as in 'main', and ARGC + 1 <= HALF_ALLOC so
247 that the upper half of ARGV can be used for string contents.
248 This may waste up to half the space but keeps the code simple,
249 which is better for this rarely-used but security-sensitive code.
251 ARGV[0] is not initialized; that is the caller's responsibility
254 During assembly, ARGV[I] (where 0 < I < ARGC) contains the offset
255 of the Ith string (relative to ARGV + HALF_ALLOC), so that
256 reallocating ARGV does not change the validity of its contents.
257 The integer offset is cast to char * during assembly, and is
258 converted to a true char * pointer on finalization.
260 During assembly, ARGV[ARGC] contains the offset of the first
261 unused string byte (relative to ARGV + HALF_ALLOC). */
266 /* The number of extra argv slots to keep room for. */
269 /* Whether processing should act as if the most recent character
270 seen was a separator. */
274 /* Expand SS so that it has at least one more argv slot and at least
275 one more string byte. */
277 splitbuf_grow (struct splitbuf
*ss
)
279 idx_t old_half_alloc
= ss
->half_alloc
;
280 idx_t string_bytes
= (intptr_t) ss
->argv
[ss
->argc
];
281 ss
->argv
= xpalloc (ss
->argv
, &ss
->half_alloc
, 1,
282 MIN (INT_MAX
, IDX_MAX
), 2 * sizeof *ss
->argv
);
283 memmove (ss
->argv
+ ss
->half_alloc
, ss
->argv
+ old_half_alloc
, string_bytes
);
286 /* In SS, append C to the last string. */
288 splitbuf_append_byte (struct splitbuf
*ss
, char c
)
290 idx_t string_bytes
= (intptr_t) ss
->argv
[ss
->argc
];
291 if (ss
->half_alloc
* sizeof *ss
->argv
<= string_bytes
)
293 ((char *) (ss
->argv
+ ss
->half_alloc
))[string_bytes
] = c
;
294 ss
->argv
[ss
->argc
] = (char *) (intptr_t) (string_bytes
+ 1);
297 /* If SS's most recent character was a separator, finish off its
298 previous argument and start a new one. */
300 check_start_new_arg (struct splitbuf
*ss
)
304 splitbuf_append_byte (ss
, '\0');
306 if (ss
->half_alloc
<= argc
+ ss
->extra_argc
+ 1)
308 ss
->argv
[argc
+ 1] = ss
->argv
[argc
];
314 /* All additions to SS have been made. Convert its offsets to pointers,
315 and return the resulting argument vector. */
317 splitbuf_finishup (struct splitbuf
*ss
)
320 char **argv
= ss
->argv
;
321 char *stringbase
= (char *) (ss
->argv
+ ss
->half_alloc
);
322 for (int i
= 1; i
< argc
; i
++)
323 argv
[i
] = stringbase
+ (intptr_t) argv
[i
];
327 /* Return a newly-allocated argv-like array,
328 by parsing and splitting the input 'str'.
330 'extra_argc' is the number of additional elements to allocate
331 in the array (on top of the number of args required to split 'str').
333 Store into *argc the number of arguments found (plus 1 for
338 char **argv = build_argv ("A=B uname -k', 3, &argc);
341 argv[0] = [not initialized]
345 argv[4,5,6,7] = [allocated due to extra_argc + 1, but not initialized]
347 To free allocated memory:
349 However, 'env' does not free since it's about to exec or exit anyway
350 and the complexity of keeping track of the storage that may have been
351 allocated via multiple calls to build_argv is not worth the hassle. */
353 build_argv (char const *str
, int extra_argc
, int *argc
)
355 bool dq
= false, sq
= false;
357 ss
.argv
= xnmalloc (extra_argc
+ 2, 2 * sizeof *ss
.argv
);
359 ss
.half_alloc
= extra_argc
+ 2;
360 ss
.extra_argc
= extra_argc
;
362 ss
.argv
[ss
.argc
] = 0;
364 /* In the following loop,
365 'break' causes the character 'newc' to be added to *dest,
366 'continue' skips the character. */
369 char newc
= *str
; /* Default: add the next character. */
377 check_start_new_arg (&ss
);
385 check_start_new_arg (&ss
);
389 case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
390 /* Start a new argument if outside quotes. */
394 str
+= strspn (str
, C_ISSPACE_CHARS
);
400 goto eos
; /* '#' as first char terminates the string. */
403 /* Backslash inside single-quotes is not special, except \\
405 if (sq
&& str
[1] != '\\' && str
[1] != '\'')
408 /* Skip the backslash and examine the next character. */
412 case '"': case '#': case '$': case '\'': case '\\':
413 /* Pass escaped character as-is. */
419 ++str
; /* '\_' outside double-quotes is arg separator. */
423 newc
= ' '; /* '\_' inside double-quotes is space. */
428 error (EXIT_CANCELED
, 0,
429 _("'\\c' must not appear in double-quoted -S string"));
430 goto eos
; /* '\c' terminates the string. */
432 case 'f': newc
= '\f'; break;
433 case 'n': newc
= '\n'; break;
434 case 'r': newc
= '\r'; break;
435 case 't': newc
= '\t'; break;
436 case 'v': newc
= '\v'; break;
439 error (EXIT_CANCELED
, 0,
440 _("invalid backslash at end of string in -S"));
443 error (EXIT_CANCELED
, 0,
444 _("invalid sequence '\\%c' in -S"), newc
);
449 /* ${VARNAME} are not expanded inside single-quotes. */
453 /* Store the ${VARNAME} value. */
455 char *n
= extract_varname (str
);
457 error (EXIT_CANCELED
, 0,
458 _("only ${VARNAME} expansion is supported, error at: %s"),
461 char *v
= getenv (n
);
464 check_start_new_arg (&ss
);
465 devmsg ("expanding ${%s} into %s\n", n
, quote (v
));
467 splitbuf_append_byte (&ss
, *v
);
470 devmsg ("replacing ${%s} with null string\n", n
);
472 str
= strchr (str
, '}') + 1;
477 check_start_new_arg (&ss
);
478 splitbuf_append_byte (&ss
, newc
);
483 error (EXIT_CANCELED
, 0, _("no terminating quote in -S string"));
486 splitbuf_append_byte (&ss
, '\0');
488 return splitbuf_finishup (&ss
);
491 /* Process an "-S" string and create the corresponding argv array.
492 Update the given argc/argv parameters with the new argv.
494 Example: if executed as:
495 $ env -S"-i -C/tmp A=B" foo bar
498 argv[1] = "-S-i -C/tmp A=B"
502 This function will modify argv to be:
510 argc will be updated from 4 to 6.
511 optind will be reset to 0 to force getopt_long to rescan all arguments. */
513 parse_split_string (char const *str
, int *orig_optind
,
514 int *orig_argc
, char ***orig_argv
)
516 int extra_argc
= *orig_argc
- *orig_optind
, newargc
;
517 char **newargv
= build_argv (str
, extra_argc
, &newargc
);
519 /* Restore argv[0] - the 'env' executable name. */
520 *newargv
= (*orig_argv
)[0];
522 /* Print parsed arguments. */
523 if (dev_debug
&& 1 < newargc
)
525 devmsg ("split -S: %s\n", quote (str
));
526 devmsg (" into: %s\n", quote (newargv
[1]));
527 for (int i
= 2; i
< newargc
; i
++)
528 devmsg (" & %s\n", quote (newargv
[i
]));
531 /* Add remaining arguments and terminating null from the original
533 memcpy (newargv
+ newargc
, *orig_argv
+ *orig_optind
,
534 (extra_argc
+ 1) * sizeof *newargv
);
536 /* Set new values for original getopt variables. */
537 *orig_argc
= newargc
+ extra_argc
;
538 *orig_argv
= newargv
;
539 *orig_optind
= 0; /* Tell getopt to restart from first argument. */
543 parse_signal_action_params (char const *arg
, bool set_default
)
546 char *optarg_writable
;
550 /* Without an argument, reset all signals.
551 Some signals cannot be set to ignore or default (e.g., SIGKILL,
552 SIGSTOP on most OSes, and SIGCONT on AIX.) - so ignore errors. */
553 for (int i
= 1 ; i
<= SIGNUM_BOUND
; i
++)
554 signals
[i
] = set_default
? DEFAULT_NOERR
: IGNORE_NOERR
;
558 optarg_writable
= xstrdup (arg
);
560 opt_sig
= strtok (optarg_writable
, ",");
563 int signum
= operand2sig (opt_sig
);
564 /* operand2sig accepts signal 0 (EXIT) - but we reject it. */
566 error (0, 0, _("%s: invalid signal"), quote (opt_sig
));
568 usage (exit_failure
);
570 signals
[signum
] = set_default
? DEFAULT
: IGNORE
;
572 opt_sig
= strtok (nullptr, ",");
575 free (optarg_writable
);
579 reset_signal_handlers (void)
581 for (int i
= 1; i
<= SIGNUM_BOUND
; i
++)
583 struct sigaction act
;
585 if (signals
[i
] == UNCHANGED
)
588 bool ignore_errors
= (signals
[i
] == DEFAULT_NOERR
589 || signals
[i
] == IGNORE_NOERR
);
591 bool set_to_default
= (signals
[i
] == DEFAULT
592 || signals
[i
] == DEFAULT_NOERR
);
594 int sig_err
= sigaction (i
, nullptr, &act
);
596 if (sig_err
&& !ignore_errors
)
597 error (EXIT_CANCELED
, errno
,
598 _("failed to get signal action for signal %d"), i
);
602 act
.sa_handler
= set_to_default
? SIG_DFL
: SIG_IGN
;
603 sig_err
= sigaction (i
, &act
, nullptr);
604 if (sig_err
&& !ignore_errors
)
605 error (EXIT_CANCELED
, errno
,
606 _("failed to set signal action for signal %d"), i
);
611 char signame
[SIG2STR_MAX
];
612 if (sig2str (i
, signame
) != 0)
613 snprintf (signame
, sizeof signame
, "SIG%d", i
);
614 devmsg ("Reset signal %s (%d) to %s%s\n",
616 set_to_default
? "DEFAULT" : "IGNORE",
617 sig_err
? " (failure ignored)" : "");
624 parse_block_signal_params (char const *arg
, bool block
)
627 char *optarg_writable
;
631 /* Without an argument, reset all signals. */
632 sigfillset (block
? &block_signals
: &unblock_signals
);
633 sigemptyset (block
? &unblock_signals
: &block_signals
);
635 else if (! sig_mask_changed
)
637 /* Initialize the sets. */
638 sigemptyset (&block_signals
);
639 sigemptyset (&unblock_signals
);
642 sig_mask_changed
= true;
647 optarg_writable
= xstrdup (arg
);
649 opt_sig
= strtok (optarg_writable
, ",");
652 int signum
= operand2sig (opt_sig
);
653 /* operand2sig accepts signal 0 (EXIT) - but we reject it. */
655 error (0, 0, _("%s: invalid signal"), quote (opt_sig
));
657 usage (exit_failure
);
659 if (sigaddset (block
? &block_signals
: &unblock_signals
, signum
) == -1)
662 error (EXIT_CANCELED
, errno
,
663 _("failed to block signal %d"), signum
);
664 /* else diagnosed in parse_signal_action_params(). */
667 sigdelset (block
? &unblock_signals
: &block_signals
, signum
);
669 opt_sig
= strtok (nullptr, ",");
672 free (optarg_writable
);
676 set_signal_proc_mask (void)
678 /* Get the existing signal mask */
680 char const *debug_act
;
684 if (sigprocmask (0, nullptr, &set
))
685 error (EXIT_CANCELED
, errno
, _("failed to get signal process mask"));
687 for (int i
= 1; i
<= SIGNUM_BOUND
; i
++)
689 if (sigismember (&block_signals
, i
))
694 else if (sigismember (&unblock_signals
, i
))
697 debug_act
= "UNBLOCK";
704 if (dev_debug
&& debug_act
)
706 char signame
[SIG2STR_MAX
];
707 if (sig2str (i
, signame
) != 0)
708 snprintf (signame
, sizeof signame
, "SIG%d", i
);
709 devmsg ("signal %s (%d) mask set to %s\n",
710 signame
, i
, debug_act
);
714 if (sigprocmask (SIG_SETMASK
, &set
, nullptr))
715 error (EXIT_CANCELED
, errno
, _("failed to set signal process mask"));
719 list_signal_handling (void)
722 char signame
[SIG2STR_MAX
];
725 if (sigprocmask (0, nullptr, &set
))
726 error (EXIT_CANCELED
, errno
, _("failed to get signal process mask"));
728 for (int i
= 1; i
<= SIGNUM_BOUND
; i
++)
730 struct sigaction act
;
731 if (sigaction (i
, nullptr, &act
))
734 char const *ignored
= act
.sa_handler
== SIG_IGN
? "IGNORE" : "";
735 char const *blocked
= sigismember (&set
, i
) ? "BLOCK" : "";
736 char const *connect
= *ignored
&& *blocked
? "," : "";
738 if (! *ignored
&& ! *blocked
)
741 if (sig2str (i
, signame
) != 0)
742 snprintf (signame
, sizeof signame
, "SIG%d", i
);
743 fprintf (stderr
, "%-10s (%2d): %s%s%s\n", signame
, i
,
744 blocked
, connect
, ignored
);
749 initialize_signals (void)
751 signals
= xmalloc ((sizeof *signals
) * (SIGNUM_BOUND
+ 1));
753 for (int i
= 0 ; i
<= SIGNUM_BOUND
; i
++)
754 signals
[i
] = UNCHANGED
;
760 main (int argc
, char **argv
)
763 bool ignore_environment
= false;
764 bool opt_nul_terminate_output
= false;
765 char const *newdir
= nullptr;
766 char *argv0
= nullptr;
768 initialize_main (&argc
, &argv
);
769 set_program_name (argv
[0]);
770 setlocale (LC_ALL
, "");
771 bindtextdomain (PACKAGE
, LOCALEDIR
);
772 textdomain (PACKAGE
);
774 initialize_exit_failure (EXIT_CANCELED
);
775 atexit (close_stdout
);
777 initialize_signals ();
779 while ((optc
= getopt_long (argc
, argv
, shortopts
, longopts
, nullptr)) != -1)
787 ignore_environment
= true;
790 append_unset_var (optarg
);
796 opt_nul_terminate_output
= true;
798 case DEFAULT_SIGNAL_OPTION
:
799 parse_signal_action_params (optarg
, true);
800 parse_block_signal_params (optarg
, false);
802 case IGNORE_SIGNAL_OPTION
:
803 parse_signal_action_params (optarg
, false);
805 case BLOCK_SIGNAL_OPTION
:
806 parse_block_signal_params (optarg
, true);
808 case LIST_SIGNAL_HANDLING_OPTION
:
809 report_signal_handling
= true;
815 parse_split_string (optarg
, &optind
, &argc
, &argv
);
817 case ' ': case '\t': case '\n': case '\v': case '\f': case '\r':
818 /* These are undocumented options. Attempt to detect
819 incorrect shebang usage with extraneous space, e.g.:
820 #!/usr/bin/env -i command
821 In which case argv[1] == "-i command". */
822 error (0, 0, _("invalid option -- '%c'"), optc
);
823 error (0, 0, _("use -[v]S to pass options in shebang lines"));
824 usage (EXIT_CANCELED
);
826 case_GETOPT_HELP_CHAR
;
827 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
829 usage (EXIT_CANCELED
);
833 if (optind
< argc
&& STREQ (argv
[optind
], "-"))
835 ignore_environment
= true;
839 if (ignore_environment
)
841 devmsg ("cleaning environ\n");
842 static char *dummy_environ
[] = { nullptr };
843 environ
= dummy_environ
;
849 while (optind
< argc
&& (eq
= strchr (argv
[optind
], '=')))
851 devmsg ("setenv: %s\n", argv
[optind
]);
853 if (putenv (argv
[optind
]))
856 error (EXIT_CANCELED
, errno
, _("cannot set %s"),
857 quote (argv
[optind
]));
862 bool program_specified
= optind
< argc
;
864 if (opt_nul_terminate_output
&& program_specified
)
866 error (0, 0, _("cannot specify --null (-0) with command"));
867 usage (EXIT_CANCELED
);
870 if (newdir
&& ! program_specified
)
872 error (0, 0, _("must specify command with --chdir (-C)"));
873 usage (EXIT_CANCELED
);
876 if (argv0
&& ! program_specified
)
878 error (0, 0, _("must specify command with --argv0 (-a)"));
879 usage (EXIT_CANCELED
);
882 if (! program_specified
)
884 /* Print the environment and exit. */
885 char *const *e
= environ
;
887 printf ("%s%c", *e
++, opt_nul_terminate_output
? '\0' : '\n');
891 reset_signal_handlers ();
892 if (sig_mask_changed
)
893 set_signal_proc_mask ();
895 if (report_signal_handling
)
896 list_signal_handling ();
900 devmsg ("chdir: %s\n", quoteaf (newdir
));
902 if (chdir (newdir
) != 0)
903 error (EXIT_CANCELED
, errno
, _("cannot change directory to %s"),
907 char *program
= argv
[optind
];
910 devmsg ("argv0: %s\n", quoteaf (argv0
));
911 argv
[optind
] = argv0
;
916 devmsg ("executing: %s\n", program
);
917 for (int i
=optind
; i
<argc
; ++i
)
918 devmsg (" arg[%d]= %s\n", i
-optind
, quote (argv
[i
]));
921 execvp (program
, &argv
[optind
]);
923 int exit_status
= errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
;
924 error (0, errno
, "%s", quote (program
));
926 if (exit_status
== EXIT_ENOENT
&& strpbrk (program
, C_ISSPACE_CHARS
))
927 error (0, 0, _("use -[v]S to pass options in shebang lines"));
929 main_exit (exit_status
);