Home | History | Annotate | Line # | Download | only in env
      1 /*	$NetBSD: env.c,v 1.25 2025/02/09 14:25:26 kre Exp $	*/
      2 /*
      3  * Copyright (c) 1988, 1993, 1994
      4  *	The Regents of the University of California.  All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  * 3. Neither the name of the University nor the names of its contributors
     15  *    may be used to endorse or promote products derived from this software
     16  *    without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     28  * SUCH DAMAGE.
     29  */
     30 
     31 #include <sys/cdefs.h>
     32 #ifndef lint
     33 __COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\
     34  The Regents of the University of California.  All rights reserved.");
     35 #endif /* not lint */
     36 
     37 #ifndef lint
     38 /*static char sccsid[] = "@(#)env.c	8.3 (Berkeley) 4/2/94";*/
     39 __RCSID("$NetBSD: env.c,v 1.25 2025/02/09 14:25:26 kre Exp $");
     40 #endif /* not lint */
     41 
     42 #include <sys/stat.h>
     43 
     44 #include <err.h>
     45 #include <stdio.h>
     46 #include <string.h>
     47 #include <stdlib.h>
     48 #include <unistd.h>
     49 #include <locale.h>
     50 #include <errno.h>
     51 #include <paths.h>
     52 
     53 static void usage(void) __dead;
     54 static const char *path_find(const char *);
     55 
     56 extern char **environ;
     57 
     58 int
     59 main(int argc, char **argv)
     60 {
     61 	char **ep, term;
     62 	char *cleanenv[1];
     63 	int ch;
     64 
     65 	setprogname(*argv);
     66 	(void)setlocale(LC_ALL, "");
     67 
     68 	term = '\n';
     69 	while ((ch = getopt(argc, argv, "-0C:iu:")) != -1)
     70 		switch((char)ch) {
     71 		case '0':
     72 			term = '\0';
     73 			break;
     74 		case 'C':
     75 			if (chdir(optarg) == -1)
     76 				err(125, "chdir '%s'", optarg);
     77 			/* better to do this than have it be invalid */
     78 			(void)unsetenv("PWD");
     79 			break;
     80 		case '-':			/* obsolete */
     81 		case 'i':
     82 			environ = cleanenv;
     83 			cleanenv[0] = NULL;
     84 			break;
     85 		case 'u':
     86 			if (unsetenv(optarg) == -1)
     87 				err(125, "unsetenv '%s'", optarg);
     88 			break;
     89 		case '?':
     90 		default:
     91 			usage();
     92 		}
     93 
     94 	for (argv += optind; *argv && strchr(*argv, '=') != NULL; ++argv)
     95 		if (putenv(*argv) == -1)
     96 			err(125, "putenv '%s'", *argv);
     97 
     98 	/*
     99 	 * Allow an extra "--" to allow utility names to contain '=' chars
    100 	 */
    101 	if (*argv && strcmp(*argv, "--") == 0)
    102 		argv++;
    103 
    104 	if (!*argv) {
    105 		/*
    106 		 * No utility name is present, simply dump the environment
    107 		 * to stdout, and we're done.
    108 		 */
    109 		for (ep = environ; *ep; ep++)
    110 			(void)printf("%s%c", *ep, term);
    111 
    112 		(void)fflush(stdout);
    113 		if (ferror(stdout))
    114 			err(125, "write to standard output");
    115 
    116 		exit(0);
    117 	}
    118 
    119 	/*
    120 	 * Run the utility, if this succeeds, it doesn't return,
    121 	 * and we no longer exist!   No need to check for errors,
    122 	 * if we have the opportunity to do so, there must be one.
    123 	 */
    124 	(void)execvp(*argv, argv);
    125 
    126 	/*
    127 	 * Return 127 if the command to be run could not be found;
    128 	 * or 126 if the command was found but could not be invoked.
    129 	 *
    130 	 * Working out which happened is hard, and impossible to
    131 	 * truly get correct, but let's try.
    132 	 *
    133 	 * First we need to discover if the utility exists, that
    134 	 * means duplicating much of the work of execvp() without
    135 	 * the actual exec attempt.
    136 	 */
    137 
    138 	if (path_find(*argv) == NULL)	/* Could not be found */
    139 		err(127, "%s", *argv);
    140 
    141 	/*
    142 	 * We could (should) free the return value from path_find()
    143 	 * if it is not identical to *argv, but since all we are
    144 	 * going to do is exit anyway, why bother?
    145 	 */
    146 
    147 	/*
    148 	 * The file does exist, so the "could not be found" is
    149 	 * false, this must be the 126 exit case.
    150 	 */
    151 
    152 	err(126, "%s", *argv);
    153 	/* NOTREACHED */
    154 }
    155 
    156 /*
    157  * search for name in directories given in env var PATH
    158  *
    159  * return the location found, or none if there is none.
    160  * (note this return value is either NULL, or == name,
    161  * or is a pointer to malloc()'d memory)
    162  *
    163  * The value of errno on entry *must* be preserved.
    164  */
    165 static const char *
    166 path_find(const char *name)
    167 {
    168 	int e = errno;
    169 	struct stat sb;
    170 	const char *path;
    171 	const char *firstfound = NULL;
    172 
    173 	if (strchr(name, '/') != NULL) {
    174 		/*
    175 		 * name contains a '/', no PATH search,
    176 		 * just work out if it exists or not.
    177 		 *
    178 		 * nb: stat, not lstat, the thing must
    179 		 * resolve to something we can exec(),
    180 		 * not just a symlink.
    181 		 */
    182 		if (stat(name, &sb) == -1) {
    183 			errno = e;
    184 			return NULL;
    185 		}
    186 		errno = e;
    187 		return name;
    188 	}
    189 
    190 	/* borrow the outline of this loop from execvp() */
    191 
    192 	path = getenv("PATH");
    193 	if (path == NULL)
    194 		path = _PATH_DEFPATH;
    195 
    196 	do {
    197 		const char *p;
    198 		char *pathname;
    199 		ptrdiff_t lp;
    200 
    201 		/* Find the end of this path element. */
    202 		for (p = path; *path != 0 && *path != ':'; path++)
    203 			continue;
    204 		/*
    205 		 * It's a SHELL path -- double, leading and trailing colons
    206 		 * mean the current directory.
    207 		 */
    208 		if (p == path) {
    209 			p = ".";
    210 			lp = 1;
    211 		} else
    212 			lp = path - p;
    213 
    214 		if (asprintf(&pathname, "%.*s/%s", (int)lp, p, name) == -1) {
    215 			/*
    216 			 * This is very unlikely, and usually means ENOMEM
    217 			 * from malloc() - just give up, and say the file could
    218 			 * not be found (which is more or less correct, given
    219 			 * a slightly slanted view on what just happened).
    220 			 */
    221 			errno = e;
    222 			return NULL;
    223 		}
    224 
    225 		if (stat(pathname, &sb) == -1) {
    226 			free(pathname);
    227 			continue;
    228 		}
    229 
    230 		if ((sb.st_mode & 0111) != 0) {
    231 			/*
    232 			 * We located an existing file with
    233 			 * the correct name in PATH, and it
    234 			 * has (someone's) execute permission.
    235 			 *
    236 			 * Done.
    237 			 */
    238 			errno = e;
    239 			return (const char *)pathname;
    240 		}
    241 
    242 		/*
    243 		 * No execute permission, but a file is located.
    244 		 * Continue looking for one which does have execute
    245 		 * permission, which is how execvp() works,
    246 		 * more or less.   (Close enough for our purposes).
    247 		 */
    248 
    249 		if (firstfound == NULL)
    250 			firstfound = pathname;
    251 		else
    252 			free(pathname);
    253 
    254 	} while (*path++ == ':');	/* Otherwise, *path was '\0' */
    255 
    256 	errno = e;
    257 	return firstfound;
    258 }
    259 
    260 static void
    261 usage(void)
    262 {
    263 	const char *me = getprogname();
    264 	int howwide = (int)strlen(me);
    265 
    266 	howwide += 7; /* "Usage: " */
    267 
    268 	(void)fprintf(stderr,
    269 	    "\nUsage: %s %s \\\n%*s %s \\\n%*s %s\n",
    270 	        me, "[-0i] [-C dir] [-u name] [--]",
    271 	        howwide, "", "[name=value ...] [--]",
    272 	        howwide, "", "[utility [arg ...] ]");
    273 
    274 	exit(125);
    275 }
    276