1 /* env - run a program in a modified environment
2 Copyright (C) 1986-2018 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>
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 const char** usvars
;
41 static size_t usvars_alloc
;
42 static size_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 static char const shortopts
[] = "+C:iS:u:v0 \t";
53 static struct option
const longopts
[] =
55 {"ignore-environment", no_argument
, NULL
, 'i'},
56 {"null", no_argument
, NULL
, '0'},
57 {"unset", required_argument
, NULL
, 'u'},
58 {"chdir", required_argument
, NULL
, 'C'},
59 {"debug", no_argument
, NULL
, 'v'},
60 {"split-string", required_argument
, NULL
, 'S'},
61 {GETOPT_HELP_OPTION_DECL
},
62 {GETOPT_VERSION_OPTION_DECL
},
69 if (status
!= EXIT_SUCCESS
)
74 Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
77 Set each NAME to VALUE in the environment and run COMMAND.\n\
80 emit_mandatory_arg_note ();
83 -i, --ignore-environment start with an empty environment\n\
84 -0, --null end each output line with NUL, not newline\n\
85 -u, --unset=NAME remove variable from the environment\n\
88 -C, --chdir=DIR change working directory to DIR\n\
91 -S, --split-string=S process and split S into separate arguments;\n\
92 used to pass multiple arguments on shebang lines\n\
93 -v, --debug print verbose information for each processing step\n\
95 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
96 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
99 A mere - implies -i. If no COMMAND, print the resulting environment.\n\
101 emit_ancillary_info (PROGRAM_NAME
);
107 append_unset_var (const char *var
)
109 if (usvars_used
== usvars_alloc
)
110 usvars
= x2nrealloc (usvars
, &usvars_alloc
, sizeof *usvars
);
111 usvars
[usvars_used
++] = var
;
117 for (size_t i
= 0; i
< usvars_used
; ++i
)
119 devmsg ("unset: %s\n", usvars
[i
]);
121 if (unsetenv (usvars
[i
]))
122 die (EXIT_CANCELED
, errno
, _("cannot unset %s"),
126 IF_LINT (free (usvars
));
127 IF_LINT (usvars
= NULL
);
128 IF_LINT (usvars_used
= 0);
129 IF_LINT (usvars_alloc
= 0);
132 static bool _GL_ATTRIBUTE_PURE
133 valid_escape_sequence (const char c
)
135 return (c
== 'c' || c
== 'f' || c
== 'n' || c
== 'r' || c
== 't' || c
== 'v' \
136 || c
== '#' || c
== '$' || c
== '_' || c
== '"' || c
== '\'' \
140 static char _GL_ATTRIBUTE_PURE
141 escape_char (const char c
)
145 /* \a,\b not supported by FreeBSD's env. */
146 case 'f': return '\f';
147 case 'n': return '\n';
148 case 'r': return '\r';
149 case 't': return '\t';
150 case 'v': return '\v';
151 default: assert (0); /* LCOV_EXCL_LINE */
155 /* Return a pointer to the end of a valid ${VARNAME} string, or NULL.
156 'str' should point to the '$' character.
157 First letter in VARNAME must be alpha or underscore,
158 rest of letters are alnum or underscore. Any other character is an error. */
159 static const char* _GL_ATTRIBUTE_PURE
160 scan_varname (const char* str
)
162 assert (str
&& *str
== '$'); /* LCOV_EXCL_LINE */
163 if ( *(str
+1) == '{' && (c_isalpha (*(str
+2)) || *(str
+2) == '_'))
165 const char* end
= str
+3;
166 while (c_isalnum (*end
) || *end
== '_')
175 /* Return a pointer to a static buffer containing the VARNAME as
176 extracted from a '${VARNAME}' string.
177 The returned string will be NUL terminated.
178 The returned pointer should not be freed.
179 Return NULL if not a valid ${VARNAME} syntax. */
181 extract_varname (const char* str
)
186 p
= scan_varname (str
);
190 /* -2 and +2 (below) account for the '${' prefix. */
196 varname
= xrealloc (varname
, vnlen
);
199 memcpy (varname
, str
+2, i
);
205 /* Validate the "-S" parameter, according to the syntax defined by FreeBSD's
206 env(1). Terminate with an error message if not valid.
208 Calculate and set two values:
209 bufsize - the size (in bytes) required to hold the resulting string
210 after ENVVAR expansion (the value is overestimated).
211 maxargc - the maximum number of arguments (the size of the new argv). */
213 validate_split_str (const char* str
, size_t* /*out*/ bufsize
,
214 int* /*out*/ maxargc
)
221 assert (str
&& str
[0] && !isspace (str
[0])); /* LCOV_EXCL_LINE */
223 dq
= sq
= sp
= false;
224 buflen
= strlen (str
)+1;
228 const char next
= *(str
+1);
230 if (isspace (*str
) && !dq
&& !sq
)
244 assert (!(sq
&& dq
)); /* LCOV_EXCL_LINE */
249 assert (!(sq
&& dq
)); /* LCOV_EXCL_LINE */
254 if (dq
&& next
== 'c')
255 die (EXIT_CANCELED
, 0,
256 _("'\\c' must not appear in double-quoted -S string"));
259 die (EXIT_CANCELED
, 0,
260 _("invalid backslash at end of string in -S"));
262 if (!valid_escape_sequence (next
))
263 die (EXIT_CANCELED
, 0, _("invalid sequence '\\%c' in -S"), next
);
276 if (!(pch
= extract_varname (str
)))
277 die (EXIT_CANCELED
, 0, _("only ${VARNAME} expansion is supported,"\
278 " error at: %s"), str
);
280 if ((pch
= getenv (pch
)))
281 buflen
+= strlen (pch
);
288 die (EXIT_CANCELED
, 0, _("no terminating quote in -S string"));
294 /* Return a newly-allocated *arg[]-like array,
295 by parsing and splitting the input 'str'.
296 'extra_argc' is the number of additional elements to allocate
297 in the array (on top of the number of args required to split 'str').
300 char **argv = build_argv ("A=B uname -k', 3)
302 argv[0] = "DUMMY" - dummy executable name, can be replaced later.
307 argv[5,6,7] = [allocated due to extra_argc, but not initialized]
309 The strings are stored in an allocated buffer, pointed by argv[0].
310 To free allocated memory:
314 build_argv (const char* str
, int extra_argc
)
316 bool dq
= false, sq
= false, sep
= true;
317 char *dest
; /* buffer to hold the new argv values. allocated as one buffer,
318 but will contain multiple NUL-terminate strings. */
319 char **newargv
, **nextargv
;
323 /* This macro is called before inserting any characters to the output
324 buffer. It checks if the previous character was a separator
325 and if so starts a new argv element. */
326 #define CHECK_START_NEW_ARG \
331 *nextargv++ = dest; \
336 assert (str
&& str
[0] && !isspace (str
[0])); /* LCOV_EXCL_LINE */
338 validate_split_str (str
, &buflen
, &newargc
);
340 /* allocate buffer. +6 for the "DUMMY\0" executable name, +1 for NUL. */
341 dest
= xmalloc (buflen
+ 6 + 1);
343 /* allocate the argv array.
344 +2 for the program name (argv[0]) and the last NULL pointer. */
345 nextargv
= newargv
= xmalloc ((newargc
+ extra_argc
+ 2) * sizeof (char *));
347 /* argv[0] = executable's name - will be replaced later. */
348 strcpy (dest
, "DUMMY");
352 /* In the following loop,
353 'break' causes the character 'newc' to be added to *dest,
354 'continue' skips the character. */
357 char newc
= *str
; /* default: add the next character. */
379 /* space/tab outside quotes starts a new argument. */
383 str
+= strspn (str
, " \t"); /* skip whitespace. */
389 goto eos
; /* '#' as first char terminates the string. */
392 /* backslash inside single-quotes is not special, except \\ and \'. */
393 if (sq
&& *(str
+1) != '\\' && *(str
+1) != '\'')
396 /* skip the backslash and examine the next character. */
398 if ((newc
== '\\' || newc
== '\'')
399 || (!sq
&& (newc
== '#' || newc
== '$' || newc
== '"')))
401 /* Pass escaped character as-is. */
403 else if (newc
== '_')
407 ++str
; /* '\_' outside double-quotes is arg separator. */
412 newc
= ' '; /* '\_' inside double-quotes is space. */
414 else if (newc
== 'c')
415 goto eos
; /* '\c' terminates the string. */
417 newc
= escape_char (newc
); /* other characters (e.g. '\n'). */
421 /* ${VARNAME} are not expanded inside single-quotes. */
425 /* Store the ${VARNAME} value. Error checking omitted as
426 the ${VARNAME} was already validated. */
428 char *n
= extract_varname (str
);
429 char *v
= getenv (n
);
433 devmsg ("expanding ${%s} into %s\n", n
, quote (v
));
434 dest
= stpcpy (dest
, v
);
437 devmsg ("replacing ${%s} with null string\n", n
);
439 str
= strchr (str
, '}') + 1;
452 *nextargv
= NULL
; /* mark the last element in argv as NULL. */
457 /* Process an "-S" string and create the corresponding argv array.
458 Update the given argc/argv parameters with the new argv.
460 Example: if executed as:
461 $ env -S"-i -C/tmp A=B" foo bar
464 argv[1] = "-S-i -C/tmp A=B"
467 This function will modify argv to be:
474 argc will be updated from 4 to 6.
475 optind will be reset to 0 to force getopt_long to rescan all arguments. */
477 parse_split_string (const char* str
, int /*out*/ *orig_optind
,
478 int /*out*/ *orig_argc
, char*** /*out*/ orig_argv
)
481 char **newargv
, **nextargv
;
484 while (isspace (*str
))
489 newargv
= build_argv (str
, *orig_argc
- *orig_optind
);
491 /* restore argv[0] - the 'env' executable name */
492 *newargv
= (*orig_argv
)[0];
494 /* Start from argv[1] */
495 nextargv
= newargv
+ 1;
497 /* Print parsed arguments */
498 if (dev_debug
&& *nextargv
)
500 devmsg ("split -S: %s\n", quote (str
));
501 devmsg (" into: %s\n", quote (*nextargv
++));
503 devmsg (" & %s\n", quote (*nextargv
++));
507 /* Ensure nextargv points to the last argument */
512 /* Add remaining arguments from original command line */
513 for (i
= *orig_optind
; i
< *orig_argc
; ++i
)
514 *nextargv
++ = (*orig_argv
)[i
];
517 /* Count how many new arguments we have */
519 for (nextargv
= newargv
; *nextargv
; ++nextargv
)
522 /* set new values for original getopt variables */
523 *orig_argc
= newargc
;
524 *orig_argv
= newargv
;
525 *orig_optind
= 0; /* tell getopt to restart from first argument */
529 main (int argc
, char **argv
)
532 bool ignore_environment
= false;
533 bool opt_nul_terminate_output
= false;
534 char const *newdir
= NULL
;
536 initialize_main (&argc
, &argv
);
537 set_program_name (argv
[0]);
538 setlocale (LC_ALL
, "");
539 bindtextdomain (PACKAGE
, LOCALEDIR
);
540 textdomain (PACKAGE
);
542 initialize_exit_failure (EXIT_CANCELED
);
543 atexit (close_stdout
);
545 while ((optc
= getopt_long (argc
, argv
, shortopts
, longopts
, NULL
)) != -1)
550 ignore_environment
= true;
553 append_unset_var (optarg
);
559 opt_nul_terminate_output
= true;
565 parse_split_string (optarg
, &optind
, &argc
, &argv
);
569 /* These are undocumented options. Attempt to detect
570 incorrect shebang usage with extraneous space, e.g.:
571 #!/usr/bin/env -i command
572 In which case argv[1] == "-i command". */
573 error (0, 0, _("invalid option -- '%c'"), optc
);
574 error (0, 0, _("use -[v]S to pass options in shebang lines"));
575 usage (EXIT_CANCELED
);
577 case_GETOPT_HELP_CHAR
;
578 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
580 usage (EXIT_CANCELED
);
584 if (optind
< argc
&& STREQ (argv
[optind
], "-"))
586 ignore_environment
= true;
590 if (ignore_environment
)
592 devmsg ("cleaning environ\n");
593 static char *dummy_environ
[] = { NULL
};
594 environ
= dummy_environ
;
600 while (optind
< argc
&& (eq
= strchr (argv
[optind
], '=')))
602 devmsg ("setenv: %s\n", argv
[optind
]);
604 if (putenv (argv
[optind
]))
607 die (EXIT_CANCELED
, errno
, _("cannot set %s"),
608 quote (argv
[optind
]));
613 bool program_specified
= optind
< argc
;
615 if (opt_nul_terminate_output
&& program_specified
)
617 error (0, 0, _("cannot specify --null (-0) with command"));
618 usage (EXIT_CANCELED
);
621 if (newdir
&& ! program_specified
)
623 error (0, 0, _("must specify command with --chdir (-C)"));
624 usage (EXIT_CANCELED
);
627 if (! program_specified
)
629 /* Print the environment and exit. */
630 char *const *e
= environ
;
632 printf ("%s%c", *e
++, opt_nul_terminate_output
? '\0' : '\n');
638 devmsg ("chdir: %s\n", quoteaf (newdir
));
640 if (chdir (newdir
) != 0)
641 die (EXIT_CANCELED
, errno
, _("cannot change directory to %s"),
647 devmsg ("executing: %s\n", argv
[optind
]);
648 for (int i
=optind
; i
<argc
; ++i
)
649 devmsg (" arg[%d]= %s\n", i
-optind
, quote (argv
[i
]));
652 execvp (argv
[optind
], &argv
[optind
]);
654 int exit_status
= errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
;
655 error (0, errno
, "%s", quote (argv
[optind
]));
657 if (exit_status
== EXIT_ENOENT
&& strchr (argv
[optind
], ' '))
658 error (0, 0, _("use -[v]S to pass options in shebang lines"));