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