Module Name: src Committed By: kre Date: Sun Feb 9 14:25:26 UTC 2025
Modified Files: src/usr.bin/env: env.1 env.c Log Message: PR bin/59058 Random minor corrections Use the correct exit status values (126 vs 127), the latter when the utility cannot be located (no matter why) and the former when it could be located, but an execvp() attempt failed for some other reason. Use exit status 125 for all errors detected by env itself, which require that it exit, but no longer consider including the -0 option along with a utility name to be such an error. The -0 is useless in such a case, but just ignore it (just as also happens if -u options are given with -i, which makes all -u options meaningless). Using 125 rather than a low number (like 1, which it mostly used to be) should make it slightly easier to detect cases where env has failed, rather than the named utility having failed. Write errors to stdout, and failure of putenv() are now detected and cause an error message and exit(125). The -C option now (also) causes PWD to be removed from the environment (better than it retaining an invalid value). (Currently OLDPWD is left alone, better might be possible.) Document all of that (and make it much clearer than when a utility is successfully invoked, the exit status, any value at all, comes from the utility, rather than pretending that utilities were only permitted to exit with status from 0..125. More wording fixes and clarifications in the man page. While there, delete the BUGS section, after implementing, and documenting, a mechanism to allow utility names to contain an '=' character (as useless as this change is likely to be in practice) - the implemented mechanism is extraordinarily simple (much simpler than this paragraph!) To generate a diff of this commit: cvs rdiff -u -r1.16 -r1.17 src/usr.bin/env/env.1 cvs rdiff -u -r1.24 -r1.25 src/usr.bin/env/env.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/usr.bin/env/env.1 diff -u src/usr.bin/env/env.1:1.16 src/usr.bin/env/env.1:1.17 --- src/usr.bin/env/env.1:1.16 Mon Oct 28 11:30:37 2024 +++ src/usr.bin/env/env.1 Sun Feb 9 14:25:26 2025 @@ -1,4 +1,4 @@ -.\" $NetBSD: env.1,v 1.16 2024/10/28 11:30:37 kim Exp $ +.\" $NetBSD: env.1,v 1.17 2025/02/09 14:25:26 kre Exp $ .\" .\" Copyright (c) 1980, 1990 The Regents of the University of California. .\" All rights reserved. @@ -30,9 +30,9 @@ .\" SUCH DAMAGE. .\" .\" from: @(#)printenv.1 6.7 (Berkeley) 7/28/91 -.\" $NetBSD: env.1,v 1.16 2024/10/28 11:30:37 kim Exp $ +.\" $NetBSD: env.1,v 1.17 2025/02/09 14:25:26 kre Exp $ .\" -.Dd October 28, 2024 +.Dd February 9, 2025 .Dt ENV 1 .Os .Sh NAME @@ -43,24 +43,34 @@ .Op Fl 0i .Op Fl C Ar dir .Op Fl u Ar name +.Op Fl Fl .Op Ar name=value ... +.Op Fl Fl .Oo .Ar utility -.Op argument ... +.Op Ar argument ... .Oc .Sh DESCRIPTION .Nm executes -.Ar utility +.Ar utility , +with the given +.Ar argument Ns s , after modifying the environment as specified on the command line. Each .Ar name=value option specifies -an environmental variable, +an environment variable, .Ar name , with a value of -.Ar value . +.Ar value +which may be empty, +that is to replace an existing environment variable +with the same +.Ar name , +or otherwise is to be added to the environment. +.Pp The .Sq Fl i option causes @@ -71,7 +81,10 @@ it inherits. The .Sq Fl C Ar dir option causes the working directory to be changed to -.Ar dir . +.Ar dir , +and the environment variable +.Ev PWD +to be removed from the environment. .Pp The .Sq Fl u Ar name @@ -88,6 +101,34 @@ must not include the .Ql = character. .Pp +To allow for either a +.Ar name +to be added to the environment, or the +.Ar utility Ns 's +name (if no environment additions are present), +to begin with a minus sign +.Pq Sq \&\- +the first +.Dq Fl Fl +argument is required. +To allow for +.Ar utility Ns 's +name to contain an equals character +.Pq Sq \&= +(anywhere in its word) +the second +.Dq Fl Fl +is required. +If there are no +.Ar name=value +arguments given, then to allow for +.Ar utility +to contain an equals character, both +.Dq Fl Fl +arguments are required. +The first ends the options, the second ends +the (in this case empty) environment variable additions. +.Pp If no .Ar utility is specified, @@ -97,38 +138,41 @@ Each .Ar name=value pair is separated by a new line unless .Fl 0 -is specified, in which case name/value pairs are separated by NUL. +is specified, in which case name/value pairs are separated by a nul +character +.Pq Sq \&\e0 . The .Fl 0 -option and +option is ignored if a .Ar utility -must not be specified together. +is given. .Sh EXIT STATUS +If a +.Ar utility +is specified, can be located, and successfully +invoked, the exit status of +.Nm +is the exit status of the +.Ar utility . +See its documentation for the possible values and interpretations. +.Pp +Otherwise .Nm exits with one of the following values: .Bl -tag -width Ds .It 0 +No .Ar utility -was invoked and completed successfully. -In this case the exit code is returned by the utility itself, not -.Nm . -If no utility was specified, then +was specified, and .Nm -completed successfully and returned the exit code itself. -.It 1 -An invalid command line option was passed to -.Nm . -.It 1\-125 -.Ar utility -was invoked, but failed in some way; -see its manual page for more information. -In this case the exit code is returned by the utility itself, not -.Nm . +has successfully written the contents of the +.Pq possibly modified +environment to standard output. .It 125 -.Ar utility -was specified together with the -.Fl 0 -option. +.Nm +was given an invalid option, +a requested operation failed, +or some other error occurred. .It 126 .Ar utility was found, but could not be invoked. @@ -136,6 +180,24 @@ was found, but could not be invoked. .Ar utility could not be found. .El +.Pp +Whenever +.Nm +exits with a non-zero status, without having invoked a +.Ar utility , +it writes a message to the standard error stream +identifying itself, and the reason for the non-zero exit. +This can help distinguish cases where +.Nm +exits because of a problem, from when +.Ar utility +does so. +The case of a zero exit status is simpler; +if a +.Ar utility +was given on the command line, the zero status +is from that utility, otherwise it is from +.Nm . .Sh COMPATIBILITY The historic .Fl @@ -166,15 +228,10 @@ The and .Fl 0 options first appeared in -.Nx 10 . +.Nx 10 , +after earlier appearing in other systems. .Pp The .Fl C option first appeared in .Nx 10.1 . -.Sh BUGS -.Nm -doesn't handle commands with equal -.Pq Dq = -signs in their -names, for obvious reasons. Index: src/usr.bin/env/env.c diff -u src/usr.bin/env/env.c:1.24 src/usr.bin/env/env.c:1.25 --- src/usr.bin/env/env.c:1.24 Mon Oct 28 11:30:37 2024 +++ src/usr.bin/env/env.c Sun Feb 9 14:25:26 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: env.c,v 1.24 2024/10/28 11:30:37 kim Exp $ */ +/* $NetBSD: env.c,v 1.25 2025/02/09 14:25:26 kre Exp $ */ /* * Copyright (c) 1988, 1993, 1994 * The Regents of the University of California. All rights reserved. @@ -36,9 +36,11 @@ __COPYRIGHT("@(#) Copyright (c) 1988, 19 #ifndef lint /*static char sccsid[] = "@(#)env.c 8.3 (Berkeley) 4/2/94";*/ -__RCSID("$NetBSD: env.c,v 1.24 2024/10/28 11:30:37 kim Exp $"); +__RCSID("$NetBSD: env.c,v 1.25 2025/02/09 14:25:26 kre Exp $"); #endif /* not lint */ +#include <sys/stat.h> + #include <err.h> #include <stdio.h> #include <string.h> @@ -46,8 +48,10 @@ __RCSID("$NetBSD: env.c,v 1.24 2024/10/2 #include <unistd.h> #include <locale.h> #include <errno.h> +#include <paths.h> static void usage(void) __dead; +static const char *path_find(const char *); extern char **environ; @@ -69,7 +73,9 @@ main(int argc, char **argv) break; case 'C': if (chdir(optarg) == -1) - err(EXIT_FAILURE, "chdir '%s'", optarg); + err(125, "chdir '%s'", optarg); + /* better to do this than have it be invalid */ + (void)unsetenv("PWD"); break; case '-': /* obsolete */ case 'i': @@ -78,7 +84,7 @@ main(int argc, char **argv) break; case 'u': if (unsetenv(optarg) == -1) - err(EXIT_FAILURE, "unsetenv '%s'", optarg); + err(125, "unsetenv '%s'", optarg); break; case '?': default: @@ -86,32 +92,184 @@ main(int argc, char **argv) } for (argv += optind; *argv && strchr(*argv, '=') != NULL; ++argv) - (void)putenv(*argv); + if (putenv(*argv) == -1) + err(125, "putenv '%s'", *argv); + + /* + * Allow an extra "--" to allow utility names to contain '=' chars + */ + if (*argv && strcmp(*argv, "--") == 0) + argv++; + + if (!*argv) { + /* + * No utility name is present, simply dump the environment + * to stdout, and we're done. + */ + for (ep = environ; *ep; ep++) + (void)printf("%s%c", *ep, term); + + (void)fflush(stdout); + if (ferror(stdout)) + err(125, "write to standard output"); + + exit(0); + } + + /* + * Run the utility, if this succeeds, it doesn't return, + * and we no longer exist! No need to check for errors, + * if we have the opportunity to do so, there must be one. + */ + (void)execvp(*argv, argv); + + /* + * Return 127 if the command to be run could not be found; + * or 126 if the command was found but could not be invoked. + * + * Working out which happened is hard, and impossible to + * truly get correct, but let's try. + * + * First we need to discover if the utility exists, that + * means duplicating much of the work of execvp() without + * the actual exec attempt. + */ + + if (path_find(*argv) == NULL) /* Could not be found */ + err(127, "%s", *argv); + + /* + * We could (should) free the return value from path_find() + * if it is not identical to *argv, but since all we are + * going to do is exit anyway, why bother? + */ + + /* + * The file does exist, so the "could not be found" is + * false, this must be the 126 exit case. + */ + + err(126, "%s", *argv); + /* NOTREACHED */ +} - if (*argv) { - /* return 127 if the command to be run could not be found; 126 - if the command was found but could not be invoked; 125 if - -0 was specified with utility.*/ - - if (term == '\0') - errx(125, "cannot specify command with -0"); - - (void)execvp(*argv, argv); - err((errno == ENOENT) ? 127 : 126, "%s", *argv); - /* NOTREACHED */ +/* + * search for name in directories given in env var PATH + * + * return the location found, or none if there is none. + * (note this return value is either NULL, or == name, + * or is a pointer to malloc()'d memory) + * + * The value of errno on entry *must* be preserved. + */ +static const char * +path_find(const char *name) +{ + int e = errno; + struct stat sb; + const char *path; + const char *firstfound = NULL; + + if (strchr(name, '/') != NULL) { + /* + * name contains a '/', no PATH search, + * just work out if it exists or not. + * + * nb: stat, not lstat, the thing must + * resolve to something we can exec(), + * not just a symlink. + */ + if (stat(name, &sb) == -1) { + errno = e; + return NULL; + } + errno = e; + return name; } - for (ep = environ; *ep; ep++) - (void)printf("%s%c", *ep, term); + /* borrow the outline of this loop from execvp() */ + + path = getenv("PATH"); + if (path == NULL) + path = _PATH_DEFPATH; + + do { + const char *p; + char *pathname; + ptrdiff_t lp; + + /* Find the end of this path element. */ + for (p = path; *path != 0 && *path != ':'; path++) + continue; + /* + * It's a SHELL path -- double, leading and trailing colons + * mean the current directory. + */ + if (p == path) { + p = "."; + lp = 1; + } else + lp = path - p; + + if (asprintf(&pathname, "%.*s/%s", (int)lp, p, name) == -1) { + /* + * This is very unlikely, and usually means ENOMEM + * from malloc() - just give up, and say the file could + * not be found (which is more or less correct, given + * a slightly slanted view on what just happened). + */ + errno = e; + return NULL; + } + + if (stat(pathname, &sb) == -1) { + free(pathname); + continue; + } + + if ((sb.st_mode & 0111) != 0) { + /* + * We located an existing file with + * the correct name in PATH, and it + * has (someone's) execute permission. + * + * Done. + */ + errno = e; + return (const char *)pathname; + } + + /* + * No execute permission, but a file is located. + * Continue looking for one which does have execute + * permission, which is how execvp() works, + * more or less. (Close enough for our purposes). + */ + + if (firstfound == NULL) + firstfound = pathname; + else + free(pathname); + + } while (*path++ == ':'); /* Otherwise, *path was '\0' */ - exit(0); + errno = e; + return firstfound; } static void usage(void) { + const char *me = getprogname(); + int howwide = (int)strlen(me); + + howwide += 7; /* "Usage: " */ + (void)fprintf(stderr, - "Usage: %s [-0i] [-C dir] [-u name] [name=value ...] [command]\n", - getprogname()); - exit(1); + "\nUsage: %s %s \\\n%*s %s \\\n%*s %s\n", + me, "[-0i] [-C dir] [-u name] [--]", + howwide, "", "[name=value ...] [--]", + howwide, "", "[utility [arg ...] ]"); + + exit(125); }