rework ARGS structures as part of ex parser rework
[nvi.git] / ex / ex_argv.c
blob37c022aecb973d1ed4c3d6bf401450052ed31939
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.18 1993/11/29 14:15:16 bostic Exp $ (Berkeley) $Date: 1993/11/29 14:15:16 $";
10 #endif /* not lint */
12 #include <sys/types.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <paths.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
21 #include "vi.h"
22 #include "excmd.h"
24 static int argv_allocate __P((SCR *, int));
25 static int argv_fexp __P((SCR *, EXCMDARG *,
26 char *, char *, size_t *, char **, size_t *, int));
27 static int argv_sexp __P((SCR *, char *, size_t, size_t *));
29 #define ARGALLOC(off, len) \
30 if (exp->args[off].len < len) { \
31 if ((exp->args[off].bp = \
32 realloc(exp->args[off].bp, len)) == NULL) { \
33 exp->args[off].bp = NULL; \
34 exp->args[off].len = 0; \
35 msgq(sp, M_SYSERR, NULL); \
36 return (1); \
37 } \
38 exp->args[off].len = len; \
39 exp->args[off].flags |= A_ALLOCATED; \
43 * argv_exp1 --
44 * Do file name expansion on a string, and leave it in a string.
46 int
47 argv_exp1(sp, ep, cmdp, s, is_bang)
48 SCR *sp;
49 EXF *ep;
50 EXCMDARG *cmdp;
51 char *s;
52 int is_bang;
54 EX_PRIVATE *exp;
55 size_t blen, len;
56 char *bp;
58 GET_SPACE(sp, bp, blen, 512);
60 len = 0;
61 exp = EXP(sp);
62 if (argv_fexp(sp, cmdp, s, bp, &len, &bp, &blen, is_bang) ||
63 exp->argscnt < 2 && argv_allocate(sp, 0)) {
64 FREE_SPACE(sp, bp, blen);
65 return (1);
68 ARGALLOC(0, len);
69 memmove(exp->args[0].bp, bp, len);
70 exp->argv[0] = exp->args[0].bp;
71 exp->argv[1] = NULL;
73 FREE_SPACE(sp, bp, blen);
75 cmdp->argv = exp->argv;
76 cmdp->argc = 1;
77 return (0);
81 * argv_exp2 --
82 * Do file name and shell expansion on a string, and break
83 * it up into an argv.
85 int
86 argv_exp2(sp, ep, cmdp, s, is_bang)
87 SCR *sp;
88 EXF *ep;
89 EXCMDARG *cmdp;
90 char *s;
91 int is_bang;
93 size_t len, blen;
94 int rval;
95 char *bp, *p;
97 GET_SPACE(sp, bp, blen, 512);
99 #define SHELLECHO "echo "
100 #define SHELLOFFSET (sizeof(SHELLECHO) - 1)
101 memmove(bp, SHELLECHO, SHELLOFFSET);
102 p = bp + SHELLOFFSET;
103 len = SHELLOFFSET;
105 #if defined(DEBUG) && 0
106 TRACE(sp, "file_argv: {%s}\n", s);
107 #endif
109 if (argv_fexp(sp, cmdp, s, p, &len, &bp, &blen, is_bang)) {
110 rval = 1;
111 goto err;
114 #if defined(DEBUG) && 0
115 TRACE(sp, "before shell: %d: {%s}\n", len, bp);
116 #endif
119 * Do shell word expansion -- it's very, very hard to figure out
120 * what magic characters the user's shell expects. If it's not
121 * pure vanilla, don't even try.
123 for (p = bp; *p; ++p)
124 if (!isalnum(*p) && !isblank(*p) && *p != '/' && *p != '.')
125 break;
126 if (*p) {
127 if (argv_sexp(sp, bp, blen, &len)) {
128 rval = 1;
129 goto err;
131 p = bp;
132 } else
133 p = bp + SHELLOFFSET;
135 #if defined(DEBUG) && 0
136 TRACE(sp, "after shell: %d: {%s}\n", len, bp);
137 #endif
139 rval = argv_exp3(sp, ep, cmdp, p);
141 err: FREE_SPACE(sp, bp, blen);
142 return (rval);
146 * argv_exp3 --
147 * Take a string and break it up into an argv.
150 argv_exp3(sp, ep, cmdp, p)
151 SCR *sp;
152 EXF *ep;
153 EXCMDARG *cmdp;
154 char *p;
156 EX_PRIVATE *exp;
157 size_t len;
158 int done, off;
159 char *ap, *t;
161 exp = EXP(sp);
162 for (done = off = 0;; ++p) {
163 /* Skip any leading whitespace. */
164 for (; isblank(p[0]); ++p);
165 if (*p == '\0')
166 break;
169 * QUOTING NOTE:
171 * New argument; NULL terminate, skipping anything
172 * that's preceded by the user's quoting character.
174 for (ap = p; p[0] != '\0'; ++p) {
175 if (term_key_val(sp, p[0]) == K_VLNEXT && p[1])
176 p += 2;
177 if (isblank(p[0]))
178 break;
180 if (*p)
181 *p = '\0';
182 else
183 done = 1;
186 * Allocate more pointer space if necessary, making
187 * sure there's space for a trailing NULL pointer.
189 if (off + 2 >= exp->argscnt - 1 && argv_allocate(sp, off))
190 return (1);
192 /* Allocate more argument space if necessary. */
193 len = (p - ap) + 1;
194 ARGALLOC(off, len);
195 exp->argv[off] = exp->args[off].bp;
198 * QUOTING NOTE:
200 * Copy the argument into place, losing quote chars.
202 for (t = exp->args[off].bp; len; *t++ = *ap++, --len)
203 if (term_key_val(sp, *ap) == K_VLNEXT) {
204 ++ap;
205 --len;
207 ++off;
209 if (done)
210 break;
212 exp->argv[off] = NULL;
213 cmdp->argv = exp->argv;
214 cmdp->argc = off;
216 #if defined(DEBUG) && 0
217 for (cnt = 0; cnt < off; ++cnt)
218 TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
219 #endif
220 return (0);
224 * argv_fexp --
225 * Do file name and bang command expansion.
227 static int
228 argv_fexp(sp, cmdp, s, p, lenp, bpp, blenp, is_bang)
229 SCR *sp;
230 EXCMDARG *cmdp;
231 char *s, *p, **bpp;
232 size_t *lenp, *blenp;
233 int is_bang;
235 EX_PRIVATE *exp;
236 char *bp, *t;
237 size_t blen, len, tlen;
239 /* Replace file name characters. */
240 for (bp = *bpp, blen = *blenp, len = *lenp; *s; ++s)
241 switch (*s) {
242 case '!':
243 if (!is_bang)
244 goto ins_ch;
245 exp = EXP(sp);
246 if (exp->lastbcomm == NULL) {
247 msgq(sp, M_ERR,
248 "No previous command to replace \"!\".");
249 return (1);
251 len += tlen = strlen(exp->lastbcomm);
252 ADD_SPACE(sp, bp, blen, len);
253 memmove(p, exp->lastbcomm, tlen);
254 p += tlen;
255 F_SET(cmdp, E_MODIFY);
256 break;
257 case '%':
258 if (sp->frp->cname == NULL && sp->frp->name == NULL) {
259 msgq(sp, M_ERR,
260 "No filename to substitute for %%.");
261 return (1);
264 * In other words:
265 * t = FILENAME(sp->frp);
267 if (sp->frp->cname != NULL) {
268 t = sp->frp->cname;
269 tlen = sp->frp->clen;
270 } else {
271 t = sp->frp->name;
272 tlen = sp->frp->nlen;
274 len += tlen;
275 ADD_SPACE(sp, bp, blen, len);
276 memmove(p, t, tlen);
277 p += tlen;
278 F_SET(cmdp, E_MODIFY);
279 break;
280 case '#':
282 * Try the alternate file name first, then the
283 * previously edited file.
285 if (sp->alt_name == NULL && (sp->p_frp == NULL ||
286 sp->frp->cname == NULL && sp->frp->name == NULL)) {
287 msgq(sp, M_ERR,
288 "No filename to substitute for #.");
289 return (1);
292 * In other words:
293 * t = sp->alt_name == NULL ?
294 * FILENAME(sp->frp): sp->alt_name;
296 if (sp->alt_name != NULL) {
297 t = sp->alt_name;
298 tlen = strlen(t);
299 } else if (sp->frp->cname != NULL) {
300 t = sp->frp->cname;
301 tlen = sp->frp->clen;
302 } else {
303 t = sp->frp->name;
304 tlen = sp->frp->nlen;
306 len += tlen;
307 ADD_SPACE(sp, bp, blen, len);
308 memmove(p, t, tlen);
309 p += tlen;
310 F_SET(cmdp, E_MODIFY);
311 break;
312 case '\\':
314 * QUOTING NOTE:
316 * Strip any backslashes that protected the file
317 * expansion characters.
319 if (s[1] == '%' || s[1] == '#')
320 ++s;
321 /* FALLTHROUGH */
322 default:
323 ins_ch: ++len;
324 ADD_SPACE(sp, bp, blen, len);
325 *p++ = *s;
328 /* Nul termination. */
329 ++len;
330 ADD_SPACE(sp, bp, blen, len);
331 *p = '\0';
333 /* Return the new string length, buffer, buffer length. */
334 *lenp = len;
335 *bpp = bp;
336 *blenp = blen;
337 return (0);
341 * argv_allocate --
342 * Make more space for arguments.
344 static int
345 argv_allocate(sp, off)
346 SCR *sp;
347 int off;
349 EX_PRIVATE *exp;
350 int cnt;
352 #define INCREMENT 20
353 exp = EXP(sp);
354 cnt = exp->argscnt + INCREMENT;
355 if ((exp->args = realloc(exp->args, cnt * sizeof(ARGS))) == NULL) {
356 (void)argv_free(sp);
357 goto mem;
359 if ((exp->argv =
360 realloc(exp->argv, cnt * sizeof(char *))) == NULL) {
361 (void)argv_free(sp);
362 mem: msgq(sp, M_SYSERR, NULL);
363 return (1);
365 memset(&exp->args[off], 0, INCREMENT * sizeof(ARGS));
366 exp->argscnt = cnt;
367 return (0);
371 * argv_free --
372 * Free up argument structures.
375 argv_free(sp)
376 SCR *sp;
378 EX_PRIVATE *exp;
379 int off;
381 exp = EXP(sp);
382 if (exp->args != NULL) {
383 for (off = 0; off < exp->argscnt; ++off)
384 if (F_ISSET(&exp->args[off], A_ALLOCATED))
385 FREE(exp->args[off].bp, exp->args[off].len);
386 FREE(exp->args, exp->argscnt * sizeof(ARGS *));
388 if (exp->argv != NULL)
389 FREE(exp->argv, exp->argscnt * sizeof(char *));
390 exp->argscnt = 0;
391 return (0);
395 * argv_sexp --
396 * Fork a shell, pipe a command through it, and read the output into
397 * a buffer.
399 static int
400 argv_sexp(sp, bp, blen, lenp)
401 SCR *sp;
402 char *bp;
403 size_t *lenp, blen;
405 FILE *ifp;
406 pid_t pid;
407 size_t len;
408 int ch, rval, output[2];
409 char *p, *sh, *sh_path;
411 sh_path = O_STR(sp, O_SHELL);
412 if ((sh = strrchr(sh_path, '/')) == NULL)
413 sh = sh_path;
414 else
415 ++sh;
418 * There are two different processes running through this code.
419 * They are named the utility and the parent. The utility reads
420 * from standard input and writes to the parent. The parent reads
421 * from the utility and writes into the buffer. The parent reads
422 * from output[0], and the utility writes to output[1].
424 if (pipe(output) < 0) {
425 msgq(sp, M_SYSERR, "pipe");
426 return (1);
428 if ((ifp = fdopen(output[0], "r")) == NULL) {
429 msgq(sp, M_SYSERR, "fdopen");
430 goto err;
434 * Do the minimal amount of work possible, the shell is going
435 * to run briefly and then exit. Hopefully.
437 switch (pid = vfork()) {
438 case -1: /* Error. */
439 msgq(sp, M_SYSERR, "vfork");
440 err: (void)close(output[0]);
441 (void)close(output[1]);
442 return (1);
443 case 0: /* Utility. */
444 /* Redirect stdout/stderr to the write end of the pipe. */
445 (void)dup2(output[1], STDOUT_FILENO);
446 (void)dup2(output[1], STDERR_FILENO);
448 /* Close the utility's file descriptors. */
449 (void)close(output[0]);
450 (void)close(output[1]);
452 /* Assumes that all shells have -c. */
453 execl(sh_path, sh, "-c", bp, NULL);
454 msgq(sp, M_ERR,
455 "Error: execl: %s: %s", sh_path, strerror(errno));
456 _exit(127);
457 default:
458 /* Close the pipe end the parent won't use. */
459 (void)close(output[1]);
460 break;
463 rval = 0;
465 /* Copy process output into a buffer. */
466 for (p = bp, len = 0, ch = EOF;
467 --blen && (ch = getc(ifp)) != EOF; *p++ = ch, ++len);
468 if (ch != EOF) {
469 rval = 1;
470 msgq(sp, M_ERR, "%s: output truncated", sh);
472 if (ferror(ifp)) {
473 rval = 1;
474 msgq(sp, M_ERR, "I/O error: %s", sh);
476 (void)fclose(ifp);
478 /* Wait for the process. */
479 rval |= proc_wait(sp, (long)pid, sh, 0);
481 /* Delete the final newline, nul terminate the string. */
482 if (p > bp && p[-1] == '\n' || p[-1] == '\r') {
483 --len;
484 *--p = '\0';
485 } else
486 *p = '\0';
487 *lenp = len;
489 return (rval);