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 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * This code is MKS code ported to Solaris originally with minimum 30 * modifications so that upgrades from MKS would readily integrate. 31 * The MKS basis for this modification was: 32 * 33 * $Id: wordexp.c 1.22 1994/11/21 18:24:50 miked 34 * 35 * Additional modifications have been made to this code to make it 36 * 64-bit clean. 37 */ 38 39 /* 40 * wordexp, wordfree -- POSIX.2 D11.2 word expansion routines. 41 * 42 * Copyright 1985, 1992 by Mortice Kern Systems Inc. All rights reserved. 43 * Modified by Roland Mainz <roland.mainz@nrubsig.org> to support ksh93. 44 * 45 */ 46 47 #pragma weak wordexp = _wordexp 48 #pragma weak wordfree = _wordfree 49 50 /* Safeguard against mistakes in the Makefiles */ 51 #ifndef WORDEXP_KSH93 52 #error "WORDEXP_KSH93 not set. Please check the Makefile flags." 53 #endif 54 55 #include "synonyms.h" 56 #include <stdio.h> 57 #include <unistd.h> 58 #include <limits.h> 59 #include <fcntl.h> 60 #include <limits.h> 61 #include <stdlib.h> 62 #if WORDEXP_KSH93 63 #include <alloca.h> 64 #endif /* WORDEXP_KSH93 */ 65 #include <string.h> 66 #include <sys/wait.h> 67 #include <unistd.h> 68 #include <wordexp.h> 69 #include <stdio.h> 70 #include <spawn.h> 71 #include <errno.h> 72 73 #define INITIAL 8 /* initial pathv allocation */ 74 #define BUFSZ 256 /* allocation unit of the line buffer */ 75 76 /* Local prototypes */ 77 static int append(wordexp_t *, char *); 78 79 #if WORDEXP_KSH93 80 /* 81 * |mystpcpy| - like |strcpy()| but returns the end of the buffer 82 * We'll add this later (and a matching multibyte/widechar version) 83 * as normal libc function. 84 * 85 * Copy string s2 to s1. s1 must be large enough. 86 * return s1-1 (position of string terminator ('\0') in destination buffer). 87 */ 88 static char * 89 mystpcpy(char *s1, const char *s2) 90 { 91 while (*s1++ = *s2++) 92 ; 93 return (s1-1); 94 } 95 96 /* 97 * Do word expansion. 98 * We built a mini-script in |buff| which takes care of all details, 99 * including stdin/stdout/stderr redirection, WRDE_NOCMD mode and 100 * the word expansion itself. 101 */ 102 int 103 wordexp(const char *word, wordexp_t *wp, int flags) 104 { 105 char *args[10]; 106 wordexp_t wptmp; 107 size_t si; 108 int i; 109 pid_t pid; 110 char *line, *eob, *cp; /* word from shell */ 111 int rv = WRDE_ERRNO; 112 int status; 113 int pv[2]; /* pipe from shell stdout */ 114 FILE *fp; /* pipe read stream */ 115 int serrno, tmpalloc; 116 117 /* 118 * Do absolute minimum necessary for the REUSE flag. Eventually 119 * want to be able to actually avoid excessive malloc calls. 120 */ 121 if (flags & WRDE_REUSE) 122 wordfree(wp); 123 124 /* 125 * Initialize wordexp_t 126 * 127 * XPG requires that the struct pointed to by wp not be modified 128 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE. 129 * So we work with wptmp, and only copy wptmp to wp if one of the 130 * previously mentioned conditions is satisfied. 131 */ 132 wptmp = *wp; 133 134 /* 135 * Man page says: 136 * 2. All of the calls must set WRDE_DOOFFS, or all must not 137 * set it. 138 * Therefore, if it's not set, we_offs will always be reset. 139 */ 140 if ((flags & WRDE_DOOFFS) == 0) 141 wptmp.we_offs = 0; 142 143 /* 144 * If we get APPEND|REUSE, how should we do? 145 * We allocate the buffer anyway to avoid segfault. 146 */ 147 tmpalloc = 0; 148 if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) { 149 wptmp.we_wordc = 0; 150 wptmp.we_wordn = wptmp.we_offs + INITIAL; 151 wptmp.we_wordv = (char **)malloc( 152 sizeof (char *) * wptmp.we_wordn); 153 if (wptmp.we_wordv == NULL) 154 return (WRDE_NOSPACE); 155 wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs; 156 for (si = 0; si < wptmp.we_offs; si++) 157 wptmp.we_wordv[si] = NULL; 158 tmpalloc = 1; 159 } 160 161 /* 162 * Set up pipe from shell stdout to "fp" for us 163 */ 164 if (pipe(pv) < 0) 165 goto cleanup; 166 167 /* 168 * Fork/exec shell 169 */ 170 171 if ((pid = fork()) == -1) { 172 serrno = errno; 173 (void) close(pv[0]); 174 (void) close(pv[1]); 175 errno = serrno; 176 goto cleanup; 177 } 178 179 if (pid == 0) { /* child */ 180 /* 181 * Calculate size of required buffer (which is size of the 182 * input string (|word|) plus all string literals below; 183 * this value MUST be adjusted each time the literals are 184 * changed!!!!). 185 */ 186 size_t bufflen = 124+strlen(word); /* Length of |buff| */ 187 char *buff = alloca(bufflen); 188 char *currbuffp; /* Current position of '\0' in |buff| */ 189 int i; 190 const char *path; 191 192 (void) dup2(pv[1], 1); 193 (void) close(pv[0]); 194 (void) close(pv[1]); 195 196 path = "/usr/bin/ksh93"; 197 i = 0; 198 199 /* Start filling the buffer */ 200 buff[0] = '\0'; 201 currbuffp = buff; 202 203 if (flags & WRDE_UNDEF) 204 currbuffp = mystpcpy(currbuffp, "set -o nounset ; "); 205 if ((flags & WRDE_SHOWERR) == 0) { 206 /* 207 * The newline ('\n') is neccesary to make sure that 208 * the redirection to /dev/null is already active in 209 * the case the printf below contains a syntax 210 * error... 211 */ 212 currbuffp = mystpcpy(currbuffp, "exec 2>/dev/null\n"); 213 } 214 /* Squish stdin */ 215 currbuffp = mystpcpy(currbuffp, "exec 0</dev/null\n"); 216 217 if (flags & WRDE_NOCMD) { 218 /* 219 * Switch to restricted shell (rksh) mode here to 220 * put the word expansion into a "cage" which 221 * prevents users from executing external commands 222 * (outside those listed by ${PATH} (which we set 223 * explicitly to /usr/no/such/path/element/)). 224 */ 225 currbuffp = mystpcpy(currbuffp, "set -o restricted\n"); 226 227 (void) putenv("PATH=/usr/no/such/path/element/"); 228 229 } 230 231 (void) snprintf(currbuffp, bufflen, 232 "print -f \"%%s\\000\" %s", word); 233 234 args[i++] = strrchr(path, '/') + 1; 235 args[i++] = "-c"; 236 args[i++] = buff; 237 args[i++] = NULL; 238 239 (void) execv(path, args); 240 _exit(127); 241 } 242 243 (void) close(pv[1]); 244 245 if ((fp = fdopen(pv[0], "rF")) == NULL) { 246 serrno = errno; 247 (void) close(pv[0]); 248 errno = serrno; 249 goto wait_cleanup; 250 } 251 252 /* 253 * Read words from shell, separated with '\0'. 254 * Since there is no way to disable IFS splitting, 255 * it would be possible to separate the output with '\n'. 256 */ 257 cp = line = malloc(BUFSZ); 258 if (line == NULL) { 259 (void) fclose(fp); 260 rv = WRDE_NOSPACE; 261 goto wait_cleanup; 262 } 263 eob = line + BUFSZ; 264 265 rv = 0; 266 while ((i = getc(fp)) != EOF) { 267 *cp++ = (char)i; 268 if (i == '\0') { 269 cp = line; 270 if ((rv = append(&wptmp, cp)) != 0) { 271 break; 272 } 273 } 274 if (cp == eob) { 275 size_t bs = (eob - line); 276 char *nl; 277 278 if ((nl = realloc(line, bs + BUFSZ)) == NULL) { 279 rv = WRDE_NOSPACE; 280 break; 281 } 282 line = nl; 283 cp = line + bs; 284 eob = cp + BUFSZ; 285 } 286 } 287 288 wptmp.we_wordp[wptmp.we_wordc] = NULL; 289 290 free(line); 291 (void) fclose(fp); /* kill shell if still writing */ 292 293 wait_cleanup: 294 if (waitpid(pid, &status, 0) == -1) 295 rv = WRDE_ERRNO; 296 else if (rv == 0) 297 rv = WEXITSTATUS(status); /* shell WRDE_* status */ 298 299 cleanup: 300 if (rv == 0) 301 *wp = wptmp; 302 else { 303 if (tmpalloc) 304 wordfree(&wptmp); 305 } 306 307 /* 308 * Map ksh errors to wordexp() errors 309 */ 310 if (rv == 4) 311 rv = WRDE_CMDSUB; 312 else if (rv == 5) 313 rv = WRDE_BADVAL; 314 else if (rv == 6) 315 rv = WRDE_SYNTAX; 316 return (rv); 317 } 318 319 #else /* WORDEXP_KSH93 */ 320 321 extern int __xpg4; /* defined in _xpg4.c; 0 if not xpg4-compiled program */ 322 323 /* 324 * Needs no locking if fetched only once. 325 * See getenv()/putenv()/setenv(). 326 */ 327 extern const char **environ; 328 329 /* 330 * Do word expansion. 331 * We just pass our arguments to shell with -E option. Note that the 332 * underlying shell must recognize the -E option, and do the right thing 333 * with it. 334 */ 335 int 336 wordexp(const char *word, wordexp_t *wp, int flags) 337 { 338 char options[9]; 339 char *optendp = options; 340 char *argv[4]; 341 const char *path; 342 wordexp_t wptmp; 343 size_t si; 344 int i; 345 pid_t pid; 346 char *line, *eob, *cp; /* word from shell */ 347 int rv = WRDE_ERRNO; 348 int status; 349 int pv[2]; /* pipe from shell stdout */ 350 FILE *fp; /* pipe read stream */ 351 int tmpalloc; 352 char *wd = NULL; 353 const char **env = NULL; 354 const char **envp; 355 const char *ev; 356 int n; 357 posix_spawnattr_t attr; 358 posix_spawn_file_actions_t fact; 359 int error; 360 361 static const char *sun_path = "/bin/ksh"; 362 static const char *xpg4_path = "/usr/xpg4/bin/sh"; 363 364 /* 365 * Do absolute minimum neccessary for the REUSE flag. Eventually 366 * want to be able to actually avoid excessive malloc calls. 367 */ 368 if (flags & WRDE_REUSE) 369 wordfree(wp); 370 371 /* 372 * Initialize wordexp_t 373 * 374 * XPG requires that the struct pointed to by wp not be modified 375 * unless wordexp() either succeeds, or fails on WRDE_NOSPACE. 376 * So we work with wptmp, and only copy wptmp to wp if one of the 377 * previously mentioned conditions is satisfied. 378 */ 379 wptmp = *wp; 380 381 /* 382 * Man page says: 383 * 2. All of the calls must set WRDE_DOOFFS, or all must not 384 * set it. 385 * Therefore, if it's not set, we_offs will always be reset. 386 */ 387 if ((flags & WRDE_DOOFFS) == 0) 388 wptmp.we_offs = 0; 389 390 /* 391 * If we get APPEND|REUSE, how should we do? 392 * allocating buffer anyway to avoid segfault. 393 */ 394 tmpalloc = 0; 395 if ((flags & WRDE_APPEND) == 0 || (flags & WRDE_REUSE)) { 396 wptmp.we_wordc = 0; 397 wptmp.we_wordn = wptmp.we_offs + INITIAL; 398 wptmp.we_wordv = malloc(sizeof (char *) * wptmp.we_wordn); 399 if (wptmp.we_wordv == NULL) 400 return (WRDE_NOSPACE); 401 wptmp.we_wordp = wptmp.we_wordv + wptmp.we_offs; 402 for (si = 0; si < wptmp.we_offs; si++) 403 wptmp.we_wordv[si] = NULL; 404 tmpalloc = 1; 405 } 406 407 /* 408 * Turn flags into shell options 409 */ 410 *optendp++ = '-'; 411 *optendp++ = (char)0x05; /* ksh -^E */ 412 if (flags & WRDE_UNDEF) 413 *optendp++ = 'u'; 414 if (flags & WRDE_NOCMD) 415 *optendp++ = 'N'; 416 *optendp = '\0'; 417 418 /* 419 * Make sure PWD is in the environment. 420 */ 421 if ((envp = environ) == NULL) { /* can't happen? */ 422 ev = NULL; 423 n = 0; 424 } else { 425 for (n = 0; (ev = envp[n]) != NULL; n++) { 426 if (*ev == 'P' && strncmp(ev, "PWD=", 4) == 0) 427 break; 428 } 429 } 430 if (ev == NULL) { /* PWD missing from the environment */ 431 /* allocate a new environment */ 432 if ((env = malloc((n + 2) * sizeof (char *))) == NULL || 433 (wd = malloc(PATH_MAX + 4)) == NULL) 434 goto cleanup; 435 for (i = 0; i < n; i++) 436 env[i] = envp[i]; 437 (void) strcpy(wd, "PWD="); 438 if (getcwd(&wd[4], PATH_MAX) == NULL) 439 (void) strcpy(&wd[4], "/"); 440 env[i] = wd; 441 env[i + 1] = NULL; 442 envp = env; 443 } 444 445 if ((error = posix_spawnattr_init(&attr)) != 0) { 446 errno = error; 447 goto cleanup; 448 } 449 if ((error = posix_spawn_file_actions_init(&fact)) != 0) { 450 (void) posix_spawnattr_destroy(&attr); 451 errno = error; 452 goto cleanup; 453 } 454 455 /* 456 * Set up pipe from shell stdout to "fp" for us 457 */ 458 if (pipe(pv) < 0) { 459 error = errno; 460 (void) posix_spawnattr_destroy(&attr); 461 (void) posix_spawn_file_actions_destroy(&fact); 462 errno = error; 463 goto cleanup; 464 } 465 466 /* 467 * Spawn shell with -E word 468 */ 469 error = posix_spawnattr_setflags(&attr, 470 POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP); 471 if (error == 0) 472 error = posix_spawn_file_actions_adddup2(&fact, pv[1], 1); 473 if (error == 0 && pv[0] != 1) 474 error = posix_spawn_file_actions_addclose(&fact, pv[0]); 475 if (error == 0 && pv[1] != 1) 476 error = posix_spawn_file_actions_addclose(&fact, pv[1]); 477 if (error == 0 && !(flags & WRDE_SHOWERR)) 478 error = posix_spawn_file_actions_addopen(&fact, 2, 479 "/dev/null", O_WRONLY, 0); 480 path = __xpg4 ? xpg4_path : sun_path; 481 argv[0] = strrchr(path, '/') + 1; 482 argv[1] = options; 483 argv[2] = (char *)word; 484 argv[3] = NULL; 485 if (error == 0) 486 error = posix_spawn(&pid, path, &fact, &attr, 487 (char *const *)argv, (char *const *)envp); 488 (void) posix_spawnattr_destroy(&attr); 489 (void) posix_spawn_file_actions_destroy(&fact); 490 (void) close(pv[1]); 491 if (error) { 492 (void) close(pv[0]); 493 errno = error; 494 goto cleanup; 495 } 496 497 if ((fp = fdopen(pv[0], "rF")) == NULL) { 498 error = errno; 499 (void) close(pv[0]); 500 errno = error; 501 goto wait_cleanup; 502 } 503 504 /* 505 * Read words from shell, separated with '\0'. 506 * Since there is no way to disable IFS splitting, 507 * it would be possible to separate the output with '\n'. 508 */ 509 cp = line = malloc(BUFSZ); 510 if (line == NULL) { 511 error = errno; 512 (void) fclose(fp); 513 errno = error; 514 goto wait_cleanup; 515 } 516 eob = line + BUFSZ; 517 518 rv = 0; 519 while ((i = getc(fp)) != EOF) { 520 *cp++ = (char)i; 521 if (i == '\0') { 522 cp = line; 523 if ((rv = append(&wptmp, cp)) != 0) { 524 break; 525 } 526 } 527 if (cp == eob) { 528 size_t bs = (eob - line); 529 char *nl; 530 531 if ((nl = realloc(line, bs + BUFSZ)) == NULL) { 532 rv = WRDE_NOSPACE; 533 break; 534 } 535 line = nl; 536 cp = line + bs; 537 eob = cp + BUFSZ; 538 } 539 } 540 541 wptmp.we_wordp[wptmp.we_wordc] = NULL; 542 543 free(line); 544 (void) fclose(fp); /* kill shell if still writing */ 545 546 wait_cleanup: 547 while (waitpid(pid, &status, 0) == -1) { 548 if (errno != EINTR) { 549 if (rv == 0) 550 rv = WRDE_ERRNO; 551 break; 552 } 553 } 554 if (rv == 0) 555 rv = WEXITSTATUS(status); /* shell WRDE_* status */ 556 557 cleanup: 558 if (rv == 0) 559 *wp = wptmp; 560 else if (tmpalloc) 561 wordfree(&wptmp); 562 563 if (env) 564 free(env); 565 if (wd) 566 free(wd); 567 /* 568 * Map ksh errors to wordexp() errors 569 */ 570 if (rv == 4) 571 rv = WRDE_CMDSUB; 572 else if (rv == 5) 573 rv = WRDE_BADVAL; 574 else if (rv == 6) 575 rv = WRDE_SYNTAX; 576 return (rv); 577 } 578 579 #endif /* WORDEXP_KSH93 */ 580 581 /* 582 * Append a word to the wordexp_t structure, growing it as necessary. 583 */ 584 static int 585 append(wordexp_t *wp, char *str) 586 { 587 char *cp; 588 char **nwp; 589 590 /* 591 * We will be adding one entry and later adding 592 * one more NULL. So we need 2 more free slots. 593 */ 594 if ((wp->we_wordp + wp->we_wordc) == 595 (wp->we_wordv + wp->we_wordn - 1)) { 596 nwp = realloc(wp->we_wordv, 597 (wp->we_wordn + INITIAL) * sizeof (char *)); 598 if (nwp == NULL) 599 return (WRDE_NOSPACE); 600 wp->we_wordn += INITIAL; 601 wp->we_wordv = nwp; 602 wp->we_wordp = wp->we_wordv + wp->we_offs; 603 } 604 if ((cp = strdup(str)) == NULL) 605 return (WRDE_NOSPACE); 606 wp->we_wordp[wp->we_wordc++] = cp; 607 return (0); 608 } 609 610 /* 611 * Free all space owned by wordexp_t. 612 */ 613 void 614 wordfree(wordexp_t *wp) 615 { 616 size_t i; 617 618 if (wp->we_wordv == NULL) 619 return; 620 for (i = wp->we_offs; i < wp->we_offs + wp->we_wordc; i++) 621 free(wp->we_wordv[i]); 622 free((void *)wp->we_wordv); 623 wp->we_wordc = 0; 624 wp->we_wordv = NULL; 625 } 626