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 #include <sys/types.h> 13 #include <sys/queue.h> 14 #include <sys/time.h> 15 16 #include <bitstring.h> 17 #include <ctype.h> 18 #include <dirent.h> 19 #include <errno.h> 20 #include <limits.h> 21 #include <pwd.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include "../common/common.h" 28 29 static int argv_alloc(SCR *, size_t); 30 static int argv_comp(const void *, const void *); 31 static int argv_fexp(SCR *, EXCMD *, 32 CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int); 33 static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *); 34 static int argv_flt_user(SCR *, EXCMD *, CHAR_T *, size_t); 35 36 /* 37 * argv_init -- 38 * Build a prototype arguments list. 39 * 40 * PUBLIC: int argv_init(SCR *, EXCMD *); 41 */ 42 int 43 argv_init(SCR *sp, EXCMD *excp) 44 { 45 EX_PRIVATE *exp; 46 47 exp = EXP(sp); 48 exp->argsoff = 0; 49 argv_alloc(sp, 1); 50 51 excp->argv = exp->args; 52 excp->argc = exp->argsoff; 53 return (0); 54 } 55 56 /* 57 * argv_exp0 -- 58 * Append a string to the argument list. 59 * 60 * PUBLIC: int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t); 61 */ 62 int 63 argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) 64 { 65 EX_PRIVATE *exp; 66 67 exp = EXP(sp); 68 argv_alloc(sp, cmdlen); 69 MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen); 70 exp->args[exp->argsoff]->bp[cmdlen] = '\0'; 71 exp->args[exp->argsoff]->len = cmdlen; 72 ++exp->argsoff; 73 excp->argv = exp->args; 74 excp->argc = exp->argsoff; 75 return (0); 76 } 77 78 /* 79 * argv_exp1 -- 80 * Do file name expansion on a string, and append it to the 81 * argument list. 82 * 83 * PUBLIC: int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int); 84 */ 85 int 86 argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang) 87 { 88 EX_PRIVATE *exp; 89 size_t blen, len; 90 CHAR_T *p, *t, *bp; 91 92 GET_SPACE_RETW(sp, bp, blen, 512); 93 94 len = 0; 95 exp = EXP(sp); 96 if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { 97 FREE_SPACEW(sp, bp, blen); 98 return (1); 99 } 100 101 /* If it's empty, we're done. */ 102 if (len != 0) { 103 for (p = bp, t = bp + len; p < t; ++p) 104 if (!cmdskip(*p)) 105 break; 106 if (p == t) 107 goto ret; 108 } else 109 goto ret; 110 111 (void)argv_exp0(sp, excp, bp, len); 112 113 ret: FREE_SPACEW(sp, bp, blen); 114 return (0); 115 } 116 117 /* 118 * argv_exp2 -- 119 * Do file name and shell expansion on a string, and append it to 120 * the argument list. 121 * 122 * PUBLIC: int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t); 123 */ 124 int 125 argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) 126 { 127 size_t blen, len, n; 128 int rval; 129 CHAR_T *bp, *p; 130 131 GET_SPACE_RETW(sp, bp, blen, 512); 132 133 #define SHELLECHO L("echo ") 134 #define SHELLOFFSET (SIZE(SHELLECHO) - 1) 135 MEMCPY(bp, SHELLECHO, SHELLOFFSET); 136 p = bp + SHELLOFFSET; 137 len = SHELLOFFSET; 138 139 #if defined(DEBUG) && 0 140 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); 141 #endif 142 143 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) { 144 rval = 1; 145 goto err; 146 } 147 148 #if defined(DEBUG) && 0 149 TRACE(sp, "before shell: %d: {%s}\n", len, bp); 150 #endif 151 152 /* 153 * Do shell word expansion -- it's very, very hard to figure out what 154 * magic characters the user's shell expects. Historically, it was a 155 * union of v7 shell and csh meta characters. We match that practice 156 * by default, so ":read \%" tries to read a file named '%'. It would 157 * make more sense to pass any special characters through the shell, 158 * but then, if your shell was csh, the above example will behave 159 * differently in nvi than in vi. If you want to get other characters 160 * passed through to your shell, change the "meta" option. 161 */ 162 if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) 163 n = 0; 164 else { 165 p = bp + SHELLOFFSET; 166 n = len - SHELLOFFSET; 167 for (; n > 0; --n, ++p) 168 if (IS_SHELLMETA(sp, *p)) 169 break; 170 } 171 172 /* 173 * If we found a meta character in the string, fork a shell to expand 174 * it. Unfortunately, this is comparatively slow. Historically, it 175 * didn't matter much, since users don't enter meta characters as part 176 * of pathnames that frequently. The addition of filename completion 177 * broke that assumption because it's easy to use. To increase the 178 * completion performance, nvi used to have an internal routine to 179 * handle "filename*". However, the shell special characters does not 180 * limit to "shellmeta", so such a hack breaks historic practice. 181 * After it all, we split the completion logic out from here. 182 */ 183 switch (n) { 184 case 0: 185 p = bp + SHELLOFFSET; 186 len -= SHELLOFFSET; 187 rval = argv_exp3(sp, excp, p, len); 188 break; 189 default: 190 if (argv_sexp(sp, &bp, &blen, &len)) { 191 rval = 1; 192 goto err; 193 } 194 p = bp; 195 rval = argv_exp3(sp, excp, p, len); 196 break; 197 } 198 199 err: FREE_SPACEW(sp, bp, blen); 200 return (rval); 201 } 202 203 /* 204 * argv_exp3 -- 205 * Take a string and break it up into an argv, which is appended 206 * to the argument list. 207 * 208 * PUBLIC: int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t); 209 */ 210 int 211 argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) 212 { 213 EX_PRIVATE *exp; 214 size_t len; 215 int ch, off; 216 CHAR_T *ap, *p; 217 218 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { 219 /* Skip any leading whitespace. */ 220 for (; cmdlen > 0; --cmdlen, ++cmd) { 221 ch = *cmd; 222 if (!cmdskip(ch)) 223 break; 224 } 225 if (cmdlen == 0) 226 break; 227 228 /* 229 * Determine the length of this whitespace delimited 230 * argument. 231 * 232 * QUOTING NOTE: 233 * 234 * Skip any character preceded by the user's quoting 235 * character. 236 */ 237 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { 238 ch = *cmd; 239 if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) { 240 ++cmd; 241 --cmdlen; 242 } else if (cmdskip(ch)) 243 break; 244 } 245 246 /* 247 * Copy the argument into place. 248 * 249 * QUOTING NOTE: 250 * 251 * Lose quote chars. 252 */ 253 argv_alloc(sp, len); 254 off = exp->argsoff; 255 exp->args[off]->len = len; 256 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) 257 if (IS_ESCAPE(sp, excp, *ap)) 258 ++ap; 259 *p = '\0'; 260 } 261 excp->argv = exp->args; 262 excp->argc = exp->argsoff; 263 264 #if defined(DEBUG) && 0 265 for (cnt = 0; cnt < exp->argsoff; ++cnt) 266 TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); 267 #endif 268 return (0); 269 } 270 271 /* 272 * argv_flt_ex -- 273 * Filter the ex commands with a prefix, and append the results to 274 * the argument list. 275 * 276 * PUBLIC: int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t); 277 */ 278 int 279 argv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen) 280 { 281 EX_PRIVATE *exp; 282 EXCMDLIST const *cp; 283 int off; 284 size_t len; 285 286 exp = EXP(sp); 287 288 for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) { 289 len = STRLEN(cp->name); 290 if (cmdlen > 0 && 291 (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen))) 292 continue; 293 294 /* Copy the matched ex command name. */ 295 argv_alloc(sp, len + 1); 296 MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1); 297 exp->args[exp->argsoff]->len = len; 298 ++exp->argsoff; 299 excp->argv = exp->args; 300 excp->argc = exp->argsoff; 301 } 302 303 return (0); 304 } 305 306 /* 307 * argv_flt_user -- 308 * Filter the ~user list on the system with a prefix, and append 309 * the results to the argument list. 310 */ 311 static int 312 argv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen) 313 { 314 EX_PRIVATE *exp; 315 struct passwd *pw; 316 int off; 317 char *np; 318 size_t len, nlen; 319 320 exp = EXP(sp); 321 off = exp->argsoff; 322 323 /* The input must come with a leading '~'. */ 324 INT2CHAR(sp, uname + 1, ulen - 1, np, nlen); 325 if ((np = v_strdup(sp, np, nlen)) == NULL) 326 return (1); 327 328 setpwent(); 329 while ((pw = getpwent()) != NULL) { 330 len = strlen(pw->pw_name); 331 if (nlen > 0 && 332 (nlen > len || memcmp(np, pw->pw_name, nlen))) 333 continue; 334 335 /* Copy '~' + the matched user name. */ 336 CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen); 337 argv_alloc(sp, ulen + 1); 338 exp->args[exp->argsoff]->bp[0] = '~'; 339 MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen); 340 exp->args[exp->argsoff]->len = ulen; 341 ++exp->argsoff; 342 excp->argv = exp->args; 343 excp->argc = exp->argsoff; 344 } 345 endpwent(); 346 free(np); 347 348 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); 349 return (0); 350 } 351 352 /* 353 * argv_fexp -- 354 * Do file name and bang command expansion. 355 */ 356 static int 357 argv_fexp(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang) 358 { 359 EX_PRIVATE *exp; 360 char *t; 361 size_t blen, len, off, tlen; 362 CHAR_T *bp; 363 CHAR_T *wp; 364 size_t wlen; 365 366 /* Replace file name characters. */ 367 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) 368 switch (*cmd) { 369 case '!': 370 if (!is_bang) 371 goto ins_ch; 372 exp = EXP(sp); 373 if (exp->lastbcomm == NULL) { 374 msgq(sp, M_ERR, 375 "115|No previous command to replace \"!\""); 376 return (1); 377 } 378 len += tlen = STRLEN(exp->lastbcomm); 379 off = p - bp; 380 ADD_SPACE_RETW(sp, bp, blen, len); 381 p = bp + off; 382 MEMCPY(p, exp->lastbcomm, tlen); 383 p += tlen; 384 F_SET(excp, E_MODIFY); 385 break; 386 case '%': 387 if ((t = sp->frp->name) == NULL) { 388 msgq(sp, M_ERR, 389 "116|No filename to substitute for %%"); 390 return (1); 391 } 392 tlen = strlen(t); 393 len += tlen; 394 off = p - bp; 395 ADD_SPACE_RETW(sp, bp, blen, len); 396 p = bp + off; 397 CHAR2INT(sp, t, tlen, wp, wlen); 398 MEMCPY(p, wp, wlen); 399 p += wlen; 400 F_SET(excp, E_MODIFY); 401 break; 402 case '#': 403 if ((t = sp->alt_name) == NULL) { 404 msgq(sp, M_ERR, 405 "117|No filename to substitute for #"); 406 return (1); 407 } 408 len += tlen = strlen(t); 409 off = p - bp; 410 ADD_SPACE_RETW(sp, bp, blen, len); 411 p = bp + off; 412 CHAR2INT(sp, t, tlen, wp, wlen); 413 MEMCPY(p, wp, wlen); 414 p += wlen; 415 F_SET(excp, E_MODIFY); 416 break; 417 case '\\': 418 /* 419 * QUOTING NOTE: 420 * 421 * Strip any backslashes that protected the file 422 * expansion characters. 423 */ 424 if (cmdlen > 1 && 425 (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) { 426 ++cmd; 427 --cmdlen; 428 } 429 /* FALLTHROUGH */ 430 default: 431 ins_ch: ++len; 432 off = p - bp; 433 ADD_SPACE_RETW(sp, bp, blen, len); 434 p = bp + off; 435 *p++ = *cmd; 436 } 437 438 /* Nul termination. */ 439 ++len; 440 off = p - bp; 441 ADD_SPACE_RETW(sp, bp, blen, len); 442 p = bp + off; 443 *p = '\0'; 444 445 /* Return the new string length, buffer, buffer length. */ 446 *lenp = len - 1; 447 *bpp = bp; 448 *blenp = blen; 449 return (0); 450 } 451 452 /* 453 * argv_alloc -- 454 * Make more space for arguments. 455 */ 456 static int 457 argv_alloc(SCR *sp, size_t len) 458 { 459 ARGS *ap; 460 EX_PRIVATE *exp; 461 int cnt, off; 462 463 /* 464 * Allocate room for another argument, always leaving 465 * enough room for an ARGS structure with a length of 0. 466 */ 467 #define INCREMENT 20 468 exp = EXP(sp); 469 off = exp->argsoff; 470 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { 471 cnt = exp->argscnt + INCREMENT; 472 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *)); 473 if (exp->args == NULL) { 474 (void)argv_free(sp); 475 goto mem; 476 } 477 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *)); 478 exp->argscnt = cnt; 479 } 480 481 /* First argument. */ 482 if (exp->args[off] == NULL) { 483 CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); 484 if (exp->args[off] == NULL) 485 goto mem; 486 } 487 488 /* First argument buffer. */ 489 ap = exp->args[off]; 490 ap->len = 0; 491 if (ap->blen < len + 1) { 492 ap->blen = len + 1; 493 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T)); 494 if (ap->bp == NULL) { 495 ap->bp = NULL; 496 ap->blen = 0; 497 F_CLR(ap, A_ALLOCATED); 498 mem: msgq(sp, M_SYSERR, NULL); 499 return (1); 500 } 501 F_SET(ap, A_ALLOCATED); 502 } 503 504 /* Second argument. */ 505 if (exp->args[++off] == NULL) { 506 CALLOC(sp, exp->args[off], 1, sizeof(ARGS)); 507 if (exp->args[off] == NULL) 508 goto mem; 509 } 510 /* 0 length serves as end-of-argument marker. */ 511 exp->args[off]->len = 0; 512 return (0); 513 } 514 515 /* 516 * argv_free -- 517 * Free up argument structures. 518 * 519 * PUBLIC: int argv_free(SCR *); 520 */ 521 int 522 argv_free(SCR *sp) 523 { 524 EX_PRIVATE *exp; 525 int off; 526 527 exp = EXP(sp); 528 if (exp->args != NULL) { 529 for (off = 0; off < exp->argscnt; ++off) { 530 if (exp->args[off] == NULL) 531 continue; 532 if (F_ISSET(exp->args[off], A_ALLOCATED)) 533 free(exp->args[off]->bp); 534 free(exp->args[off]); 535 } 536 free(exp->args); 537 } 538 exp->args = NULL; 539 exp->argscnt = 0; 540 exp->argsoff = 0; 541 return (0); 542 } 543 544 /* 545 * argv_flt_path -- 546 * Find all file names matching the prefix and append them to the 547 * argument list. 548 * 549 * PUBLIC: int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t); 550 */ 551 int 552 argv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen) 553 { 554 struct dirent *dp; 555 DIR *dirp; 556 EX_PRIVATE *exp; 557 int off; 558 size_t dlen, len, nlen; 559 CHAR_T *dname; 560 CHAR_T *p, *np, *n; 561 char *name, *tp, *epd = NULL; 562 CHAR_T *wp; 563 size_t wlen; 564 565 exp = EXP(sp); 566 567 /* Set up the name and length for comparison. */ 568 if ((path = v_wstrdup(sp, path, plen)) == NULL) 569 return (1); 570 if ((p = STRRCHR(path, '/')) == NULL) { 571 if (*path == '~') { 572 int rc; 573 574 /* Filter ~user list instead. */ 575 rc = argv_flt_user(sp, excp, path, plen); 576 free(path); 577 return (rc); 578 } 579 dname = L("."); 580 dlen = 0; 581 np = path; 582 } else { 583 if (p == path) { 584 dname = L("/"); 585 dlen = 1; 586 } else { 587 *p = '\0'; 588 dname = path; 589 dlen = p - path; 590 } 591 np = p + 1; 592 } 593 594 INT2CHAR(sp, dname, dlen + 1, tp, nlen); 595 if ((epd = expanduser(tp)) != NULL) 596 tp = epd; 597 if ((dirp = opendir(tp)) == NULL) { 598 free(epd); 599 free(path); 600 return (1); 601 } 602 free(epd); 603 604 INT2CHAR(sp, np, STRLEN(np), tp, nlen); 605 if ((name = v_strdup(sp, tp, nlen)) == NULL) { 606 free(path); 607 return (1); 608 } 609 610 for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { 611 if (nlen == 0) { 612 if (dp->d_name[0] == '.') 613 continue; 614 #ifdef HAVE_DIRENT_D_NAMLEN 615 len = dp->d_namlen; 616 #else 617 len = strlen(dp->d_name); 618 #endif 619 } else { 620 #ifdef HAVE_DIRENT_D_NAMLEN 621 len = dp->d_namlen; 622 #else 623 len = strlen(dp->d_name); 624 #endif 625 if (len < nlen || memcmp(dp->d_name, name, nlen)) 626 continue; 627 } 628 629 /* Directory + name + slash + null. */ 630 CHAR2INT(sp, dp->d_name, len + 1, wp, wlen); 631 argv_alloc(sp, dlen + wlen + 1); 632 n = exp->args[exp->argsoff]->bp; 633 if (dlen != 0) { 634 MEMCPY(n, dname, dlen); 635 n += dlen; 636 if (dlen > 1 || dname[0] != '/') 637 *n++ = '/'; 638 exp->args[exp->argsoff]->len = dlen + 1; 639 } 640 MEMCPY(n, wp, wlen); 641 exp->args[exp->argsoff]->len += wlen - 1; 642 ++exp->argsoff; 643 excp->argv = exp->args; 644 excp->argc = exp->argsoff; 645 } 646 closedir(dirp); 647 free(name); 648 free(path); 649 650 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); 651 return (0); 652 } 653 654 /* 655 * argv_comp -- 656 * Alphabetic comparison. 657 */ 658 static int 659 argv_comp(const void *a, const void *b) 660 { 661 return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp)); 662 } 663 664 /* 665 * argv_sexp -- 666 * Fork a shell, pipe a command through it, and read the output into 667 * a buffer. 668 */ 669 static int 670 argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp) 671 { 672 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; 673 FILE *ifp; 674 pid_t pid; 675 size_t blen, len; 676 int ch, std_output[2]; 677 CHAR_T *bp, *p; 678 char *sh, *sh_path; 679 char *np; 680 size_t nlen; 681 682 /* Secure means no shell access. */ 683 if (O_ISSET(sp, O_SECURE)) { 684 msgq(sp, M_ERR, 685 "289|Shell expansions not supported when the secure edit option is set"); 686 return (1); 687 } 688 689 sh_path = O_STR(sp, O_SHELL); 690 if ((sh = strrchr(sh_path, '/')) == NULL) 691 sh = sh_path; 692 else 693 ++sh; 694 695 /* Local copies of the buffer variables. */ 696 bp = *bpp; 697 blen = *blenp; 698 699 /* 700 * There are two different processes running through this code, named 701 * the utility (the shell) and the parent. The utility reads standard 702 * input and writes standard output and standard error output. The 703 * parent writes to the utility, reads its standard output and ignores 704 * its standard error output. Historically, the standard error output 705 * was discarded by vi, as it produces a lot of noise when file patterns 706 * don't match. 707 * 708 * The parent reads std_output[0], and the utility writes std_output[1]. 709 */ 710 ifp = NULL; 711 std_output[0] = std_output[1] = -1; 712 if (pipe(std_output) < 0) { 713 msgq(sp, M_SYSERR, "pipe"); 714 return (1); 715 } 716 if ((ifp = fdopen(std_output[0], "r")) == NULL) { 717 msgq(sp, M_SYSERR, "fdopen"); 718 goto err; 719 } 720 721 /* 722 * Do the minimal amount of work possible, the shell is going to run 723 * briefly and then exit. We sincerely hope. 724 */ 725 switch (pid = vfork()) { 726 case -1: /* Error. */ 727 msgq(sp, M_SYSERR, "vfork"); 728 err: if (ifp != NULL) 729 (void)fclose(ifp); 730 else if (std_output[0] != -1) 731 close(std_output[0]); 732 if (std_output[1] != -1) 733 close(std_output[0]); 734 return (1); 735 case 0: /* Utility. */ 736 /* Redirect stdout to the write end of the pipe. */ 737 (void)dup2(std_output[1], STDOUT_FILENO); 738 739 /* Close the utility's file descriptors. */ 740 (void)close(std_output[0]); 741 (void)close(std_output[1]); 742 (void)close(STDERR_FILENO); 743 744 /* 745 * XXX 746 * Assume that all shells have -c. 747 */ 748 INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen); 749 execl(sh_path, sh, "-c", np, (char *)NULL); 750 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); 751 _exit(127); 752 default: /* Parent. */ 753 /* Close the pipe ends the parent won't use. */ 754 (void)close(std_output[1]); 755 break; 756 } 757 758 /* 759 * Copy process standard output into a buffer. 760 * 761 * !!! 762 * Historic vi apparently discarded leading \n and \r's from 763 * the shell output stream. We don't on the grounds that any 764 * shell that does that is broken. 765 */ 766 for (p = bp, len = 0, ch = EOF; 767 (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len) 768 if (blen < 5) { 769 ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2); 770 p = bp + len; 771 blen = *blenp - len; 772 } 773 774 /* Delete the final newline, nul terminate the string. */ 775 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { 776 --p; 777 --len; 778 } 779 *p = '\0'; 780 *lenp = len; 781 *bpp = bp; /* *blenp is already updated. */ 782 783 if (ferror(ifp)) 784 goto ioerr; 785 if (fclose(ifp)) { 786 ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s"); 787 alloc_err: rval = SEXP_ERR; 788 } else 789 rval = SEXP_OK; 790 791 /* 792 * Wait for the process. If the shell process fails (e.g., "echo $q" 793 * where q wasn't a defined variable) or if the returned string has 794 * no characters or only blank characters, (e.g., "echo $5"), complain 795 * that the shell expansion failed. We can't know for certain that's 796 * the error, but it's a good guess, and it matches historic practice. 797 * This won't catch "echo foo_$5", but that's not a common error and 798 * historic vi didn't catch it either. 799 */ 800 if (proc_wait(sp, (long)pid, sh, 1, 0)) 801 rval = SEXP_EXPANSION_ERR; 802 803 for (p = bp; len; ++p, --len) 804 if (!cmdskip(*p)) 805 break; 806 if (len == 0) 807 rval = SEXP_EXPANSION_ERR; 808 809 if (rval == SEXP_EXPANSION_ERR) 810 msgq(sp, M_ERR, "304|Shell expansion failed"); 811 812 return (rval == SEXP_OK ? 0 : 1); 813 } 814 815 /* 816 * argv_esc -- 817 * Escape a string into an ex and shell argument. 818 * 819 * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t); 820 */ 821 CHAR_T * 822 argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) 823 { 824 size_t blen, off; 825 CHAR_T *bp, *p; 826 int ch; 827 828 GET_SPACE_GOTOW(sp, bp, blen, len + 1); 829 830 /* 831 * Leaving the first '~' unescaped causes the user to need a 832 * "./" prefix to edit a file which really starts with a '~'. 833 * However, the file completion happens to not work for these 834 * files without the prefix. 835 * 836 * All ex expansion characters, "!%#", are double escaped. 837 */ 838 for (p = bp; len > 0; ++str, --len) { 839 ch = *str; 840 off = p - bp; 841 if (blen / sizeof(CHAR_T) - off < 3) { 842 ADD_SPACE_GOTOW(sp, bp, blen, off + 3); 843 p = bp + off; 844 } 845 if (cmdskip(ch) || ch == '\n' || 846 IS_ESCAPE(sp, excp, ch)) /* Ex. */ 847 *p++ = CH_LITERAL; 848 else switch (ch) { 849 case '~': /* ~user. */ 850 if (p != bp) 851 *p++ = '\\'; 852 break; 853 case '+': /* Ex +cmd. */ 854 if (p == bp) 855 *p++ = '\\'; 856 break; 857 case '!': case '%': case '#': /* Ex exp. */ 858 *p++ = '\\'; 859 *p++ = '\\'; 860 break; 861 case ',': case '-': case '.': case '/': /* Safe. */ 862 case ':': case '=': case '@': case '_': 863 break; 864 default: /* Unsafe. */ 865 if (isascii(ch) && !isalnum(ch)) 866 *p++ = '\\'; 867 } 868 *p++ = ch; 869 } 870 *p = '\0'; 871 872 return bp; 873 874 alloc_err: 875 return NULL; 876 } 877 878 /* 879 * argv_uesc -- 880 * Unescape an escaped ex and shell argument. 881 * 882 * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t); 883 */ 884 CHAR_T * 885 argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len) 886 { 887 size_t blen; 888 CHAR_T *bp, *p; 889 890 GET_SPACE_GOTOW(sp, bp, blen, len + 1); 891 892 for (p = bp; len > 0; ++str, --len) { 893 if (IS_ESCAPE(sp, excp, *str)) { 894 if (--len < 1) 895 break; 896 ++str; 897 } else if (*str == '\\') { 898 if (--len < 1) 899 break; 900 ++str; 901 902 /* Check for double escaping. */ 903 if (*str == '\\' && len > 1) 904 switch (str[1]) { 905 case '!': case '%': case '#': 906 ++str; 907 --len; 908 } 909 } 910 *p++ = *str; 911 } 912 *p = '\0'; 913 914 return bp; 915 916 alloc_err: 917 return NULL; 918 } 919