/* env - run a program in a modified environment This is the env utility
Copyright (C) 1986-2018 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */ The GNUv3 license
/* Richard Mlynarik and David MacKenzie */
#include <config.h> Provides system specific information
#include <stdio.h> Provides standard I/O capability
#include <sys/types.h> Provides system data types
#include <getopt.h> ...!includes auto-comment...
#include <c-ctype.h> ...!includes auto-comment...
#include <assert.h> ...!includes auto-comment...
#include "system.h" ...!includes auto-comment...
#include "die.h" ...!includes auto-comment...
#include "error.h" ...!includes auto-comment...
#include "quote.h" ...!includes auto-comment...
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "env" Line 32
#define AUTHORS \ Line 34
proper_name ("Richard Mlynarik"), \ Line 35
proper_name ("David MacKenzie"), \ Line 36
proper_name ("Assaf Gordon") Line 37
/* array of envvars to unset. */
static const char** usvars; Line 40
static size_t usvars_alloc; Line 41
static size_t usvars_used; Line 42
/* Annotate the output with extra info to aid the user. */
static bool dev_debug; Line 45
/* buffer and length of extracted envvars in -S strings. */
static char *varname; Line 48
static size_t vnlen; Line 49
static char const shortopts[] = "+C:iS:u:v0 \t"; Line 51
static struct option const longopts[] = Line 53
{
{"ignore-environment", no_argument, NULL, 'i'}, Line 55
{"null", no_argument, NULL, '0'}, Line 56
{"unset", required_argument, NULL, 'u'}, Line 57
{"chdir", required_argument, NULL, 'C'}, Line 58
{"debug", no_argument, NULL, 'v'}, Line 59
{"split-string", required_argument, NULL, 'S'}, Line 60
{GETOPT_HELP_OPTION_DECL}, Line 61
{GETOPT_VERSION_OPTION_DECL}, Line 62
{NULL, 0, NULL, 0} Line 63
}; Block 1
void Line 66
usage (int status) Line 67
{
if (status != EXIT_SUCCESS) Line 69
emit_try_help (); ...!common auto-comment...
else Line 71
{
printf (_("\ Line 73
Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"), Line 74
program_name); Line 75
fputs (_("\ Line 76
Set each NAME to VALUE in the environment and run COMMAND.\n\ Line 77
"), stdout); Line 78
emit_mandatory_arg_note (); ...!common auto-comment...
fputs (_("\ Line 82
-i, --ignore-environment start with an empty environment\n\ Line 83
-0, --null end each output line with NUL, not newline\n\ Line 84
-u, --unset=NAME remove variable from the environment\n\ Line 85
"), stdout); Line 86
fputs (_("\ Line 87
-C, --chdir=DIR change working directory to DIR\n\ Line 88
"), stdout); Line 89
fputs (_("\ Line 90
-S, --split-string=S process and split S into separate arguments;\n\ Line 91
used to pass multiple arguments on shebang lines\n\ Line 92
-v, --debug print verbose information for each processing step\n\ Line 93
"), stdout); Line 94
fputs (HELP_OPTION_DESCRIPTION, stdout); Line 95
fputs (VERSION_OPTION_DESCRIPTION, stdout); Line 96
fputs (_("\ Line 97
\n\
A mere - implies -i. If no COMMAND, print the resulting environment.\n\ Line 99
"), stdout); Line 100
emit_ancillary_info (PROGRAM_NAME); Line 101
}
exit (status); Line 103
} Block 2
static void Line 106
append_unset_var (const char *var) Line 107
{
if (usvars_used == usvars_alloc) Line 109
usvars = x2nrealloc (usvars, &usvars_alloc, sizeof *usvars); Line 110
usvars[usvars_used++] = var; Line 111
} Block 3
static void Line 114
unset_envvars (void) Line 115
{
for (size_t i = 0; i < usvars_used; ++i) Line 117
{
devmsg ("unset: %s\n", usvars[i]); Line 119
if (unsetenv (usvars[i])) Line 121
die (EXIT_CANCELED, errno, _("cannot unset %s"), Line 122
quote (usvars[i])); Line 123
}
IF_LINT (free (usvars)); Line 126
IF_LINT (usvars = NULL); Line 127
IF_LINT (usvars_used = 0); Line 128
IF_LINT (usvars_alloc = 0); Line 129
} Block 4
static bool _GL_ATTRIBUTE_PURE Line 132
valid_escape_sequence (const char c) Line 133
{
return (c == 'c' || c == 'f' || c == 'n' || c == 'r' || c == 't' || c == 'v' \Line 135
|| c == '#' || c == '$' || c == '_' || c == '"' || c == '\'' \ Line 136
|| c == '\\'); Line 137
} Block 5
static char _GL_ATTRIBUTE_PURE Line 140
escape_char (const char c) Line 141
{
switch (c) Line 143
{
/* \a,\b not supported by FreeBSD's env. */
case 'f': return '\f'; Line 146
case 'n': return '\n'; Line 147
case 'r': return '\r'; Line 148
case 't': return '\t'; Line 149
case 'v': return '\v'; Line 150
default: assert (0); /* LCOV_EXCL_LINE */ Line 151
}
} Block 6
/* Return a pointer to the end of a valid ${VARNAME} string, or NULL.
'str' should point to the '$' character.
First letter in VARNAME must be alpha or underscore,
rest of letters are alnum or underscore. Any other character is an error. */
static const char* _GL_ATTRIBUTE_PURE Line 159
scan_varname (const char* str) Line 160
{
assert (str && *str == '$'); /* LCOV_EXCL_LINE */ Line 162
if ( *(str+1) == '{' && (c_isalpha (*(str+2)) || *(str+2) == '_')) Line 163
{
const char* end = str+3; Line 165
while (c_isalnum (*end) || *end == '_') Line 166
++end; Line 167
if (*end == '}') Line 168
return end; Line 169
}
return NULL; Line 172
} Block 7
/* Return a pointer to a static buffer containing the VARNAME as
extracted from a '${VARNAME}' string.
The returned string will be NUL terminated.
The returned pointer should not be freed.
Return NULL if not a valid ${VARNAME} syntax. */
static char* Line 180
extract_varname (const char* str) Line 181
{
ptrdiff_t i; Line 183
const char* p; Line 184
p = scan_varname (str); Line 186
if (!p) Line 187
return NULL; Line 188
/* -2 and +2 (below) account for the '${' prefix. */
i = p - str - 2; Line 191
if (i >= vnlen) Line 193
{
vnlen = i + 1; Line 195
varname = xrealloc (varname, vnlen); Line 196
}
memcpy (varname, str+2, i); Line 199
varname[i]=0; Line 200
return varname; Line 202
} Block 8
/* Validate the "-S" parameter, according to the syntax defined by FreeBSD's
env(1). Terminate with an error message if not valid.
Calculate and set two values:
bufsize - the size (in bytes) required to hold the resulting string
after ENVVAR expansion (the value is overestimated).
maxargc - the maximum number of arguments (the size of the new argv). */
static void Line 212
validate_split_str (const char* str, size_t* /*out*/ bufsize, Line 213
int* /*out*/ maxargc) Line 214
{
bool dq, sq, sp; Line 216
const char *pch; Line 217
size_t buflen; Line 218
int cnt = 1; Line 219
assert (str && str[0] && !isspace (str[0])); /* LCOV_EXCL_LINE */ Line 221
dq = sq = sp = false; Line 223
buflen = strlen (str)+1; Line 224
while (*str) Line 226
{
const char next = *(str+1); Line 228
if (isspace (*str) && !dq && !sq) Line 230
{
sp = true; Line 232
}
else Line 234
{
if (sp) Line 236
++cnt; Line 237
sp = false; Line 238
}
switch (*str) Line 241
{
case '\'': Line 243
assert (!(sq && dq)); /* LCOV_EXCL_LINE */ Line 244
sq = !sq && !dq; Line 245
break; Line 246
case '"': Line 248
assert (!(sq && dq)); /* LCOV_EXCL_LINE */ Line 249
dq = !sq && !dq; Line 250
break; Line 251
case '\\': Line 253
if (dq && next == 'c') Line 254
die (EXIT_CANCELED, 0, Line 255
_("'\\c' must not appear in double-quoted -S string")); Line 256
if (next == '\0') Line 258
die (EXIT_CANCELED, 0, Line 259
_("invalid backslash at end of string in -S")); Line 260
if (!valid_escape_sequence (next)) Line 262
die (EXIT_CANCELED, 0, _("invalid sequence '\\%c' in -S"), next); Line 263
if (next == '_') Line 265
++cnt; Line 266
++str; Line 268
break; Line 269
case '$': Line 272
if (sq) Line 273
break; Line 274
if (!(pch = extract_varname (str))) Line 276
die (EXIT_CANCELED, 0, _("only ${VARNAME} expansion is supported,"\ Line 277
" error at: %s"), str); Line 278
if ((pch = getenv (pch))) Line 280
buflen += strlen (pch); Line 281
break; Line 282
}
++str; Line 284
}
if (dq || sq) Line 287
die (EXIT_CANCELED, 0, _("no terminating quote in -S string")); Line 288
*maxargc = cnt; Line 290
*bufsize = buflen; Line 291
} Block 9
/* Return a newly-allocated *arg[]-like array,
by parsing and splitting the input 'str'.
'extra_argc' is the number of additional elements to allocate
in the array (on top of the number of args required to split 'str').
Example:
char **argv = build_argv ("A=B uname -k', 3)
Results in:
argv[0] = "DUMMY" - dummy executable name, can be replaced later.
argv[1] = "A=B"
argv[2] = "uname"
argv[3] = "-k"
argv[4] = NULL
argv[5,6,7] = [allocated due to extra_argc, but not initialized]
The strings are stored in an allocated buffer, pointed by argv[0].
To free allocated memory:
free (argv[0]);
free (argv); */
static char** Line 313
build_argv (const char* str, int extra_argc) Line 314
{
bool dq = false, sq = false, sep = true; Line 316
char *dest; /* buffer to hold the new argv values. allocated as one buffer,Line 317
but will contain multiple NUL-terminate strings. */
char **newargv, **nextargv; Line 319
int newargc = 0; Line 320
size_t buflen = 0; Line 321
/* This macro is called before inserting any characters to the output
buffer. It checks if the previous character was a separator
and if so starts a new argv element. */
#define CHECK_START_NEW_ARG \ Line 326
do { \ Line 327
if (sep) \ Line 328
{ \ Line 329
*dest++ = '\0'; \ Line 330
*nextargv++ = dest; \ Line 331
sep = false; \ Line 332
} \ Line 333
} while (0) Line 334
assert (str && str[0] && !isspace (str[0])); /* LCOV_EXCL_LINE */ Line 336
validate_split_str (str, &buflen, &newargc); Line 338
/* allocate buffer. +6 for the "DUMMY\0" executable name, +1 for NUL. */
dest = xmalloc (buflen + 6 + 1); Line 341
/* allocate the argv array.
+2 for the program name (argv[0]) and the last NULL pointer. */
nextargv = newargv = xmalloc ((newargc + extra_argc + 2) * sizeof (char *)); Line 345
/* argv[0] = executable's name - will be replaced later. */
strcpy (dest, "DUMMY"); Line 348
*nextargv++ = dest; Line 349
dest += 6; Line 350
/* In the following loop,
'break' causes the character 'newc' to be added to *dest,
'continue' skips the character. */
while (*str) Line 355
{
char newc = *str; /* default: add the next character. */ Line 357
switch (*str) Line 359
{
case '\'': Line 361
if (dq) Line 362
break; Line 363
sq = !sq; Line 364
CHECK_START_NEW_ARG; Line 365
++str; Line 366
continue; Line 367
case '"': Line 369
if (sq) Line 370
break; Line 371
dq = !dq; Line 372
CHECK_START_NEW_ARG; Line 373
++str; Line 374
continue; Line 375
case ' ': Line 377
case '\t': Line 378
/* space/tab outside quotes starts a new argument. */
if (sq || dq) Line 380
break; Line 381
sep = true; Line 382
str += strspn (str, " \t"); /* skip whitespace. */ Line 383
continue; Line 384
case '#': Line 386
if (!sep) Line 387
break; Line 388
goto eos; /* '#' as first char terminates the string. */ Line 389
case '\\': Line 391
/* backslash inside single-quotes is not special, except \\ and \'. */
if (sq && *(str+1) != '\\' && *(str+1) != '\'') Line 393
break; Line 394
/* skip the backslash and examine the next character. */
newc = *(++str); Line 397
if ((newc == '\\' || newc == '\'') Line 398
|| (!sq && (newc == '#' || newc == '$' || newc == '"'))) Line 399
{
/* Pass escaped character as-is. */
}
else if (newc == '_') Line 403
{
if (!dq) Line 405
{
++str; /* '\_' outside double-quotes is arg separator. */ Line 407
sep = true; Line 408
continue; Line 409
}
else Line 411
newc = ' '; /* '\_' inside double-quotes is space. */ Line 412
}
else if (newc == 'c') Line 414
goto eos; /* '\c' terminates the string. */ Line 415
else Line 416
newc = escape_char (newc); /* other characters (e.g. '\n'). */ Line 417
break; Line 418
case '$': Line 420
/* ${VARNAME} are not expanded inside single-quotes. */
if (sq) Line 422
break; Line 423
/* Store the ${VARNAME} value. Error checking omitted as
the ${VARNAME} was already validated. */
{
char *n = extract_varname (str); Line 428
char *v = getenv (n); Line 429
if (v) Line 430
{
CHECK_START_NEW_ARG; Line 432
devmsg ("expanding ${%s} into %s\n", n, quote (v)); Line 433
dest = stpcpy (dest, v); Line 434
}
else Line 436
devmsg ("replacing ${%s} with null string\n", n); Line 437
str = strchr (str, '}') + 1; Line 439
continue; Line 440
}
}
CHECK_START_NEW_ARG; Line 445
*dest++ = newc; Line 446
++str; Line 447
} Block 10
eos: Line 450
*dest = '\0'; Line 451
*nextargv = NULL; /* mark the last element in argv as NULL. */ Line 452
return newargv; Line 454
}
/* Process an "-S" string and create the corresponding argv array.
Update the given argc/argv parameters with the new argv.
Example: if executed as:
$ env -S"-i -C/tmp A=B" foo bar
The input argv is:
argv[0] = 'env'
argv[1] = "-S-i -C/tmp A=B"
argv[2] = foo
argv[3] = bar
This function will modify argv to be:
argv[0] = 'env'
argv[1] = "-i"
argv[2] = "-C/tmp"
argv[3] = A=B"
argv[4] = foo
argv[5] = bar
argc will be updated from 4 to 6.
optind will be reset to 0 to force getopt_long to rescan all arguments. */
static void Line 476
parse_split_string (const char* str, int /*out*/ *orig_optind, Line 477
int /*out*/ *orig_argc, char*** /*out*/ orig_argv) Line 478
{
int i, newargc; Line 480
char **newargv, **nextargv; Line 481
while (isspace (*str)) Line 484
str++; Line 485
if (*str == '\0') Line 486
return; Line 487
newargv = build_argv (str, *orig_argc - *orig_optind); Line 489
/* restore argv[0] - the 'env' executable name */
*newargv = (*orig_argv)[0]; Line 492
/* Start from argv[1] */
nextargv = newargv + 1; Line 495
/* Print parsed arguments */
if (dev_debug && *nextargv) Line 498
{
devmsg ("split -S: %s\n", quote (str)); Line 500
devmsg (" into: %s\n", quote (*nextargv++)); Line 501
while (*nextargv) Line 502
devmsg (" & %s\n", quote (*nextargv++)); Line 503
} Block 11
else Line 505
{
/* Ensure nextargv points to the last argument */
while (*nextargv) Line 508
++nextargv; Line 509
}
/* Add remaining arguments from original command line */
for (i = *orig_optind; i < *orig_argc; ++i) Line 513
*nextargv++ = (*orig_argv)[i]; Line 514
*nextargv = NULL; Line 515
/* Count how many new arguments we have */
newargc = 0; Line 518
for (nextargv = newargv; *nextargv; ++nextargv) Line 519
++newargc; Line 520
/* set new values for original getopt variables */
*orig_argc = newargc; Line 523
*orig_argv = newargv; Line 524
*orig_optind = 0; /* tell getopt to restart from first argument */ Line 525
}
int
main (int argc, char **argv) Line 529
{
int optc; Line 531
bool ignore_environment = false; Line 532
bool opt_nul_terminate_output = false; Line 533
char const *newdir = NULL; Line 534
initialize_main (&argc, &argv); VMS-specific entry point handling wildcard expansion
set_program_name (argv[0]); Retains program name and discards path
setlocale (LC_ALL, ""); Sets up internationalization (i18n)
bindtextdomain (PACKAGE, LOCALEDIR); Assigns i18n directorySets text domain for _() [gettext()] function
textdomain (PACKAGE); Sets text domain for _() [gettext()] function
initialize_exit_failure (EXIT_CANCELED); Line 542
atexit (close_stdout); Close stdout on exit (see gnulib)
while ((optc = getopt_long (argc, argv, shortopts, longopts, NULL)) != -1) Line 545
{
switch (optc) Line 547
{
case 'i': Line 549
ignore_environment = true; Line 550
break; Line 551
case 'u': Line 552
append_unset_var (optarg); Line 553
break; Line 554
case 'v': Line 555
dev_debug = true; Line 556
break; Line 557
case '0': Line 558
opt_nul_terminate_output = true; Line 559
break; Line 560
case 'C': Line 561
newdir = optarg; Line 562
break; Line 563
case 'S': Line 564
parse_split_string (optarg, &optind, &argc, &argv); Line 565
break; Line 566
case ' ': Line 567
case '\t': Line 568
/* These are undocumented options. Attempt to detect
incorrect shebang usage with extraneous space, e.g.:
#!/usr/bin/env -i command
In which case argv[1] == "-i command". */
error (0, 0, _("invalid option -- '%c'"), optc); Line 573
error (0, 0, _("use -[v]S to pass options in shebang lines")); Line 574
usage (EXIT_CANCELED); Line 575
case_GETOPT_HELP_CHAR; Line 577
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); Line 578
default: Line 579
usage (EXIT_CANCELED); Line 580
}
} Block 13
if (optind < argc && STREQ (argv[optind], "-")) Line 584
{
ignore_environment = true; Line 586
++optind; Line 587
} Block 14
if (ignore_environment) Line 590
{
devmsg ("cleaning environ\n"); Line 592
static char *dummy_environ[] = { NULL }; Line 593
environ = dummy_environ; Line 594
} Block 15
else Line 596
unset_envvars (); Line 597
char *eq; Line 599
while (optind < argc && (eq = strchr (argv[optind], '='))) Line 600
{
devmsg ("setenv: %s\n", argv[optind]); Line 602
if (putenv (argv[optind])) Line 604
{
*eq = '\0'; Line 606
die (EXIT_CANCELED, errno, _("cannot set %s"), Line 607
quote (argv[optind])); Line 608
}
optind++; Line 610
} Block 16
bool program_specified = optind < argc; Line 613
if (opt_nul_terminate_output && program_specified) Line 615
{
error (0, 0, _("cannot specify --null (-0) with command")); Line 617
usage (EXIT_CANCELED); Line 618
} Block 17
if (newdir && ! program_specified) Line 621
{
error (0, 0, _("must specify command with --chdir (-C)")); Line 623
usage (EXIT_CANCELED); Line 624
} Block 18
if (! program_specified) Line 627
{
/* Print the environment and exit. */
char *const *e = environ; Line 630
while (*e) Line 631
printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n'); Line 632
return EXIT_SUCCESS; Line 633
}
if (newdir) Line 636
{
devmsg ("chdir: %s\n", quoteaf (newdir)); Line 638
if (chdir (newdir) != 0) Line 640
die (EXIT_CANCELED, errno, _("cannot change directory to %s"), Line 641
quoteaf (newdir)); Line 642
} Block 20
if (dev_debug) Line 645
{
devmsg ("executing: %s\n", argv[optind]); Line 647
for (int i=optind; i<argc; ++i) Line 648
devmsg (" arg[%d]= %s\n", i-optind, quote (argv[i])); Line 649
} Block 21
execvp (argv[optind], &argv[optind]); Line 652
int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE; Line 654
error (0, errno, "%s", quote (argv[optind])); Line 655
if (exit_status == EXIT_ENOENT && strchr (argv[optind], ' ')) Line 657
error (0, 0, _("use -[v]S to pass options in shebang lines")); Line 658
return exit_status; Line 660
}