change <paths.h> to "pathnames.h", it has global ones too, noww
[nvi.git] / ex / ex_argv.c
blobca7138976cf005df013d8c0f62541bfeccbfdb74
1 /*-
2 * Copyright (c) 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
9 static char sccsid[] = "$Id: ex_argv.c,v 8.26 1994/01/02 17:55:15 bostic Exp $ (Berkeley) $Date: 1994/01/02 17:55:15 $";
10 #endif /* not lint */
12 #include <sys/types.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
20 #include "vi.h"
21 #include "excmd.h"
23 static int argv_alloc __P((SCR *, size_t));
24 static int argv_fexp __P((SCR *, EXCMDARG *,
25 char *, size_t, char *, size_t *, char **, size_t *, int));
26 static int argv_sexp __P((SCR *, char **, size_t *, size_t *));
29 * argv_init --
30 * Build a prototype arguments list.
32 int
33 argv_init(sp, ep, excp)
34 SCR *sp;
35 EXF *ep;
36 EXCMDARG *excp;
38 EX_PRIVATE *exp;
40 exp = EXP(sp);
41 exp->argsoff = 0;
42 argv_alloc(sp, 1);
44 excp->argv = exp->args;
45 excp->argc = exp->argsoff;
46 return (0);
50 * argv_exp0 --
51 * Put a string into an argv.
53 int
54 argv_exp0(sp, ep, excp, cmd, cmdlen)
55 SCR *sp;
56 EXF *ep;
57 EXCMDARG *excp;
58 char *cmd;
59 size_t cmdlen;
61 EX_PRIVATE *exp;
63 exp = EXP(sp);
64 argv_alloc(sp, cmdlen);
65 memmove(exp->args[exp->argsoff]->bp, cmd, cmdlen);
66 exp->args[exp->argsoff]->bp[cmdlen] = '\0';
67 exp->args[exp->argsoff]->len = cmdlen;
68 ++exp->argsoff;
69 excp->argv = exp->args;
70 excp->argc = exp->argsoff;
71 return (0);
75 * argv_exp1 --
76 * Do file name expansion on a string, and leave it in a string.
78 int
79 argv_exp1(sp, ep, excp, cmd, cmdlen, is_bang)
80 SCR *sp;
81 EXF *ep;
82 EXCMDARG *excp;
83 char *cmd;
84 size_t cmdlen;
85 int is_bang;
87 EX_PRIVATE *exp;
88 size_t blen, len;
89 char *bp;
91 GET_SPACE_RET(sp, bp, blen, 512);
93 len = 0;
94 exp = EXP(sp);
95 if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
96 FREE_SPACE(sp, bp, blen);
97 return (1);
100 argv_alloc(sp, len);
101 memmove(exp->args[exp->argsoff]->bp, bp, len);
102 exp->args[exp->argsoff]->bp[len] = '\0';
103 exp->args[exp->argsoff]->len = len;
104 ++exp->argsoff;
105 excp->argv = exp->args;
106 excp->argc = exp->argsoff;
108 FREE_SPACE(sp, bp, blen);
109 return (0);
113 * argv_exp2 --
114 * Do file name and shell expansion on a string, and break
115 * it up into an argv.
118 argv_exp2(sp, ep, excp, cmd, cmdlen, is_bang)
119 SCR *sp;
120 EXF *ep;
121 EXCMDARG *excp;
122 char *cmd;
123 size_t cmdlen;
124 int is_bang;
126 size_t blen, len, n;
127 int rval;
128 char *bp, *p;
130 GET_SPACE_RET(sp, bp, blen, 512);
132 #define SHELLECHO "echo "
133 #define SHELLOFFSET (sizeof(SHELLECHO) - 1)
134 memmove(bp, SHELLECHO, SHELLOFFSET);
135 p = bp + SHELLOFFSET;
136 len = SHELLOFFSET;
138 #if defined(DEBUG) && 0
139 TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
140 #endif
142 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, is_bang)) {
143 rval = 1;
144 goto err;
147 #if defined(DEBUG) && 0
148 TRACE(sp, "before shell: %d: {%s}\n", len, bp);
149 #endif
152 * Do shell word expansion -- it's very, very hard to figure out
153 * what magic characters the user's shell expects. If it's not
154 * pure vanilla, don't even try.
156 for (p = bp, n = len; n > 0; --n, ++p)
157 if (!isalnum(*p) && !isblank(*p) && *p != '/' && *p != '.')
158 break;
159 if (n > 0) {
160 if (argv_sexp(sp, &bp, &blen, &len)) {
161 rval = 1;
162 goto err;
164 p = bp;
165 } else {
166 p = bp + SHELLOFFSET;
167 len -= SHELLOFFSET;
170 #if defined(DEBUG) && 0
171 TRACE(sp, "after shell: %d: {%s}\n", len, bp);
172 #endif
174 rval = argv_exp3(sp, ep, excp, p, len);
176 err: FREE_SPACE(sp, bp, blen);
177 return (rval);
181 * argv_exp3 --
182 * Take a string and break it up into an argv.
185 argv_exp3(sp, ep, excp, cmd, cmdlen)
186 SCR *sp;
187 EXF *ep;
188 EXCMDARG *excp;
189 char *cmd;
190 size_t cmdlen;
192 CHAR_T vlit;
193 EX_PRIVATE *exp;
194 size_t len;
195 int ch, off;
196 char *ap, *p;
198 (void)term_key_ch(sp, K_VLNEXT, &vlit);
199 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
200 /* Skip any leading whitespace. */
201 for (; cmdlen > 0; --cmdlen, ++cmd) {
202 ch = *cmd;
203 if (!isblank(ch))
204 break;
206 if (cmdlen == 0)
207 break;
210 * Determine the length of this whitespace delimited
211 * argument.
213 * QUOTING NOTE:
215 * Skip any character preceded by the user's quoting
216 * character.
218 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len)
219 if ((ch = *cmd) == vlit && cmdlen > 1) {
220 ++cmd;
221 --cmdlen;
222 } else if (isblank(ch))
223 break;
226 * Copy the argument into place.
228 * QUOTING NOTE:
230 * Lose quote chars.
232 argv_alloc(sp, len);
233 off = exp->argsoff;
234 exp->args[off]->len = len;
235 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
236 if (*ap == vlit) {
237 ++ap;
238 --exp->args[off]->len;
240 *p = '\0';
242 excp->argv = exp->args;
243 excp->argc = exp->argsoff;
245 #if defined(DEBUG) && 0
246 for (cnt = 0; cnt < exp->argsoff; ++cnt)
247 TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
248 #endif
249 return (0);
253 * argv_fexp --
254 * Do file name and bang command expansion.
256 static int
257 argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
258 SCR *sp;
259 EXCMDARG *excp;
260 char *cmd, *p, **bpp;
261 size_t cmdlen, *lenp, *blenp;
262 int is_bang;
264 EX_PRIVATE *exp;
265 char *bp, *t;
266 size_t blen, len, tlen;
268 /* Replace file name characters. */
269 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
270 switch (*cmd) {
271 case '!':
272 if (!is_bang)
273 goto ins_ch;
274 exp = EXP(sp);
275 if (exp->lastbcomm == NULL) {
276 msgq(sp, M_ERR,
277 "No previous command to replace \"!\".");
278 return (1);
280 len += tlen = strlen(exp->lastbcomm);
281 ADD_SPACE_RET(sp, bp, blen, len);
282 memmove(p, exp->lastbcomm, tlen);
283 p += tlen;
284 F_SET(excp, E_MODIFY);
285 break;
286 case '%':
287 if (sp->frp->cname == NULL && sp->frp->name == NULL) {
288 msgq(sp, M_ERR,
289 "No filename to substitute for %%.");
290 return (1);
292 tlen = strlen(t = FILENAME(sp->frp));
293 len += tlen;
294 ADD_SPACE_RET(sp, bp, blen, len);
295 memmove(p, t, tlen);
296 p += tlen;
297 F_SET(excp, E_MODIFY);
298 break;
299 case '#':
301 * Try the alternate file name first, then the
302 * previously edited file.
304 if (sp->alt_name == NULL && (sp->p_frp == NULL ||
305 sp->frp->cname == NULL && sp->frp->name == NULL)) {
306 msgq(sp, M_ERR,
307 "No filename to substitute for #.");
308 return (1);
310 if (sp->alt_name != NULL)
311 t = sp->alt_name;
312 else
313 t = FILENAME(sp->frp);
314 len += tlen = strlen(t);
315 ADD_SPACE_RET(sp, bp, blen, len);
316 memmove(p, t, tlen);
317 p += tlen;
318 F_SET(excp, E_MODIFY);
319 break;
320 case '\\':
322 * QUOTING NOTE:
324 * Strip any backslashes that protected the file
325 * expansion characters.
327 if (cmdlen > 1 && cmd[1] == '%' || cmd[1] == '#')
328 ++cmd;
329 /* FALLTHROUGH */
330 default:
331 ins_ch: ++len;
332 ADD_SPACE_RET(sp, bp, blen, len);
333 *p++ = *cmd;
336 /* Nul termination. */
337 ++len;
338 ADD_SPACE_RET(sp, bp, blen, len);
339 *p = '\0';
341 /* Return the new string length, buffer, buffer length. */
342 *lenp = len - 1;
343 *bpp = bp;
344 *blenp = blen;
345 return (0);
349 * argv_alloc --
350 * Make more space for arguments.
352 static int
353 argv_alloc(sp, len)
354 SCR *sp;
355 size_t len;
357 ARGS *ap;
358 EX_PRIVATE *exp;
359 int cnt, off;
362 * Allocate room for another argument, always leaving
363 * enough room for an ARGS structure with a length of 0.
365 #define INCREMENT 20
366 exp = EXP(sp);
367 off = exp->argsoff;
368 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
369 cnt = exp->argscnt + INCREMENT;
370 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
371 if (exp->args == NULL) {
372 (void)argv_free(sp);
373 goto mem;
375 memset(&exp->args[off], 0, INCREMENT * sizeof(ARGS *));
376 exp->argscnt = cnt;
379 /* First argument. */
380 if (exp->args[off] == NULL) {
381 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
382 if (exp->args[off] == NULL)
383 goto mem;
386 /* First argument buffer. */
387 ap = exp->args[off];
388 ap->len = 0;
389 if (ap->blen < len + 1) {
390 ap->blen = len + 1;
391 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
392 if (ap->bp == NULL) {
393 ap->bp = NULL;
394 ap->blen = 0;
395 F_CLR(ap, A_ALLOCATED);
396 mem: msgq(sp, M_SYSERR, NULL);
397 return (1);
399 F_SET(ap, A_ALLOCATED);
402 /* Second argument. */
403 if (exp->args[++off] == NULL) {
404 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
405 if (exp->args[off] == NULL)
406 goto mem;
408 /* 0 length serves as end-of-argument marker. */
409 exp->args[off]->len = 0;
410 return (0);
414 * argv_free --
415 * Free up argument structures.
418 argv_free(sp)
419 SCR *sp;
421 EX_PRIVATE *exp;
422 int off;
424 exp = EXP(sp);
425 if (exp->args != NULL) {
426 for (off = 0; off < exp->argscnt; ++off) {
427 if (exp->args[off] == NULL)
428 continue;
429 if (F_ISSET(exp->args[off], A_ALLOCATED))
430 free(exp->args[off]->bp);
431 FREE(exp->args[off], sizeof(ARGS));
433 FREE(exp->args, exp->argscnt * sizeof(ARGS *));
435 exp->args = NULL;
436 exp->argscnt = 0;
437 exp->argsoff = 0;
438 return (0);
442 * argv_sexp --
443 * Fork a shell, pipe a command through it, and read the output into
444 * a buffer.
446 static int
447 argv_sexp(sp, bpp, blenp, lenp)
448 SCR *sp;
449 char **bpp;
450 size_t *blenp, *lenp;
452 FILE *ifp;
453 pid_t pid;
454 size_t blen, len;
455 int ch, rval, output[2];
456 char *bp, *p, *sh, *sh_path;
458 bp = *bpp;
459 blen = *blenp;
461 sh_path = O_STR(sp, O_SHELL);
462 if ((sh = strrchr(sh_path, '/')) == NULL)
463 sh = sh_path;
464 else
465 ++sh;
468 * There are two different processes running through this code.
469 * They are named the utility and the parent. The utility reads
470 * from standard input and writes to the parent. The parent reads
471 * from the utility and writes into the buffer. The parent reads
472 * from output[0], and the utility writes to output[1].
474 if (pipe(output) < 0) {
475 msgq(sp, M_SYSERR, "pipe");
476 return (1);
478 if ((ifp = fdopen(output[0], "r")) == NULL) {
479 msgq(sp, M_SYSERR, "fdopen");
480 goto err;
484 * Do the minimal amount of work possible, the shell is going
485 * to run briefly and then exit. Hopefully.
487 switch (pid = vfork()) {
488 case -1: /* Error. */
489 msgq(sp, M_SYSERR, "vfork");
490 err: (void)close(output[0]);
491 (void)close(output[1]);
492 return (1);
493 case 0: /* Utility. */
494 /* Redirect stdout/stderr to the write end of the pipe. */
495 (void)dup2(output[1], STDOUT_FILENO);
496 (void)dup2(output[1], STDERR_FILENO);
498 /* Close the utility's file descriptors. */
499 (void)close(output[0]);
500 (void)close(output[1]);
502 /* Assumes that all shells have -c. */
503 execl(sh_path, sh, "-c", bp, NULL);
504 msgq(sp, M_ERR,
505 "Error: execl: %s: %s", sh_path, strerror(errno));
506 _exit(127);
507 default:
508 /* Close the pipe end the parent won't use. */
509 (void)close(output[1]);
510 break;
513 rval = 0;
516 * Copy process output into a buffer.
518 * !!!
519 * Historic vi apparently discarded leading \n and \r's from
520 * the shell output stream. We don't on the grounds that any
521 * shell that does that is broken.
523 for (p = bp, len = 0, ch = EOF;
524 (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
525 if (blen < 5) {
526 ADD_SPACE_GOTO(sp, bp, blen, *blenp * 2);
527 p = bp + len;
528 blen = *blenp - len;
531 /* Delete the final newline, nul terminate the string. */
532 if (p > bp && p[-1] == '\n' || p[-1] == '\r') {
533 --len;
534 *--p = '\0';
535 } else
536 *p = '\0';
537 *lenp = len;
539 if (ferror(ifp)) {
540 msgq(sp, M_ERR, "I/O error: %s", sh);
541 binc_err: rval = 1;
543 (void)fclose(ifp);
545 *bpp = bp; /* *blenp is already updated. */
547 /* Wait for the process. */
548 return (proc_wait(sp, (long)pid, sh, 0) | rval);