build: ensure sys/select.h is included
[coreutils.git] / src / env.c
blob3a1a3869e19ec359065e401ee191af87e61a787e
1 /* env - run a program in a modified environment
2 Copyright (C) 1986-2019 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 */
19 #include <config.h>
20 #include <stdio.h>
21 #include <sys/types.h>
22 #include <getopt.h>
23 #include <c-ctype.h>
25 #include <assert.h>
26 #include "system.h"
27 #include "die.h"
28 #include "error.h"
29 #include "quote.h"
31 /* The official name of this program (e.g., no 'g' prefix). */
32 #define PROGRAM_NAME "env"
34 #define AUTHORS \
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. */
48 static char *varname;
49 static size_t vnlen;
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},
63 {NULL, 0, NULL, 0}
66 void
67 usage (int status)
69 if (status != EXIT_SUCCESS)
70 emit_try_help ();
71 else
73 printf (_("\
74 Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
75 program_name);
76 fputs (_("\
77 Set each NAME to VALUE in the environment and run COMMAND.\n\
78 "), stdout);
80 emit_mandatory_arg_note ();
82 fputs (_("\
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\
86 "), stdout);
87 fputs (_("\
88 -C, --chdir=DIR change working directory to DIR\n\
89 "), stdout);
90 fputs (_("\
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\
94 "), stdout);
95 fputs (HELP_OPTION_DESCRIPTION, stdout);
96 fputs (VERSION_OPTION_DESCRIPTION, stdout);
97 fputs (_("\
98 \n\
99 A mere - implies -i. If no COMMAND, print the resulting environment.\n\
100 "), stdout);
101 emit_ancillary_info (PROGRAM_NAME);
103 exit (status);
106 static void
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;
114 static void
115 unset_envvars (void)
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"),
123 quote (usvars[i]));
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 == '\'' \
137 || c == '\\');
140 static char _GL_ATTRIBUTE_PURE
141 escape_char (const char c)
143 switch (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 == '_')
167 ++end;
168 if (*end == '}')
169 return end;
172 return NULL;
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. */
180 static char*
181 extract_varname (const char* str)
183 ptrdiff_t i;
184 const char* p;
186 p = scan_varname (str);
187 if (!p)
188 return NULL;
190 /* -2 and +2 (below) account for the '${' prefix. */
191 i = p - str - 2;
193 if (i >= vnlen)
195 vnlen = i + 1;
196 varname = xrealloc (varname, vnlen);
199 memcpy (varname, str+2, i);
200 varname[i]=0;
202 return varname;
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). */
212 static void
213 validate_split_str (const char* str, size_t* /*out*/ bufsize,
214 int* /*out*/ maxargc)
216 bool dq, sq, sp;
217 const char *pch;
218 size_t buflen;
219 int cnt = 1;
221 assert (str && str[0] && !isspace (str[0])); /* LCOV_EXCL_LINE */
223 dq = sq = sp = false;
224 buflen = strlen (str)+1;
226 while (*str)
228 const char next = *(str+1);
230 if (isspace (*str) && !dq && !sq)
232 sp = true;
234 else
236 if (sp)
237 ++cnt;
238 sp = false;
241 switch (*str)
243 case '\'':
244 assert (!(sq && dq)); /* LCOV_EXCL_LINE */
245 sq = !sq && !dq;
246 break;
248 case '"':
249 assert (!(sq && dq)); /* LCOV_EXCL_LINE */
250 dq = !sq && !dq;
251 break;
253 case '\\':
254 if (dq && next == 'c')
255 die (EXIT_CANCELED, 0,
256 _("'\\c' must not appear in double-quoted -S string"));
258 if (next == '\0')
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);
265 if (next == '_')
266 ++cnt;
268 ++str;
269 break;
272 case '$':
273 if (sq)
274 break;
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);
282 break;
284 ++str;
287 if (dq || sq)
288 die (EXIT_CANCELED, 0, _("no terminating quote in -S string"));
290 *maxargc = cnt;
291 *bufsize = buflen;
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').
299 Example:
300 char **argv = build_argv ("A=B uname -k', 3)
301 Results in:
302 argv[0] = "DUMMY" - dummy executable name, can be replaced later.
303 argv[1] = "A=B"
304 argv[2] = "uname"
305 argv[3] = "-k"
306 argv[4] = NULL
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:
311 free (argv[0]);
312 free (argv); */
313 static char**
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;
320 int newargc = 0;
321 size_t buflen = 0;
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 \
327 do { \
328 if (sep) \
330 *dest++ = '\0'; \
331 *nextargv++ = dest; \
332 sep = false; \
334 } while (0)
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");
349 *nextargv++ = dest;
350 dest += 6;
352 /* In the following loop,
353 'break' causes the character 'newc' to be added to *dest,
354 'continue' skips the character. */
355 while (*str)
357 char newc = *str; /* default: add the next character. */
359 switch (*str)
361 case '\'':
362 if (dq)
363 break;
364 sq = !sq;
365 CHECK_START_NEW_ARG;
366 ++str;
367 continue;
369 case '"':
370 if (sq)
371 break;
372 dq = !dq;
373 CHECK_START_NEW_ARG;
374 ++str;
375 continue;
377 case ' ':
378 case '\t':
379 /* space/tab outside quotes starts a new argument. */
380 if (sq || dq)
381 break;
382 sep = true;
383 str += strspn (str, " \t"); /* skip whitespace. */
384 continue;
386 case '#':
387 if (!sep)
388 break;
389 goto eos; /* '#' as first char terminates the string. */
391 case '\\':
392 /* backslash inside single-quotes is not special, except \\ and \'. */
393 if (sq && *(str+1) != '\\' && *(str+1) != '\'')
394 break;
396 /* skip the backslash and examine the next character. */
397 newc = *(++str);
398 if ((newc == '\\' || newc == '\'')
399 || (!sq && (newc == '#' || newc == '$' || newc == '"')))
401 /* Pass escaped character as-is. */
403 else if (newc == '_')
405 if (!dq)
407 ++str; /* '\_' outside double-quotes is arg separator. */
408 sep = true;
409 continue;
411 else
412 newc = ' '; /* '\_' inside double-quotes is space. */
414 else if (newc == 'c')
415 goto eos; /* '\c' terminates the string. */
416 else
417 newc = escape_char (newc); /* other characters (e.g. '\n'). */
418 break;
420 case '$':
421 /* ${VARNAME} are not expanded inside single-quotes. */
422 if (sq)
423 break;
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);
430 if (v)
432 CHECK_START_NEW_ARG;
433 devmsg ("expanding ${%s} into %s\n", n, quote (v));
434 dest = stpcpy (dest, v);
436 else
437 devmsg ("replacing ${%s} with null string\n", n);
439 str = strchr (str, '}') + 1;
440 continue;
445 CHECK_START_NEW_ARG;
446 *dest++ = newc;
447 ++str;
450 eos:
451 *dest = '\0';
452 *nextargv = NULL; /* mark the last element in argv as NULL. */
454 return newargv;
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
462 The input argv is:
463 argv[0] = 'env'
464 argv[1] = "-S-i -C/tmp A=B"
465 argv[2] = foo
466 argv[3] = bar
467 This function will modify argv to be:
468 argv[0] = 'env'
469 argv[1] = "-i"
470 argv[2] = "-C/tmp"
471 argv[3] = A=B"
472 argv[4] = foo
473 argv[5] = bar
474 argc will be updated from 4 to 6.
475 optind will be reset to 0 to force getopt_long to rescan all arguments. */
476 static void
477 parse_split_string (const char* str, int /*out*/ *orig_optind,
478 int /*out*/ *orig_argc, char*** /*out*/ orig_argv)
480 int i, newargc;
481 char **newargv, **nextargv;
484 while (isspace (*str))
485 str++;
486 if (*str == '\0')
487 return;
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++));
502 while (*nextargv)
503 devmsg (" & %s\n", quote (*nextargv++));
505 else
507 /* Ensure nextargv points to the last argument */
508 while (*nextargv)
509 ++nextargv;
512 /* Add remaining arguments from original command line */
513 for (i = *orig_optind; i < *orig_argc; ++i)
514 *nextargv++ = (*orig_argv)[i];
515 *nextargv = NULL;
517 /* Count how many new arguments we have */
518 newargc = 0;
519 for (nextargv = newargv; *nextargv; ++nextargv)
520 ++newargc;
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)
531 int optc;
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)
547 switch (optc)
549 case 'i':
550 ignore_environment = true;
551 break;
552 case 'u':
553 append_unset_var (optarg);
554 break;
555 case 'v':
556 dev_debug = true;
557 break;
558 case '0':
559 opt_nul_terminate_output = true;
560 break;
561 case 'C':
562 newdir = optarg;
563 break;
564 case 'S':
565 parse_split_string (optarg, &optind, &argc, &argv);
566 break;
567 case ' ':
568 case '\t':
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);
579 default:
580 usage (EXIT_CANCELED);
584 if (optind < argc && STREQ (argv[optind], "-"))
586 ignore_environment = true;
587 ++optind;
590 if (ignore_environment)
592 devmsg ("cleaning environ\n");
593 static char *dummy_environ[] = { NULL };
594 environ = dummy_environ;
596 else
597 unset_envvars ();
599 char *eq;
600 while (optind < argc && (eq = strchr (argv[optind], '=')))
602 devmsg ("setenv: %s\n", argv[optind]);
604 if (putenv (argv[optind]))
606 *eq = '\0';
607 die (EXIT_CANCELED, errno, _("cannot set %s"),
608 quote (argv[optind]));
610 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;
631 while (*e)
632 printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
633 return EXIT_SUCCESS;
636 if (newdir)
638 devmsg ("chdir: %s\n", quoteaf (newdir));
640 if (chdir (newdir) != 0)
641 die (EXIT_CANCELED, errno, _("cannot change directory to %s"),
642 quoteaf (newdir));
645 if (dev_debug)
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"));
660 return exit_status;