1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * This code is MKS code ported to Solaris originally with minimum 29 * modifications so that upgrades from MKS would readily integrate. 30 * The MKS basis for this modification was: 31 * 32 * $Id: wordexp.c 1.22 1994/11/21 18:24:50 miked 33 * 34 * Additional modifications have been made to this code to make it 35 * 64-bit clean. 36 */ 37 38 /* 39 * wordexp, wordfree -- POSIX.2 D11.2 word expansion routines. 40 * 41 * Copyright 1985, 1992 by Mortice Kern Systems Inc. All rights reserved. 42 * Modified by Roland Mainz <roland.mainz@nrubsig.org> to support ksh93. 43 */ 44 45 #pragma weak _wordexp = wordexp 46 #pragma weak _wordfree = wordfree 47 48 #include "lint.h" 49 #include <stdio.h> 50 #include <unistd.h> 51 #include <limits.h> 52 #include <fcntl.h> 53 #include <limits.h> 54 #include <stdlib.h> 55 #include <alloca.h> 56 #include <string.h> 57 #include <sys/wait.h> 58 #include <pthread.h> 59 #include <unistd.h> 60 #include <wordexp.h> 61 #include <stdio.h> 62 #include <spawn.h> 63 #include <errno.h> 64 65 #define INITIAL 8 /* initial pathv allocation */ 66 #define BUFSZ 256 /* allocation unit of the line buffer */ 67 68 /* 69 * Needs no locking if fetched only once. 70 * See getenv()/putenv()/setenv(). 71 */ 72 extern const char **_environ; 73 74 /* Local prototypes */ 75 static int append(wordexp_t *, char *); 76 77 /* 78 * |mystpcpy| - like |strcpy()| but returns the end of the buffer 79 * We'll add this later (and a matching multibyte/widechar version) 80 * as normal libc function. 81 * 82 * Copy string s2 to s1. s1 must be large enough. 83 * return s1-1 (position of string terminator ('\0') in destination buffer). 84 */ 85 static char * 86 mystpcpy(char *s1, const char *s2) 87 { 88 while (*s1++ = *s2++) 89 ; 90 return (s1-1); 91 } 92 93 /* 94 * Do word expansion. 95 * We build a mini-script in |buff| which takes care of all details, 96 * including stdin/stdout/stderr redirection, WRDE_NOCMD mode and 97 * the word expansion itself. 98 */ 99 int 100 wordexp(const char *word, wordexp_t *wp, int flags) 101 { 102 const char *path = "/usr/bin/ksh93"; 103 wordexp_t wptmp; 104 size_t si; 105 pid_t pid; 106 char *line, *eob, *cp; /* word from shell */ 107 int rv = WRDE_ERRNO; 108 int status; 109 int pv[2]; /* pipe from shell stdout */ 110 FILE *fp; /* pipe read stream */ 111 int tmpalloc; 112 char *wd = NULL; 113 const char **env = NULL; 114 const char **envp; 115 const char *ev; 116 int n; 117 posix_spawnattr_t attr; 118 posix_spawn_file_actions_t fact; 119 int error; 120 int cancel_state; 121 size_t bufflen; /* Length of |buff| */ 122 char *buff; 123 char *currbuffp; /* Current position of '\0' in |buff| */ 124 char *args[10]; 125 int i; 126 127 /* 128 * Do absolute minimum necessary for the REUSE flag. Eventually 129 * want to be able to actually avoid excessive malloc calls. 130 */ 131 if (flags & WRDE_REUSE) 132 wordfree(wp); 133 134 /* 135 * Initialize wordexp_t 136 * 137 * XPG requires that the struct pointed to by wp not be modified 138 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE. 139 * So we work with wptmp, and only copy wptmp to wp if one of the 140 * previously mentioned conditions is satisfied. 141 */ 142 wptmp = *wp; 143 144 /* 145 * Man page says: 146 * 2. All of the calls must set WRDE_DOOFFS, or all must not 147 * set it. 148 * Therefore, if it's not set, we_offs will always be reset. 149 */ 150 if ((flags & WRDE_DOOFFS) == 0) 151 wptmp.we_offs = 0; 152 153 /* 154 * If we get APPEND|REUSE, how should we do? 155 * allocating buffer anyway to avoid segfault. 156 */ 157 tmpalloc = 0; 158 if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) { 159 wptmp.we_wordc = 0; 160 wptmp.we_wordn = wptmp.we_offs + INITIAL; 161 wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn); 162 if (wptmp.we_wordv == NULL) 163 return (WRDE_NOSPACE); 164 wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs; 165 for (si = 0; si < wptmp.we_offs; si++) 166 wptmp.we_wordv[si] = NULL; 167 tmpalloc = 1; 168 } 169 170 /* 171 * The UNIX98 Posix conformance test suite requires 172 * |wordexp()| to not be a cancellation point. 173 */ 174 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); 175 176 /* 177 * Make sure PWD is in the environment. 178 */ 179 if ((envp = _environ) == NULL) { 180 /* can happen when processing a SunOS 4.x AOUT file */ 181 ev = NULL; 182 n = 0; 183 } else { 184 for (n = 0; (ev = envp[n]) != NULL; n++) { 185 if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0) 186 break; 187 } 188 } 189 if (ev == NULL) { /* PWD missing from the environment */ 190 /* allocate a new environment */ 191 if ((env = malloc((n + 2) * sizeof (char *))) == NULL || 192 (wd = malloc(PATH_MAX + 4)) == NULL) 193 goto cleanup; 194 for (i = 0; i < n; i++) 195 env[i] = envp[i]; 196 (void) strcpy(wd, "PWD="); 197 if (getcwd(&wd[4], PATH_MAX) == NULL) 198 (void) strcpy(&wd[4], "/"); 199 env[i] = wd; 200 env[i + 1] = NULL; 201 envp = env; 202 } 203 204 /* 205 * Calculate size of required buffer (which is size of the 206 * input string (|word|) plus all string literals below; 207 * this value MUST be adjusted each time the literals are 208 * changed!!). 209 */ 210 bufflen = 165 + strlen(word); 211 buff = alloca(bufflen); 212 i = 0; 213 214 /* Start filling the buffer */ 215 buff[0] = '\0'; 216 currbuffp = buff; 217 218 if (flags & WRDE_UNDEF) 219 currbuffp = mystpcpy(currbuffp, "set -o nounset\n"); 220 if ((flags & WRDE_SHOWERR) == 0) { 221 /* 222 * The newline ('\n') is neccesary to make sure that 223 * the redirection to /dev/null is already active in 224 * the case the printf below contains a syntax 225 * error... 226 */ 227 currbuffp = mystpcpy(currbuffp, "exec 2>/dev/null\n"); 228 } 229 /* Squish stdin */ 230 currbuffp = mystpcpy(currbuffp, "exec 0</dev/null\n"); 231 232 if (flags & WRDE_NOCMD) { 233 /* 234 * Switch to restricted shell (rksh) mode here to 235 * put the word expansion into a "cage" which 236 * prevents users from executing external commands 237 * (outside those listed by ${PATH} (which we set 238 * explicitly to /usr/no/such/path/element/)). 239 */ 240 currbuffp = mystpcpy(currbuffp, 241 "export PATH=/usr/no/such/path/element/ ; " 242 "set -o restricted\n"); 243 } 244 245 (void) snprintf(currbuffp, bufflen, 246 "print -f '%%s\\000' -- %s", word); 247 248 args[i++] = strrchr(path, '/') + 1; 249 args[i++] = "-c"; 250 args[i++] = buff; 251 args[i++] = NULL; 252 253 if ((error = posix_spawnattr_init(&attr)) != 0) { 254 errno = error; 255 goto cleanup; 256 } 257 if ((error = posix_spawn_file_actions_init(&fact)) != 0) { 258 (void) posix_spawnattr_destroy(&attr); 259 errno = error; 260 goto cleanup; 261 } 262 263 /* 264 * Set up pipe from shell stdout to "fp" for us 265 */ 266 if (pipe(pv) < 0) { 267 error = errno; 268 (void) posix_spawnattr_destroy(&attr); 269 (void) posix_spawn_file_actions_destroy(&fact); 270 errno = error; 271 goto cleanup; 272 } 273 274 /* 275 * Spawn shell 276 */ 277 error = posix_spawnattr_setflags(&attr, 278 POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP); 279 if (error == 0) 280 error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1); 281 if (error == 0 && pv[0] != 1) 282 error = posix_spawn_file_actions_addclose(&fact, pv[0]); 283 if (error == 0 && pv[1] != 1) 284 error = posix_spawn_file_actions_addclose(&fact, pv[1]); 285 if (error == 0 && !(flags & WRDE_SHOWERR)) 286 error = posix_spawn_file_actions_addopen(&fact, 2, 287 "/dev/null", O_WRONLY, 0); 288 289 if (error == 0) 290 error = posix_spawn(&pid, path, &fact, &attr, 291 (char *const *)args, (char *const *)envp); 292 (void) posix_spawnattr_destroy(&attr); 293 (void) posix_spawn_file_actions_destroy(&fact); 294 (void) close(pv[1]); 295 if (error) { 296 (void) close(pv[0]); 297 errno = error; 298 goto cleanup; 299 } 300 301 if ((fp = fdopen(pv[0], "rF")) == NULL) { 302 error = errno; 303 (void) close(pv[0]); 304 errno = error; 305 goto wait_cleanup; 306 } 307 308 /* 309 * Read words from shell, separated with '\0'. 310 * Since there is no way to disable IFS splitting, 311 * it would be possible to separate the output with '\n'. 312 */ 313 cp = line = malloc(BUFSZ); 314 if (line == NULL) { 315 error = errno; 316 (void) fclose(fp); 317 errno = error; 318 goto wait_cleanup; 319 } 320 eob = line + BUFSZ; 321 322 rv = 0; 323 flockfile(fp); 324 while ((i = getc_unlocked(fp)) != EOF) { 325 *cp++ = (char)i; 326 if (i == '\0') { 327 cp = line; 328 if ((rv = append(&wptmp, cp)) != 0) { 329 break; 330 } 331 } 332 if (cp == eob) { 333 size_t bs = (eob - line); 334 char *nl; 335 336 if ((nl = realloc(line, bs + BUFSZ)) == NULL) { 337 rv = WRDE_NOSPACE; 338 break; 339 } 340 line = nl; 341 cp = line + bs; 342 eob = cp + BUFSZ; 343 } 344 } 345 funlockfile(fp); 346 347 wptmp.we_wordp[wptmp.we_wordc] = NULL; 348 349 free(line); 350 (void) fclose(fp); /* kill shell if still writing */ 351 352 wait_cleanup: 353 while (waitpid(pid, &status, 0) == -1) { 354 if (errno != EINTR) { 355 if (rv == 0) 356 rv = WRDE_ERRNO; 357 break; 358 } 359 } 360 if (rv == 0) 361 rv = WEXITSTATUS(status); /* shell WRDE_* status */ 362 363 cleanup: 364 if (rv == 0) 365 *wp = wptmp; 366 else if (tmpalloc) 367 wordfree(&wptmp); 368 369 if (env) 370 free(env); 371 if (wd) 372 free(wd); 373 /* 374 * Map ksh93 errors to |wordexp()| errors 375 */ 376 switch (rv) { 377 case 1: 378 rv = WRDE_BADVAL; 379 break; 380 case 127: 381 rv = WRDE_BADCHAR; 382 break; 383 } 384 385 (void) pthread_setcancelstate(cancel_state, NULL); 386 return (rv); 387 } 388 389 /* 390 * Append a word to the wordexp_t structure, growing it as necessary. 391 */ 392 static int 393 append(wordexp_t *wp, char *str) 394 { 395 char *cp; 396 char **nwp; 397 398 /* 399 * We will be adding one entry and later adding 400 * one more NULL. So we need 2 more free slots. 401 */ 402 if ((wp->we_wordp + wp->we_wordc) == 403 (wp->we_wordv + wp->we_wordn - 1)) { 404 nwp = realloc(wp->we_wordv, 405 (wp->we_wordn + INITIAL) * sizeof (char *)); 406 if (nwp == NULL) 407 return (WRDE_NOSPACE); 408 wp->we_wordn += INITIAL; 409 wp->we_wordv = nwp; 410 wp->we_wordp = wp->we_wordv + wp->we_offs; 411 } 412 if ((cp = strdup(str)) == NULL) 413 return (WRDE_NOSPACE); 414 wp->we_wordp[wp->we_wordc++] = cp; 415 return (0); 416 } 417 418 /* 419 * Free all space owned by wordexp_t. 420 */ 421 void 422 wordfree(wordexp_t *wp) 423 { 424 size_t i; 425 426 if (wp->we_wordv == NULL) 427 return; 428 for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++) 429 free(wp->we_wordv[i]); 430 free((void *)wp->we_wordv); 431 wp->we_wordc = 0; 432 wp->we_wordv = NULL; 433 } 434