1 /*- 2 * Copyright (c) 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1993, 1994, 1995, 1996 5 * Keith Bostic. All rights reserved. 6 * 7 * See the LICENSE file for redistribution information. 8 */ 9 10 #include "config.h" 11 12 #ifndef lint 13 static const char sccsid[] = "@(#)ex_argv.c 10.26 (Berkeley) 9/20/96"; 14 #endif /* not lint */ 15 16 #include <sys/types.h> 17 #include <sys/queue.h> 18 19 #include <bitstring.h> 20 #include <ctype.h> 21 #include <dirent.h> 22 #include <errno.h> 23 #include <limits.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 29 #include "../common/common.h" 30 31 static int argv_alloc __P((SCR *, size_t)); 32 static int argv_comp __P((const void *, const void *)); 33 static int argv_fexp __P((SCR *, EXCMD *, 34 char *, size_t, char *, size_t *, char **, size_t *, int)); 35 static int argv_lexp __P((SCR *, EXCMD *, char *)); 36 static int argv_sexp __P((SCR *, char **, size_t *, size_t *)); 37 38 /* 39 * argv_init -- 40 * Build a prototype arguments list. 41 * 42 * PUBLIC: int argv_init __P((SCR *, EXCMD *)); 43 */ 44 int 45 argv_init(sp, excp) 46 SCR *sp; 47 EXCMD *excp; 48 { 49 EX_PRIVATE *exp; 50 51 exp = EXP(sp); 52 exp->argsoff = 0; 53 argv_alloc(sp, 1); 54 55 excp->argv = exp->args; 56 excp->argc = exp->argsoff; 57 return (0); 58 } 59 60 /* 61 * argv_exp0 -- 62 * Append a string to the argument list. 63 * 64 * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t)); 65 */ 66 int 67 argv_exp0(sp, excp, cmd, cmdlen) 68 SCR *sp; 69 EXCMD *excp; 70 char *cmd; 71 size_t cmdlen; 72 { 73 EX_PRIVATE *exp; 74 75 exp = EXP(sp); 76 argv_alloc(sp, cmdlen); 77 memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen); 78 exp->args[exp->argsoff]->bp[cmdlen] = '\0'; 79 exp->args[exp->argsoff]->len = cmdlen; 80 ++exp->argsoff; 81 excp->argv = exp->args; 82 excp->argc = exp->argsoff; 83 return (0); 84 } 85 86 /* 87 * argv_exp1 -- 88 * Do file name expansion on a string, and append it to the 89 * argument list. 90 * 91 * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int)); 92 */ 93 int 94 argv_exp1(sp, excp, cmd, cmdlen, is_bang) 95 SCR *sp; 96 EXCMD *excp; 97 char *cmd; 98 size_t cmdlen; 99 int is_bang; 100 { 101 EX_PRIVATE *exp; 102 size_t blen, len; 103 char *bp, *p, *t; 104 105 GET_SPACE_RET(sp, bp, blen, 512); 106 107 len = 0; 108 exp = EXP(sp); 109 if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { 110 FREE_SPACE(sp, bp, blen); 111 return (1); 112 } 113 114 /* If it's empty, we're done. */ 115 if (len != 0) { 116 for (p = bp, t = bp + len; p < t; ++p) 117 if (!isblank(*p)) 118 break; 119 if (p == t) 120 goto ret; 121 } else 122 goto ret; 123 124 (void)argv_exp0(sp, excp, bp, len); 125 126 ret: FREE_SPACE(sp, bp, blen); 127 return (0); 128 } 129 130 /* 131 * argv_exp2 -- 132 * Do file name and shell expansion on a string, and append it to 133 * the argument list. 134 * 135 * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t)); 136 */ 137 int 138 argv_exp2(sp, excp, cmd, cmdlen) 139 SCR *sp; 140 EXCMD *excp; 141 char *cmd; 142 size_t cmdlen; 143 { 144 size_t blen, len, n; 145 int rval; 146 char *bp, *mp, *p; 147 148 GET_SPACE_RET(sp, bp, blen, 512); 149 150 #define SHELLECHO "echo " 151 #define SHELLOFFSET (sizeof(SHELLECHO) - 1) 152 memcpy(bp, SHELLECHO, SHELLOFFSET); 153 p = bp + SHELLOFFSET; 154 len = SHELLOFFSET; 155 156 #if defined(DEBUG) && 0 157 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); 158 #endif 159 160 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { 161 rval = 1; 162 goto err; 163 } 164 165 #if defined(DEBUG) && 0 166 TRACE(sp, "before shell: %d: {%s}\n", len, bp); 167 #endif 168 169 /* 170 * Do shell word expansion -- it's very, very hard to figure out what 171 * magic characters the user's shell expects. Historically, it was a 172 * union of v7 shell and csh meta characters. We match that practice 173 * by default, so ":read \%" tries to read a file named '%'. It would 174 * make more sense to pass any special characters through the shell, 175 * but then, if your shell was csh, the above example will behave 176 * differently in nvi than in vi. If you want to get other characters 177 * passed through to your shell, change the "meta" option. 178 * 179 * To avoid a function call per character, we do a first pass through 180 * the meta characters looking for characters that aren't expected 181 * to be there, and then we can ignore them in the user's argument. 182 */ 183 if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) 184 n = 0; 185 else { 186 for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p) 187 if (isblank(*p) || isalnum(*p)) 188 break; 189 p = bp + SHELLOFFSET; 190 n = len - SHELLOFFSET; 191 if (*p != '\0') { 192 for (; n > 0; --n, ++p) 193 if (strchr(mp, *p) != NULL) 194 break; 195 } else 196 for (; n > 0; --n, ++p) 197 if (!isblank(*p) && 198 !isalnum(*p) && strchr(mp, *p) != NULL) 199 break; 200 } 201 202 /* 203 * If we found a meta character in the string, fork a shell to expand 204 * it. Unfortunately, this is comparatively slow. Historically, it 205 * didn't matter much, since users don't enter meta characters as part 206 * of pathnames that frequently. The addition of filename completion 207 * broke that assumption because it's easy to use. As a result, lots 208 * folks have complained that the expansion code is too slow. So, we 209 * detect filename completion as a special case, and do it internally. 210 * Note that this code assumes that the <asterisk> character is the 211 * match-anything meta character. That feels safe -- if anyone writes 212 * a shell that doesn't follow that convention, I'd suggest giving them 213 * a festive hot-lead enema. 214 */ 215 switch (n) { 216 case 0: 217 p = bp + SHELLOFFSET; 218 len -= SHELLOFFSET; 219 rval = argv_exp3(sp, excp, p, len); 220 break; 221 case 1: 222 if (*p == '*') { 223 *p = '\0'; 224 rval = argv_lexp(sp, excp, bp + SHELLOFFSET); 225 break; 226 } 227 /* FALLTHROUGH */ 228 default: 229 if (argv_sexp(sp, &bp, &blen, &len)) { 230 rval = 1; 231 goto err; 232 } 233 p = bp; 234 rval = argv_exp3(sp, excp, p, len); 235 break; 236 } 237 238 err: FREE_SPACE(sp, bp, blen); 239 return (rval); 240 } 241 242 /* 243 * argv_exp3 -- 244 * Take a string and break it up into an argv, which is appended 245 * to the argument list. 246 * 247 * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t)); 248 */ 249 int 250 argv_exp3(sp, excp, cmd, cmdlen) 251 SCR *sp; 252 EXCMD *excp; 253 char *cmd; 254 size_t cmdlen; 255 { 256 EX_PRIVATE *exp; 257 size_t len; 258 int ch, off; 259 char *ap, *p; 260 261 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { 262 /* Skip any leading whitespace. */ 263 for (; cmdlen > 0; --cmdlen, ++cmd) { 264 ch = *cmd; 265 if (!isblank(ch)) 266 break; 267 } 268 if (cmdlen == 0) 269 break; 270 271 /* 272 * Determine the length of this whitespace delimited 273 * argument. 274 * 275 * QUOTING NOTE: 276 * 277 * Skip any character preceded by the user's quoting 278 * character. 279 */ 280 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { 281 ch = *cmd; 282 if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { 283 ++cmd; 284 --cmdlen; 285 } else if (isblank(ch)) 286 break; 287 } 288 289 /* 290 * Copy the argument into place. 291 * 292 * QUOTING NOTE: 293 * 294 * Lose quote chars. 295 */ 296 argv_alloc(sp, len); 297 off = exp->argsoff; 298 exp->args[off]->len = len; 299 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) 300 if (IS_ESCAPE(sp, excp, *ap)) 301 ++ap; 302 *p = '\0'; 303 } 304 excp->argv = exp->args; 305 excp->argc = exp->argsoff; 306 307 #if defined(DEBUG) && 0 308 for (cnt = 0; cnt < exp->argsoff; ++cnt) 309 TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); 310 #endif 311 return (0); 312 } 313 314 /* 315 * argv_fexp -- 316 * Do file name and bang command expansion. 317 */ 318 static int 319 argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) 320 SCR *sp; 321 EXCMD *excp; 322 char *cmd, *p, **bpp; 323 size_t cmdlen, *lenp, *blenp; 324 int is_bang; 325 { 326 EX_PRIVATE *exp; 327 char *bp, *t; 328 size_t blen, len, off, tlen; 329 330 /* Replace file name characters. */ 331 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) 332 switch (*cmd) { 333 case '!': 334 if (!is_bang) 335 goto ins_ch; 336 exp = EXP(sp); 337 if (exp->lastbcomm == NULL) { 338 msgq(sp, M_ERR, 339 "115|No previous command to replace \"!\""); 340 return (1); 341 } 342 len += tlen = strlen(exp->lastbcomm); 343 off = p - bp; 344 ADD_SPACE_RET(sp, bp, blen, len); 345 p = bp + off; 346 memcpy(p, exp->lastbcomm, tlen); 347 p += tlen; 348 F_SET(excp, E_MODIFY); 349 break; 350 case '%': 351 if ((t = sp->frp->name) == NULL) { 352 msgq(sp, M_ERR, 353 "116|No filename to substitute for %%"); 354 return (1); 355 } 356 tlen = strlen(t); 357 len += tlen; 358 off = p - bp; 359 ADD_SPACE_RET(sp, bp, blen, len); 360 p = bp + off; 361 memcpy(p, t, tlen); 362 p += tlen; 363 F_SET(excp, E_MODIFY); 364 break; 365 case '#': 366 if ((t = sp->alt_name) == NULL) { 367 msgq(sp, M_ERR, 368 "117|No filename to substitute for #"); 369 return (1); 370 } 371 len += tlen = strlen(t); 372 off = p - bp; 373 ADD_SPACE_RET(sp, bp, blen, len); 374 p = bp + off; 375 memcpy(p, t, tlen); 376 p += tlen; 377 F_SET(excp, E_MODIFY); 378 break; 379 case '\\': 380 /* 381 * QUOTING NOTE: 382 * 383 * Strip any backslashes that protected the file 384 * expansion characters. 385 */ 386 if (cmdlen > 1 && 387 (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { 388 ++cmd; 389 --cmdlen; 390 } 391 /* FALLTHROUGH */ 392 default: 393 ins_ch: ++len; 394 off = p - bp; 395 ADD_SPACE_RET(sp, bp, blen, len); 396 p = bp + off; 397 *p++ = *cmd; 398 } 399 400 /* Nul termination. */ 401 ++len; 402 off = p - bp; 403 ADD_SPACE_RET(sp, bp, blen, len); 404 p = bp + off; 405 *p = '\0'; 406 407 /* Return the new string length, buffer, buffer length. */ 408 *lenp = len - 1; 409 *bpp = bp; 410 *blenp = blen; 411 return (0); 412 } 413 414 /* 415 * argv_alloc -- 416 * Make more space for arguments. 417 */ 418 static int 419 argv_alloc(sp, len) 420 SCR *sp; 421 size_t len; 422 { 423 ARGS *ap; 424 EX_PRIVATE *exp; 425 int cnt, off; 426 427 /* 428 * Allocate room for another argument, always leaving 429 * enough room for an ARGS structure with a length of 0. 430 */ 431 #define INCREMENT 20 432 exp = EXP(sp); 433 off = exp->argsoff; 434 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { 435 cnt = exp->argscnt + INCREMENT; 436 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *)); 437 if (exp->args == NULL) { 438 (void)argv_free(sp); 439 goto mem; 440 } 441 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); 442 exp->argscnt = cnt; 443 } 444 445 /* First argument. */ 446 if (exp->args[off] == NULL) { 447 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS)); 448 if (exp->args[off] == NULL) 449 goto mem; 450 } 451 452 /* First argument buffer. */ 453 ap = exp->args[off]; 454 ap->len = 0; 455 if (ap->blen < len + 1) { 456 ap->blen = len + 1; 457 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T)); 458 if (ap->bp == NULL) { 459 ap->bp = NULL; 460 ap->blen = 0; 461 F_CLR(ap, A_ALLOCATED); 462 mem: msgq(sp, M_SYSERR, NULL); 463 return (1); 464 } 465 F_SET(ap, A_ALLOCATED); 466 } 467 468 /* Second argument. */ 469 if (exp->args[++off] == NULL) { 470 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS)); 471 if (exp->args[off] == NULL) 472 goto mem; 473 } 474 /* 0 length serves as end-of-argument marker. */ 475 exp->args[off]->len = 0; 476 return (0); 477 } 478 479 /* 480 * argv_free -- 481 * Free up argument structures. 482 * 483 * PUBLIC: int argv_free __P((SCR *)); 484 */ 485 int 486 argv_free(sp) 487 SCR *sp; 488 { 489 EX_PRIVATE *exp; 490 int off; 491 492 exp = EXP(sp); 493 if (exp->args != NULL) { 494 for (off = 0; off < exp->argscnt; ++off) { 495 if (exp->args[off] == NULL) 496 continue; 497 if (F_ISSET(exp->args[off], A_ALLOCATED)) 498 free(exp->args[off]->bp); 499 free(exp->args[off]); 500 } 501 free(exp->args); 502 } 503 exp->args = NULL; 504 exp->argscnt = 0; 505 exp->argsoff = 0; 506 return (0); 507 } 508 509 /* 510 * argv_lexp -- 511 * Find all file names matching the prefix and append them to the 512 * buffer. 513 */ 514 static int 515 argv_lexp(sp, excp, path) 516 SCR *sp; 517 EXCMD *excp; 518 char *path; 519 { 520 struct dirent *dp; 521 DIR *dirp; 522 EX_PRIVATE *exp; 523 int off; 524 size_t dlen, len, nlen; 525 char *dname, *name, *p; 526 527 exp = EXP(sp); 528 529 /* Set up the name and length for comparison. */ 530 if ((p = strrchr(path, '/')) == NULL) { 531 dname = "."; 532 dlen = 0; 533 name = path; 534 } else { 535 if (p == path) { 536 dname = "/"; 537 dlen = 1; 538 } else { 539 *p = '\0'; 540 dname = path; 541 dlen = strlen(path); 542 } 543 name = p + 1; 544 } 545 nlen = strlen(name); 546 547 /* 548 * XXX 549 * We don't use the d_namlen field, it's not portable enough; we 550 * assume that d_name is nul terminated, instead. 551 */ 552 if ((dirp = opendir(dname)) == NULL) { 553 msgq_str(sp, M_SYSERR, dname, "%s"); 554 return (1); 555 } 556 for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { 557 if (nlen == 0) { 558 if (dp->d_name[0] == '.') 559 continue; 560 len = strlen(dp->d_name); 561 } else { 562 len = strlen(dp->d_name); 563 if (len < nlen || memcmp(dp->d_name, name, nlen)) 564 continue; 565 } 566 567 /* Directory + name + slash + null. */ 568 argv_alloc(sp, dlen + len + 2); 569 p = exp->args[exp->argsoff]->bp; 570 if (dlen != 0) { 571 memcpy(p, dname, dlen); 572 p += dlen; 573 if (dlen > 1 || dname[0] != '/') 574 *p++ = '/'; 575 } 576 memcpy(p, dp->d_name, len + 1); 577 exp->args[exp->argsoff]->len = dlen + len + 1; 578 ++exp->argsoff; 579 excp->argv = exp->args; 580 excp->argc = exp->argsoff; 581 } 582 closedir(dirp); 583 584 if (off == exp->argsoff) { 585 /* 586 * If we didn't find a match, complain that the expansion 587 * failed. We can't know for certain that's the error, but 588 * it's a good guess, and it matches historic practice. 589 */ 590 msgq(sp, M_ERR, "304|Shell expansion failed"); 591 return (1); 592 } 593 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); 594 return (0); 595 } 596 597 /* 598 * argv_comp -- 599 * Alphabetic comparison. 600 */ 601 static int 602 argv_comp(a, b) 603 const void *a, *b; 604 { 605 return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp)); 606 } 607 608 /* 609 * argv_sexp -- 610 * Fork a shell, pipe a command through it, and read the output into 611 * a buffer. 612 */ 613 static int 614 argv_sexp(sp, bpp, blenp, lenp) 615 SCR *sp; 616 char **bpp; 617 size_t *blenp, *lenp; 618 { 619 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; 620 FILE *ifp; 621 pid_t pid; 622 size_t blen, len; 623 int ch, std_output[2]; 624 char *bp, *p, *sh, *sh_path; 625 626 /* Secure means no shell access. */ 627 if (O_ISSET(sp, O_SECURE)) { 628 msgq(sp, M_ERR, 629 "289|Shell expansions not supported when the secure edit option is set"); 630 return (1); 631 } 632 633 sh_path = O_STR(sp, O_SHELL); 634 if ((sh = strrchr(sh_path, '/')) == NULL) 635 sh = sh_path; 636 else 637 ++sh; 638 639 /* Local copies of the buffer variables. */ 640 bp = *bpp; 641 blen = *blenp; 642 643 /* 644 * There are two different processes running through this code, named 645 * the utility (the shell) and the parent. The utility reads standard 646 * input and writes standard output and standard error output. The 647 * parent writes to the utility, reads its standard output and ignores 648 * its standard error output. Historically, the standard error output 649 * was discarded by vi, as it produces a lot of noise when file patterns 650 * don't match. 651 * 652 * The parent reads std_output[0], and the utility writes std_output[1]. 653 */ 654 ifp = NULL; 655 std_output[0] = std_output[1] = -1; 656 if (pipe(std_output) < 0) { 657 msgq(sp, M_SYSERR, "pipe"); 658 return (1); 659 } 660 if ((ifp = fdopen(std_output[0], "r")) == NULL) { 661 msgq(sp, M_SYSERR, "fdopen"); 662 goto err; 663 } 664 665 /* 666 * Do the minimal amount of work possible, the shell is going to run 667 * briefly and then exit. We sincerely hope. 668 */ 669 switch (pid = vfork()) { 670 case -1: /* Error. */ 671 msgq(sp, M_SYSERR, "vfork"); 672 err: if (ifp != NULL) 673 (void)fclose(ifp); 674 else if (std_output[0] != -1) 675 close(std_output[0]); 676 if (std_output[1] != -1) 677 close(std_output[0]); 678 return (1); 679 case 0: /* Utility. */ 680 /* Redirect stdout to the write end of the pipe. */ 681 (void)dup2(std_output[1], STDOUT_FILENO); 682 683 /* Close the utility's file descriptors. */ 684 (void)close(std_output[0]); 685 (void)close(std_output[1]); 686 (void)close(STDERR_FILENO); 687 688 /* 689 * XXX 690 * Assume that all shells have -c. 691 */ 692 execl(sh_path, sh, "-c", bp, NULL); 693 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); 694 _exit(127); 695 default: /* Parent. */ 696 /* Close the pipe ends the parent won't use. */ 697 (void)close(std_output[1]); 698 break; 699 } 700 701 /* 702 * Copy process standard output into a buffer. 703 * 704 * !!! 705 * Historic vi apparently discarded leading \n and \r's from 706 * the shell output stream. We don't on the grounds that any 707 * shell that does that is broken. 708 */ 709 for (p = bp, len = 0, ch = EOF; 710 (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len) 711 if (blen < 5) { 712 ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2); 713 p = bp + len; 714 blen = *blenp - len; 715 } 716 717 /* Delete the final newline, nul terminate the string. */ 718 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { 719 --p; 720 --len; 721 } 722 *p = '\0'; 723 *lenp = len; 724 *bpp = bp; /* *blenp is already updated. */ 725 726 if (ferror(ifp)) 727 goto ioerr; 728 if (fclose(ifp)) { 729 ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s"); 730 alloc_err: rval = SEXP_ERR; 731 } else 732 rval = SEXP_OK; 733 734 /* 735 * Wait for the process. If the shell process fails (e.g., "echo $q" 736 * where q wasn't a defined variable) or if the returned string has 737 * no characters or only blank characters, (e.g., "echo $5"), complain 738 * that the shell expansion failed. We can't know for certain that's 739 * the error, but it's a good guess, and it matches historic practice. 740 * This won't catch "echo foo_$5", but that's not a common error and 741 * historic vi didn't catch it either. 742 */ 743 if (proc_wait(sp, (long)pid, sh, 1, 0)) 744 rval = SEXP_EXPANSION_ERR; 745 746 for (p = bp; len; ++p, --len) 747 if (!isblank(*p)) 748 break; 749 if (len == 0) 750 rval = SEXP_EXPANSION_ERR; 751 752 if (rval == SEXP_EXPANSION_ERR) 753 msgq(sp, M_ERR, "304|Shell expansion failed"); 754 755 return (rval == SEXP_OK ? 0 : 1); 756 } 757