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