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