xref: /original-bsd/bin/csh/dol.c (revision d54be081)
1 /*-
2  * Copyright (c) 1980, 1991 The Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)dol.c	5.13 (Berkeley) 06/08/91";
10 #endif /* not lint */
11 
12 #include <sys/types.h>
13 #include <fcntl.h>
14 #include <errno.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 #if __STDC__
19 # include <stdarg.h>
20 #else
21 # include <varargs.h>
22 #endif
23 
24 #include "csh.h"
25 #include "extern.h"
26 
27 /*
28  * These routines perform variable substitution and quoting via ' and ".
29  * To this point these constructs have been preserved in the divided
30  * input words.  Here we expand variables and turn quoting via ' and " into
31  * QUOTE bits on characters (which prevent further interpretation).
32  * If the `:q' modifier was applied during history expansion, then
33  * some QUOTEing may have occurred already, so we dont "trim()" here.
34  */
35 
36 static int Dpeekc, Dpeekrd;	/* Peeks for DgetC and Dreadc */
37 static Char *Dcp, **Dvp;	/* Input vector for Dreadc */
38 
39 #define	DEOF	-1
40 
41 #define	unDgetC(c)	Dpeekc = c
42 
43 #define QUOTES		(_Q|_Q1|_ESC)	/* \ ' " ` */
44 
45 /*
46  * The following variables give the information about the current
47  * $ expansion, recording the current word position, the remaining
48  * words within this expansion, the count of remaining words, and the
49  * information about any : modifier which is being applied.
50  */
51 static Char *dolp;		/* Remaining chars from this word */
52 static Char **dolnxt;		/* Further words */
53 static int dolcnt;		/* Count of further words */
54 static Char dolmod;		/* : modifier character */
55 static int dolmcnt;		/* :gx -> 10000, else 1 */
56 
57 static void	 Dfix2 __P((Char **));
58 static Char 	*Dpack __P((Char *, Char *));
59 static int	 Dword __P((void));
60 static void	 dolerror __P((Char *));
61 static int	 DgetC __P((int));
62 static void	 Dgetdol __P((void));
63 static void	 fixDolMod __P((void));
64 static void	 setDolp __P((Char *));
65 static void	 unDredc __P((int));
66 static int	 Dredc __P((void));
67 static void	 Dtestq __P((int));
68 
69 
70 /*
71  * Fix up the $ expansions and quotations in the
72  * argument list to command t.
73  */
74 void
75 Dfix(t)
76     register struct command *t;
77 {
78     register Char **pp;
79     register Char *p;
80 
81     if (noexec)
82 	return;
83     /* Note that t_dcom isn't trimmed thus !...:q's aren't lost */
84     for (pp = t->t_dcom; p = *pp++;)
85 	for (; *p; p++) {
86 	    if (cmap(*p, _DOL | QUOTES)) {	/* $, \, ', ", ` */
87 		Dfix2(t->t_dcom);	/* found one */
88 		blkfree(t->t_dcom);
89 		t->t_dcom = gargv;
90 		gargv = 0;
91 		return;
92 	    }
93 	}
94 }
95 
96 /*
97  * $ substitute one word, for i/o redirection
98  */
99 Char   *
100 Dfix1(cp)
101     register Char *cp;
102 {
103     Char   *Dv[2];
104 
105     if (noexec)
106 	return (0);
107     Dv[0] = cp;
108     Dv[1] = NULL;
109     Dfix2(Dv);
110     if (gargc != 1) {
111 	setname(short2str(cp));
112 	stderror(ERR_NAME | ERR_AMBIG);
113     }
114     cp = Strsave(gargv[0]);
115     blkfree(gargv), gargv = 0;
116     return (cp);
117 }
118 
119 /*
120  * Subroutine to do actual fixing after state initialization.
121  */
122 static void
123 Dfix2(v)
124     Char  **v;
125 {
126     ginit();			/* Initialize glob's area pointers */
127     Dvp = v;
128     Dcp = STRNULL;		/* Setup input vector for Dreadc */
129     unDgetC(0);
130     unDredc(0);			/* Clear out any old peeks (at error) */
131     dolp = 0;
132     dolcnt = 0;			/* Clear out residual $ expands (...) */
133     while (Dword())
134 	continue;
135 }
136 
137 #define MAXWLEN (BUFSIZ - 4)
138 /*
139  * Pack up more characters in this word
140  */
141 static Char *
142 Dpack(wbuf, wp)
143     Char   *wbuf, *wp;
144 {
145     register int c;
146     register int i = MAXWLEN - (wp - wbuf);
147 
148     for (;;) {
149 	c = DgetC(DODOL);
150 	if (c == '\\') {
151 	    c = DgetC(0);
152 	    if (c == DEOF) {
153 		unDredc(c);
154 		*wp = 0;
155 		Gcat(STRNULL, wbuf);
156 		return (NULL);
157 	    }
158 	    if (c == '\n')
159 		c = ' ';
160 	    else
161 		c |= QUOTE;
162 	}
163 	if (c == DEOF) {
164 	    unDredc(c);
165 	    *wp = 0;
166 	    Gcat(STRNULL, wbuf);
167 	    return (NULL);
168 	}
169 	if (cmap(c, _SP | _NL | _Q | _Q1)) {	/* sp \t\n'"` */
170 	    unDgetC(c);
171 	    if (cmap(c, QUOTES))
172 		return (wp);
173 	    *wp++ = 0;
174 	    Gcat(STRNULL, wbuf);
175 	    return (NULL);
176 	}
177 	if (--i <= 0)
178 	    stderror(ERR_WTOOLONG);
179 	*wp++ = c;
180     }
181 }
182 
183 /*
184  * Get a word.  This routine is analogous to the routine
185  * word() in sh.lex.c for the main lexical input.  One difference
186  * here is that we don't get a newline to terminate our expansion.
187  * Rather, DgetC will return a DEOF when we hit the end-of-input.
188  */
189 static int
190 Dword()
191 {
192     register int c, c1;
193     Char    wbuf[BUFSIZ];
194     register Char *wp = wbuf;
195     register int i = MAXWLEN;
196     register bool dolflg;
197     bool    sofar = 0, done = 0;
198 
199     while (!done) {
200 	done = 1;
201 	c = DgetC(DODOL);
202 	switch (c) {
203 
204 	case DEOF:
205 	    if (sofar == 0)
206 		return (0);
207 	    /* finish this word and catch the code above the next time */
208 	    unDredc(c);
209 	    /* fall into ... */
210 
211 	case '\n':
212 	    *wp = 0;
213 	    Gcat(STRNULL, wbuf);
214 	    return (1);
215 
216 	case ' ':
217 	case '\t':
218 	    done = 0;
219 	    break;
220 
221 	case '`':
222 	    /* We preserve ` quotations which are done yet later */
223 	    *wp++ = c, --i;
224 	case '\'':
225 	case '"':
226 	    /*
227 	     * Note that DgetC never returns a QUOTES character from an
228 	     * expansion, so only true input quotes will get us here or out.
229 	     */
230 	    c1 = c;
231 	    dolflg = c1 == '"' ? DODOL : 0;
232 	    for (;;) {
233 		c = DgetC(dolflg);
234 		if (c == c1)
235 		    break;
236 		if (c == '\n' || c == DEOF)
237 		    stderror(ERR_UNMATCHED, c1);
238 		if ((c & (QUOTE | TRIM)) == ('\n' | QUOTE))
239 		    --wp, ++i;
240 		if (--i <= 0)
241 		    stderror(ERR_WTOOLONG);
242 		switch (c1) {
243 
244 		case '"':
245 		    /*
246 		     * Leave any `s alone for later. Other chars are all
247 		     * quoted, thus `...` can tell it was within "...".
248 		     */
249 		    *wp++ = c == '`' ? '`' : c | QUOTE;
250 		    break;
251 
252 		case '\'':
253 		    /* Prevent all further interpretation */
254 		    *wp++ = c | QUOTE;
255 		    break;
256 
257 		case '`':
258 		    /* Leave all text alone for later */
259 		    *wp++ = c;
260 		    break;
261 		}
262 	    }
263 	    if (c1 == '`')
264 		*wp++ = '`', --i;
265 	    sofar = 1;
266 	    if ((wp = Dpack(wbuf, wp)) == NULL)
267 		return (1);
268 	    else {
269 		i = MAXWLEN - (wp - wbuf);
270 		done = 0;
271 	    }
272 	    break;
273 
274 	case '\\':
275 	    c = DgetC(0);	/* No $ subst! */
276 	    if (c == '\n' || c == DEOF) {
277 		done = 0;
278 		break;
279 	    }
280 	    c |= QUOTE;
281 	    break;
282 	}
283 	if (done) {
284 	    unDgetC(c);
285 	    sofar = 1;
286 	    if ((wp = Dpack(wbuf, wp)) == NULL)
287 		return (1);
288 	    else {
289 		i = MAXWLEN - (wp - wbuf);
290 		done = 0;
291 	    }
292 	}
293     }
294     /* Really NOTREACHED */
295     return (0);
296 }
297 
298 
299 /*
300  * Get a character, performing $ substitution unless flag is 0.
301  * Any QUOTES character which is returned from a $ expansion is
302  * QUOTEd so that it will not be recognized above.
303  */
304 static int
305 DgetC(flag)
306     register int flag;
307 {
308     register int c;
309 
310 top:
311     if (c = Dpeekc) {
312 	Dpeekc = 0;
313 	return (c);
314     }
315     if (lap) {
316 	c = *lap++ & (QUOTE | TRIM);
317 	if (c == 0) {
318 	    lap = 0;
319 	    goto top;
320 	}
321 quotspec:
322 	if (cmap(c, QUOTES))
323 	    return (c | QUOTE);
324 	return (c);
325     }
326     if (dolp) {
327 	if (c = *dolp++ & (QUOTE | TRIM))
328 	    goto quotspec;
329 	if (dolcnt > 0) {
330 	    setDolp(*dolnxt++);
331 	    --dolcnt;
332 	    return (' ');
333 	}
334 	dolp = 0;
335     }
336     if (dolcnt > 0) {
337 	setDolp(*dolnxt++);
338 	--dolcnt;
339 	goto top;
340     }
341     c = Dredc();
342     if (c == '$' && flag) {
343 	Dgetdol();
344 	goto top;
345     }
346     return (c);
347 }
348 
349 static Char *nulvec[] = {0};
350 static struct varent nulargv = {nulvec, STRargv, 0};
351 
352 static void
353 dolerror(s)
354     Char   *s;
355 {
356     setname(short2str(s));
357     stderror(ERR_NAME | ERR_RANGE);
358 }
359 
360 /*
361  * Handle the multitudinous $ expansion forms.
362  * Ugh.
363  */
364 static void
365 Dgetdol()
366 {
367     register Char *np;
368     register struct varent *vp = NULL;
369     Char    name[4 * MAXVARLEN + 1];
370     int     c, sc;
371     int     subscr = 0, lwb = 1, upb = 0;
372     bool    dimen = 0, bitset = 0;
373     char    tnp;
374     Char    wbuf[BUFSIZ];
375 
376     dolmod = dolmcnt = 0;
377     c = sc = DgetC(0);
378     if (c == '{')
379 	c = DgetC(0);		/* sc is { to take } later */
380     if ((c & TRIM) == '#')
381 	dimen++, c = DgetC(0);	/* $# takes dimension */
382     else if (c == '?')
383 	bitset++, c = DgetC(0);	/* $? tests existence */
384     switch (c) {
385 
386     case '$':
387 	if (dimen || bitset)
388 	    stderror(ERR_SYNTAX);
389 	setDolp(doldol);
390 	goto eatbrac;
391 
392     case '<' | QUOTE:
393 	if (bitset)
394 	    stderror(ERR_NOTALLOWED, "$?<");
395 	if (dimen)
396 	    stderror(ERR_NOTALLOWED, "$?#");
397 	for (np = wbuf; read(OLDSTD, &tnp, 1) == 1; np++) {
398 	    *np = tnp;
399 	    if (np >= &wbuf[BUFSIZ - 1])
400 		stderror(ERR_LTOOLONG);
401 	    if (SIGN_EXTEND_CHAR(tnp) <= 0 || tnp == '\n')
402 		break;
403 	}
404 	*np = 0;
405 	/*
406 	 * KLUDGE: dolmod is set here because it will cause setDolp to call
407 	 * domod and thus to copy wbuf. Otherwise setDolp would use it
408 	 * directly. If we saved it ourselves, no one would know when to free
409 	 * it. The actual function of the 'q' causes filename expansion not to
410 	 * be done on the interpolated value.
411 	 */
412 	dolmod = 'q';
413 	dolmcnt = 10000;
414 	setDolp(wbuf);
415 	goto eatbrac;
416 
417     case DEOF:
418     case '\n':
419 	stderror(ERR_SYNTAX);
420 	/* NOTREACHED */
421 	break;
422 
423     case '*':
424 	(void) Strcpy(name, STRargv);
425 	vp = adrof(STRargv);
426 	subscr = -1;		/* Prevent eating [...] */
427 	break;
428 
429     default:
430 	np = name;
431 	if (Isdigit(c)) {
432 	    if (dimen)
433 		stderror(ERR_NOTALLOWED, "$#<num>");
434 	    subscr = 0;
435 	    do {
436 		subscr = subscr * 10 + c - '0';
437 		c = DgetC(0);
438 	    } while (Isdigit(c));
439 	    unDredc(c);
440 	    if (subscr < 0) {
441 		dolerror(vp->v_name);
442 		return;
443 	    }
444 	    if (subscr == 0) {
445 		if (bitset) {
446 		    dolp = ffile ? STR1 : STR0;
447 		    goto eatbrac;
448 		}
449 		if (ffile == 0)
450 		    stderror(ERR_DOLZERO);
451 		fixDolMod();
452 		setDolp(ffile);
453 		goto eatbrac;
454 	    }
455 	    if (bitset)
456 		stderror(ERR_DOLQUEST);
457 	    vp = adrof(STRargv);
458 	    if (vp == 0) {
459 		vp = &nulargv;
460 		goto eatmod;
461 	    }
462 	    break;
463 	}
464 	if (!alnum(c))
465 	    stderror(ERR_VARALNUM);
466 	for (;;) {
467 	    *np++ = c;
468 	    c = DgetC(0);
469 	    if (!alnum(c))
470 		break;
471 	    if (np >= &name[MAXVARLEN])
472 		stderror(ERR_VARTOOLONG);
473 	}
474 	*np++ = 0;
475 	unDredc(c);
476 	vp = adrof(name);
477     }
478     if (bitset) {
479 	dolp = (vp || getenv(short2str(name))) ? STR1 : STR0;
480 	goto eatbrac;
481     }
482     if (vp == 0) {
483 	np = str2short(getenv(short2str(name)));
484 	if (np) {
485 	    fixDolMod();
486 	    setDolp(np);
487 	    goto eatbrac;
488 	}
489 	udvar(name);
490 	/* NOTREACHED */
491     }
492     c = DgetC(0);
493     upb = blklen(vp->vec);
494     if (dimen == 0 && subscr == 0 && c == '[') {
495 	np = name;
496 	for (;;) {
497 	    c = DgetC(DODOL);	/* Allow $ expand within [ ] */
498 	    if (c == ']')
499 		break;
500 	    if (c == '\n' || c == DEOF)
501 		stderror(ERR_INCBR);
502 	    if (np >= &name[sizeof(name) / sizeof(Char) - 2])
503 		stderror(ERR_VARTOOLONG);
504 	    *np++ = c;
505 	}
506 	*np = 0, np = name;
507 	if (dolp || dolcnt)	/* $ exp must end before ] */
508 	    stderror(ERR_EXPORD);
509 	if (!*np)
510 	    stderror(ERR_SYNTAX);
511 	if (Isdigit(*np)) {
512 	    int     i;
513 
514 	    for (i = 0; Isdigit(*np); i = i * 10 + *np++ - '0');
515 	    if ((i < 0 || i > upb) && !any("-*", *np)) {
516 		dolerror(vp->v_name);
517 		return;
518 	    }
519 	    lwb = i;
520 	    if (!*np)
521 		upb = lwb, np = STRstar;
522 	}
523 	if (*np == '*')
524 	    np++;
525 	else if (*np != '-')
526 	    stderror(ERR_MISSING, '-');
527 	else {
528 	    register int i = upb;
529 
530 	    np++;
531 	    if (Isdigit(*np)) {
532 		i = 0;
533 		while (Isdigit(*np))
534 		    i = i * 10 + *np++ - '0';
535 		if (i < 0 || i > upb) {
536 		    dolerror(vp->v_name);
537 		    return;
538 		}
539 	    }
540 	    if (i < lwb)
541 		upb = lwb - 1;
542 	    else
543 		upb = i;
544 	}
545 	if (lwb == 0) {
546 	    if (upb != 0) {
547 		dolerror(vp->v_name);
548 		return;
549 	    }
550 	    upb = -1;
551 	}
552 	if (*np)
553 	    stderror(ERR_SYNTAX);
554     }
555     else {
556 	if (subscr > 0)
557 	    if (subscr > upb)
558 		lwb = 1, upb = 0;
559 	    else
560 		lwb = upb = subscr;
561 	unDredc(c);
562     }
563     if (dimen) {
564 	Char   *cp = putn(upb - lwb + 1);
565 
566 	addla(cp);
567 	xfree((ptr_t) cp);
568     }
569     else {
570 eatmod:
571 	fixDolMod();
572 	dolnxt = &vp->vec[lwb - 1];
573 	dolcnt = upb - lwb + 1;
574     }
575 eatbrac:
576     if (sc == '{') {
577 	c = Dredc();
578 	if (c != '}')
579 	    stderror(ERR_MISSING, '}');
580     }
581 }
582 
583 static void
584 fixDolMod()
585 {
586     register int c;
587 
588     c = DgetC(0);
589     if (c == ':') {
590 	c = DgetC(0), dolmcnt = 1;
591 	if (c == 'g')
592 	    c = DgetC(0), dolmcnt = 10000;
593 	if (!any("htrqxe", c))
594 	    stderror(ERR_BADMOD, c);
595 	dolmod = c;
596 	if (c == 'q')
597 	    dolmcnt = 10000;
598     }
599     else
600 	unDredc(c);
601 }
602 
603 static void
604 setDolp(cp)
605     register Char *cp;
606 {
607     register Char *dp;
608 
609     if (dolmod == 0 || dolmcnt == 0) {
610 	dolp = cp;
611 	return;
612     }
613     dp = domod(cp, dolmod);
614     if (dp) {
615 	dolmcnt--;
616 	addla(dp);
617 	xfree((ptr_t) dp);
618     }
619     else
620 	addla(cp);
621     dolp = STRNULL;
622     if (seterr)
623 	stderror(ERR_OLD);
624 }
625 
626 static void
627 unDredc(c)
628     int     c;
629 {
630 
631     Dpeekrd = c;
632 }
633 
634 static int
635 Dredc()
636 {
637     register int c;
638 
639     if (c = Dpeekrd) {
640 	Dpeekrd = 0;
641 	return (c);
642     }
643     if (Dcp && (c = *Dcp++))
644 	return (c & (QUOTE | TRIM));
645     if (*Dvp == 0) {
646 	Dcp = 0;
647 	return (DEOF);
648     }
649     Dcp = *Dvp++;
650     return (' ');
651 }
652 
653 static void
654 Dtestq(c)
655     register int c;
656 {
657 
658     if (cmap(c, QUOTES))
659 	gflag = 1;
660 }
661 
662 /*
663  * Form a shell temporary file (in unit 0) from the words
664  * of the shell input up to EOF or a line the same as "term".
665  * Unit 0 should have been closed before this call.
666  */
667 void
668 heredoc(term)
669     Char   *term;
670 {
671     register int c;
672     Char   *Dv[2];
673     Char    obuf[BUFSIZ], lbuf[BUFSIZ], mbuf[BUFSIZ];
674     int     ocnt, lcnt, mcnt;
675     register Char *lbp, *obp, *mbp;
676     Char  **vp;
677     bool    quoted;
678     char   *tmp;
679 
680     if (creat(tmp = short2str(shtemp), 0600) < 0)
681 	stderror(ERR_SYSTEM, tmp, strerror(errno));
682     (void) close(0);
683     if (open(tmp, O_RDWR) < 0) {
684 	int     oerrno = errno;
685 
686 	(void) unlink(tmp);
687 	errno = oerrno;
688 	stderror(ERR_SYSTEM, tmp, strerror(errno));
689     }
690     (void) unlink(tmp);		/* 0 0 inode! */
691     Dv[0] = term;
692     Dv[1] = NULL;
693     gflag = 0;
694     trim(Dv);
695     rscan(Dv, Dtestq);
696     quoted = gflag;
697     ocnt = BUFSIZ;
698     obp = obuf;
699     for (;;) {
700 	/*
701 	 * Read up a line
702 	 */
703 	lbp = lbuf;
704 	lcnt = BUFSIZ - 4;
705 	for (;;) {
706 	    c = readc(1);	/* 1 -> Want EOF returns */
707 	    if (c < 0 || c == '\n')
708 		break;
709 	    if (c &= TRIM) {
710 		*lbp++ = c;
711 		if (--lcnt < 0) {
712 		    setname("<<");
713 		    stderror(ERR_NAME | ERR_OVERFLOW);
714 		}
715 	    }
716 	}
717 	*lbp = 0;
718 
719 	/*
720 	 * Check for EOF or compare to terminator -- before expansion
721 	 */
722 	if (c < 0 || eq(lbuf, term)) {
723 	    (void) write(0, short2str(obuf), (size_t) (BUFSIZ - ocnt));
724 	    (void) lseek(0, 0l, L_SET);
725 	    return;
726 	}
727 
728 	/*
729 	 * If term was quoted or -n just pass it on
730 	 */
731 	if (quoted || noexec) {
732 	    *lbp++ = '\n';
733 	    *lbp = 0;
734 	    for (lbp = lbuf; c = *lbp++;) {
735 		*obp++ = c;
736 		if (--ocnt == 0) {
737 		    (void) write(0, short2str(obuf), BUFSIZ);
738 		    obp = obuf;
739 		    ocnt = BUFSIZ;
740 		}
741 	    }
742 	    continue;
743 	}
744 
745 	/*
746 	 * Term wasn't quoted so variable and then command expand the input
747 	 * line
748 	 */
749 	Dcp = lbuf;
750 	Dvp = Dv + 1;
751 	mbp = mbuf;
752 	mcnt = BUFSIZ - 4;
753 	for (;;) {
754 	    c = DgetC(DODOL);
755 	    if (c == DEOF)
756 		break;
757 	    if ((c &= TRIM) == 0)
758 		continue;
759 	    /* \ quotes \ $ ` here */
760 	    if (c == '\\') {
761 		c = DgetC(0);
762 		if (!any("$\\`", c))
763 		    unDgetC(c | QUOTE), c = '\\';
764 		else
765 		    c |= QUOTE;
766 	    }
767 	    *mbp++ = c;
768 	    if (--mcnt == 0) {
769 		setname("<<");
770 		stderror(ERR_NAME | ERR_OVERFLOW);
771 	    }
772 	}
773 	*mbp++ = 0;
774 
775 	/*
776 	 * If any ` in line do command substitution
777 	 */
778 	mbp = mbuf;
779 	if (any(short2str(mbp), '`')) {
780 	    /*
781 	     * 1 arg to dobackp causes substitution to be literal. Words are
782 	     * broken only at newlines so that all blanks and tabs are
783 	     * preserved.  Blank lines (null words) are not discarded.
784 	     */
785 	    vp = dobackp(mbuf, 1);
786 	}
787 	else
788 	    /* Setup trivial vector similar to return of dobackp */
789 	    Dv[0] = mbp, Dv[1] = NULL, vp = Dv;
790 
791 	/*
792 	 * Resurrect the words from the command substitution each separated by
793 	 * a newline.  Note that the last newline of a command substitution
794 	 * will have been discarded, but we put a newline after the last word
795 	 * because this represents the newline after the last input line!
796 	 */
797 	for (; *vp; vp++) {
798 	    for (mbp = *vp; *mbp; mbp++) {
799 		*obp++ = *mbp & TRIM;
800 		if (--ocnt == 0) {
801 		    (void) write(0, short2str(obuf), BUFSIZ);
802 		    obp = obuf;
803 		    ocnt = BUFSIZ;
804 		}
805 	    }
806 	    *obp++ = '\n';
807 	    if (--ocnt == 0) {
808 		(void) write(0, short2str(obuf), BUFSIZ);
809 		obp = obuf;
810 		ocnt = BUFSIZ;
811 	    }
812 	}
813 	if (pargv)
814 	    blkfree(pargv), pargv = 0;
815     }
816 }
817