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