xref: /dragonfly/contrib/tcsh-6/sh.dol.c (revision b97fef05)
1 /*
2  * sh.dol.c: Variable substitutions
3  */
4 /*-
5  * Copyright (c) 1980, 1991 The Regents of the University of California.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 #include "sh.h"
33 #include "ed.h"
34 #include "tw.h"
35 
36 /*
37  * C shell
38  */
39 
40 /*
41  * These routines perform variable substitution and quoting via ' and ".
42  * To this point these constructs have been preserved in the divided
43  * input words.  Here we expand variables and turn quoting via ' and " into
44  * QUOTE bits on characters (which prevent further interpretation).
45  * If the `:q' modifier was applied during history expansion, then
46  * some QUOTEing may have occurred already, so we dont "trim()" here.
47  */
48 
49 static eChar Dpeekc;		/* Peek for DgetC */
50 static eChar Dpeekrd;		/* Peek for Dreadc */
51 static Char *Dcp, *const *Dvp;	/* Input vector for Dreadc */
52 
53 #define	DEOF	CHAR_ERR
54 
55 #define	unDgetC(c)	Dpeekc = c
56 
57 #define QUOTES		(_QF|_QB|_ESC)	/* \ ' " ` */
58 
59 /*
60  * The following variables give the information about the current
61  * $ expansion, recording the current word position, the remaining
62  * words within this expansion, the count of remaining words, and the
63  * information about any : modifier which is being applied.
64  */
65 static Char *dolp;		/* Remaining chars from this word */
66 static Char **dolnxt;		/* Further words */
67 static int dolcnt;		/* Count of further words */
68 static struct Strbuf dolmod; /* = Strbuf_INIT; : modifier characters */
69 
70 static int ndolflags;		/* keep track of mod counts for each modifier */
71 static int *dolmcnts;		/* :gx -> INT_MAX, else 1 */
72 static int *dolaflags;		/* :ax -> 1, else 0 */
73 
74 static	Char	 **Dfix2	(Char *const *);
75 static	int 	 Dpack		(struct Strbuf *);
76 static	int	 Dword		(struct blk_buf *);
77 static	void	 dolerror	(Char *);
78 static	eChar	 DgetC		(int);
79 static	void	 Dgetdol	(void);
80 static	void	 fixDolMod	(void);
81 static	void	 setDolp	(Char *);
82 static	void	 unDredc	(eChar);
83 static	eChar	 Dredc		(void);
84 static	void	 Dtestq		(Char);
85 
86 /*
87  * Fix up the $ expansions and quotations in the
88  * argument list to command t.
89  */
90 void
91 Dfix(struct command *t)
92 {
93     Char **pp;
94     Char *p;
95 
96     if (noexec)
97 	return;
98     /* Note that t_dcom isn't trimmed thus !...:q's aren't lost */
99     for (pp = t->t_dcom; (p = *pp++) != NULL;) {
100 	for (; *p; p++) {
101 	    if (cmap(*p, _DOL | QUOTES)) {	/* $, \, ', ", ` */
102 		Char **expanded;
103 
104 		expanded = Dfix2(t->t_dcom);	/* found one */
105 		blkfree(t->t_dcom);
106 		t->t_dcom = expanded;
107 		return;
108 	    }
109 	}
110     }
111 }
112 
113 /*
114  * $ substitute one word, for i/o redirection
115  */
116 Char   *
117 Dfix1(Char *cp)
118 {
119     Char *Dv[2], **expanded;
120 
121     if (noexec)
122 	return (0);
123     Dv[0] = cp;
124     Dv[1] = NULL;
125     expanded = Dfix2(Dv);
126     if (expanded[0] == NULL || expanded[1] != NULL) {
127 	blkfree(expanded);
128 	setname(short2str(cp));
129 	stderror(ERR_NAME | ERR_AMBIG);
130     }
131     cp = Strsave(expanded[0]);
132     blkfree(expanded);
133     return (cp);
134 }
135 
136 /*
137  * Subroutine to do actual fixing after state initialization.
138  */
139 static Char **
140 Dfix2(Char *const *v)
141 {
142     struct blk_buf *bb = bb_alloc();
143     Char **vec;
144 
145     Dvp = v;
146     Dcp = STRNULL;		/* Setup input vector for Dreadc */
147     unDgetC(0);
148     unDredc(0);			/* Clear out any old peeks (at error) */
149     dolp = 0;
150     dolcnt = 0;			/* Clear out residual $ expands (...) */
151     cleanup_push(bb, bb_free);
152     while (Dword(bb))
153 	continue;
154     cleanup_ignore(bb);
155     cleanup_until(bb);
156     vec = bb_finish(bb);
157     xfree(bb);
158     return vec;
159 }
160 
161 /*
162  * Pack up more characters in this word
163  */
164 static int
165 Dpack(struct Strbuf *wbuf)
166 {
167     eChar c;
168 
169     for (;;) {
170 	c = DgetC(DODOL);
171 	if (c == '\\') {
172 	    c = DgetC(0);
173 	    if (c == DEOF) {
174 		unDredc(c);
175 		return 1;
176 	    }
177 	    if (c == '\n')
178 		c = ' ';
179 	    else
180 		c |= QUOTE;
181 	}
182 	if (c == DEOF) {
183 	    unDredc(c);
184 	    return 1;
185 	}
186 	if (cmap(c, _SP | _NL | _QF | _QB)) {	/* sp \t\n'"` */
187 	    unDgetC(c);
188 	    if (cmap(c, QUOTES))
189 		return 0;
190 	    return 1;
191 	}
192 	Strbuf_append1(wbuf, (Char) c);
193     }
194 }
195 
196 /*
197  * Get a word.  This routine is analogous to the routine
198  * word() in sh.lex.c for the main lexical input.  One difference
199  * here is that we don't get a newline to terminate our expansion.
200  * Rather, DgetC will return a DEOF when we hit the end-of-input.
201  */
202 static int
203 Dword(struct blk_buf *bb)
204 {
205     eChar c, c1;
206     struct Strbuf *wbuf = Strbuf_alloc();
207     int dolflg;
208     int    sofar = 0;
209     Char *str;
210 
211     cleanup_push(wbuf, Strbuf_free);
212     for (;;) {
213 	c = DgetC(DODOL);
214 	switch (c) {
215 
216 	case DEOF:
217 	    if (sofar == 0) {
218 		cleanup_until(wbuf);
219 		return (0);
220 	    }
221 	    /* finish this word and catch the code above the next time */
222 	    unDredc(c);
223 	    /*FALLTHROUGH*/
224 
225 	case '\n':
226 	    goto end;
227 
228 	case ' ':
229 	case '\t':
230 	    continue;
231 
232 	case '`':
233 	    /* We preserve ` quotations which are done yet later */
234 	    Strbuf_append1(wbuf, (Char) c);
235 	    /*FALLTHROUGH*/
236 	case '\'':
237 	case '"':
238 	    /*
239 	     * Note that DgetC never returns a QUOTES character from an
240 	     * expansion, so only true input quotes will get us here or out.
241 	     */
242 	    c1 = c;
243 	    dolflg = c1 == '"' ? DODOL : 0;
244 	    for (;;) {
245 		c = DgetC(dolflg);
246 		if (c == c1)
247 		    break;
248 		if (c == '\n' || c == DEOF) {
249 		    cleanup_until(bb);
250 		    stderror(ERR_UNMATCHED, (int)c1);
251 		}
252 		if ((c & (QUOTE | TRIM)) == ('\n' | QUOTE)) {
253 		    if (wbuf->len != 0 && (wbuf->s[wbuf->len - 1] & TRIM) == '\\')
254 			wbuf->len--;
255 		}
256 		switch (c1) {
257 
258 		case '"':
259 		    /*
260 		     * Leave any `s alone for later. Other chars are all
261 		     * quoted, thus `...` can tell it was within "...".
262 		     */
263 		    Strbuf_append1(wbuf, c == '`' ? '`' : c | QUOTE);
264 		    break;
265 
266 		case '\'':
267 		    /* Prevent all further interpretation */
268 		    Strbuf_append1(wbuf, c | QUOTE);
269 		    break;
270 
271 		case '`':
272 		    /* Leave all text alone for later */
273 		    Strbuf_append1(wbuf, (Char) c);
274 		    break;
275 
276 		default:
277 		    break;
278 		}
279 	    }
280 	    if (c1 == '`')
281 		Strbuf_append1(wbuf, '`');
282 	    sofar = 1;
283 	    if (Dpack(wbuf) != 0)
284 		goto end;
285 	    continue;
286 
287 	case '\\':
288 	    c = DgetC(0);	/* No $ subst! */
289 	    if (c == '\n' || c == DEOF)
290 		continue;
291 	    c |= QUOTE;
292 	    break;
293 
294 	default:
295 	    break;
296 	}
297 	unDgetC(c);
298 	sofar = 1;
299 	if (Dpack(wbuf) != 0)
300 	    goto end;
301     }
302 
303  end:
304     cleanup_ignore(wbuf);
305     cleanup_until(wbuf);
306     str = Strbuf_finish(wbuf);
307     bb_append(bb, str);
308     xfree(wbuf);
309     return 1;
310 }
311 
312 
313 /*
314  * Get a character, performing $ substitution unless flag is 0.
315  * Any QUOTES character which is returned from a $ expansion is
316  * QUOTEd so that it will not be recognized above.
317  */
318 static eChar
319 DgetC(int flag)
320 {
321     eChar c;
322 
323 top:
324     if ((c = Dpeekc) != 0) {
325 	Dpeekc = 0;
326 	return (c);
327     }
328     if (lap < labuf.len) {
329 	c = labuf.s[lap++] & (QUOTE | TRIM);
330 quotspec:
331 	if (cmap(c, QUOTES))
332 	    return (c | QUOTE);
333 	return (c);
334     }
335     if (dolp) {
336 	if ((c = *dolp++ & (QUOTE | TRIM)) != 0)
337 	    goto quotspec;
338 	if (dolcnt > 0) {
339 	    setDolp(*dolnxt++);
340 	    --dolcnt;
341 	    return (' ');
342 	}
343 	dolp = 0;
344     }
345     if (dolcnt > 0) {
346 	setDolp(*dolnxt++);
347 	--dolcnt;
348 	goto top;
349     }
350     c = Dredc();
351     if (c == '$' && flag) {
352 	Dgetdol();
353 	goto top;
354     }
355     return (c);
356 }
357 
358 static Char *nulvec[] = { NULL };
359 static struct varent nulargv = {nulvec, STRargv, VAR_READWRITE,
360 				{ NULL, NULL, NULL }, 0 };
361 
362 static void
363 dolerror(Char *s)
364 {
365     setname(short2str(s));
366     stderror(ERR_NAME | ERR_RANGE);
367 }
368 
369 /*
370  * Handle the multitudinous $ expansion forms.
371  * Ugh.
372  */
373 static void
374 Dgetdol(void)
375 {
376     Char *np;
377     struct varent *vp = NULL;
378     struct Strbuf *name = Strbuf_alloc();
379     eChar   c, sc;
380     int     subscr = 0, lwb = 1, upb = 0;
381     int    dimen = 0, bitset = 0, length = 0;
382     static Char *dolbang = NULL;
383 
384     cleanup_push(name, Strbuf_free);
385     dolmod.len = ndolflags = 0;
386     c = sc = DgetC(0);
387     if (c == DEOF) {
388       stderror(ERR_SYNTAX);
389       return;
390     }
391     if ((c & TRIM) == '\'') {
392 	const Char *cp;
393 	struct Strbuf *expanded = Strbuf_alloc();
394 
395 	cleanup_push(expanded, Strbuf_free);
396 	for (;;) {
397 	    c = DgetC(0);
398 	    if ((c & TRIM) == '\'')
399 		break;
400 	    if ((c & TRIM) == '\\') {
401 		Strbuf_append1(name, (Char) c);
402 		c = DgetC(0);
403 	    }
404 	    if (c == '\n' || c == DEOF) {
405 		cleanup_until(name);
406 		stderror(ERR_MISSING, '\'');
407 		return;
408 	    }
409 	    Strbuf_append1(name, (Char) c);
410 	}
411 	Strbuf_terminate(name);
412 	for (cp = name->s; (c = *cp) != 0; cp++) {
413 	    if (c == '\\' && (c = parseescape(&cp, TRUE)) == CHAR_ERR)
414 		c = '\\';
415 	    Strbuf_append1(expanded, (Char) c | QUOTE);
416 	}
417 	Strbuf_terminate(expanded);
418 	np = Strsave(expanded->s);
419 	cleanup_until(name);
420 	addla(np);
421 	return;
422     }
423     if (c == '{')
424 	c = DgetC(0);		/* sc is { to take } later */
425     if ((c & TRIM) == '#')
426 	dimen++, c = DgetC(0);	/* $# takes dimension */
427     else if (c == '?')
428 	bitset++, c = DgetC(0);	/* $? tests existence */
429     else if (c == '%')
430 	length++, c = DgetC(0); /* $% returns length in chars */
431     switch (c) {
432 
433     case '!':
434 	if (dimen || bitset || length)
435 	    stderror(ERR_SYNTAX);
436 	if (backpid != 0) {
437 	    xfree(dolbang);
438 	    setDolp(dolbang = putn((tcsh_number_t)backpid));
439 	}
440 	cleanup_until(name);
441 	goto eatbrac;
442 
443     case '$':
444 	if (dimen || bitset || length)
445 	    stderror(ERR_SYNTAX);
446 	setDolp(doldol);
447 	cleanup_until(name);
448 	goto eatbrac;
449 
450     case '<'|QUOTE: {
451 	static struct Strbuf wbuf; /* = Strbuf_INIT; */
452 
453 	if (bitset)
454 	    stderror(ERR_NOTALLOWED, "$?<");
455 	if (dimen)
456 	    stderror(ERR_NOTALLOWED, "$#<");
457 	if (length)
458 	    stderror(ERR_NOTALLOWED, "$%<");
459 	wbuf.len = 0;
460 	{
461 	    char cbuf[MB_LEN_MAX];
462 	    size_t cbp = 0;
463 	    int old_pintr_disabled;
464 
465 	    for (;;) {
466 	        int len;
467 		ssize_t res;
468 		Char wc;
469 
470 		pintr_push_enable(&old_pintr_disabled);
471 		res = force_read(OLDSTD, cbuf + cbp, 1);
472 		cleanup_until(&old_pintr_disabled);
473 		if (res != 1)
474 		    break;
475 		cbp++;
476 		len = normal_mbtowc(&wc, cbuf, cbp);
477 		if (len == -1) {
478 		    reset_mbtowc();
479 		    if (cbp < MB_LEN_MAX)
480 		        continue; /* Maybe a partial character */
481 		    wc = (unsigned char)*cbuf | INVALID_BYTE;
482 		}
483 		if (len <= 0)
484 		    len = 1;
485 		if (cbp != (size_t)len)
486 		    memmove(cbuf, cbuf + len, cbp - len);
487 		cbp -= len;
488 		if (wc == '\n')
489 		    break;
490 		Strbuf_append1(&wbuf, wc);
491 	    }
492 	    while (cbp != 0) {
493 		int len;
494 		Char wc;
495 
496 		len = normal_mbtowc(&wc, cbuf, cbp);
497 		if (len == -1) {
498 		    reset_mbtowc();
499 		    wc = (unsigned char)*cbuf | INVALID_BYTE;
500 		}
501 		if (len <= 0)
502 		    len = 1;
503 		if (cbp != (size_t)len)
504 		    memmove(cbuf, cbuf + len, cbp - len);
505 		cbp -= len;
506 		if (wc == '\n')
507 		    break;
508 		Strbuf_append1(&wbuf, wc);
509 	    }
510 	    Strbuf_terminate(&wbuf);
511 	}
512 
513 	fixDolMod();
514 	setDolp(wbuf.s); /* Kept allocated until next $< expansion */
515 	cleanup_until(name);
516 	goto eatbrac;
517     }
518 
519     case '*':
520 	Strbuf_append(name, STRargv);
521 	Strbuf_terminate(name);
522 	vp = adrof(STRargv);
523 	subscr = -1;		/* Prevent eating [...] */
524 	break;
525 
526     case DEOF:
527     case '\n':
528 	np = dimen ? STRargv : (bitset ? STRstatus : NULL);
529 	if (np) {
530 	    bitset = 0;
531 	    Strbuf_append(name, np);
532 	    Strbuf_terminate(name);
533 	    vp = adrof(np);
534 	    subscr = -1;		/* Prevent eating [...] */
535 	    unDredc(c);
536 	    break;
537 	}
538 	else
539 	    stderror(ERR_SYNTAX);
540 	/*NOTREACHED*/
541 
542     default:
543 	if (Isdigit(c)) {
544 	    if (dimen)
545 		stderror(ERR_NOTALLOWED, "$#<num>");
546 	    subscr = 0;
547 	    do {
548 		subscr = subscr * 10 + c - '0';
549 		c = DgetC(0);
550 	    } while (c != DEOF && Isdigit(c));
551 	    unDredc(c);
552 	    if (subscr < 0)
553 		stderror(ERR_RANGE);
554 	    if (subscr == 0) {
555 		if (bitset) {
556 		    dolp = dolzero ? STR1 : STR0;
557 		    cleanup_until(name);
558 		    goto eatbrac;
559 		}
560 		if (ffile == 0)
561 		    stderror(ERR_DOLZERO);
562 		if (length) {
563 		    length = Strlen(ffile);
564 		    addla(putn((tcsh_number_t)length));
565 		}
566 		else {
567 		    fixDolMod();
568 		    setDolp(ffile);
569 		}
570 		cleanup_until(name);
571 		goto eatbrac;
572 	    }
573 #if 0
574 	    if (bitset)
575 		stderror(ERR_NOTALLOWED, "$?<num>");
576 	    if (length)
577 		stderror(ERR_NOTALLOWED, "$%<num>");
578 #endif
579 	    vp = adrof(STRargv);
580 	    if (vp == 0) {
581 		vp = &nulargv;
582 		cleanup_until(name);
583 		goto eatmod;
584 	    }
585 	    break;
586 	}
587 	if (c == DEOF || !alnum(c)) {
588 	    np = dimen ? STRargv : (bitset ? STRstatus : NULL);
589 	    if (np) {
590 		bitset = 0;
591 		Strbuf_append(name, np);
592 		Strbuf_terminate(name);
593 		vp = adrof(np);
594 		subscr = -1;		/* Prevent eating [...] */
595 		unDredc(c);
596 		break;
597 	    }
598 	    else
599 		stderror(ERR_VARALNUM);
600 	}
601 	for (;;) {
602 	    Strbuf_append1(name, (Char) c);
603 	    c = DgetC(0);
604 	    if (c == DEOF || !alnum(c))
605 		break;
606 	}
607 	Strbuf_terminate(name);
608 	unDredc(c);
609 	vp = adrof(name->s);
610     }
611     if (bitset) {
612 	dolp = (vp || getenv(short2str(name->s))) ? STR1 : STR0;
613 	cleanup_until(name);
614 	goto eatbrac;
615     }
616     if (vp == NULL || vp->vec == NULL) {
617 	np = str2short(getenv(short2str(name->s)));
618 	if (np) {
619 	    static Char *env_val; /* = NULL; */
620 
621 	    cleanup_until(name);
622 	    fixDolMod();
623 	    if (length) {
624 		    addla(putn((tcsh_number_t)Strlen(np)));
625 	    } else {
626 		    xfree(env_val);
627 		    env_val = Strsave(np);
628 		    setDolp(env_val);
629 	    }
630 	    goto eatbrac;
631 	}
632 	udvar(name->s);
633 	/* NOTREACHED */
634     }
635     cleanup_until(name);
636     c = DgetC(0);
637     upb = blklen(vp->vec);
638     if (dimen == 0 && subscr == 0 && c == '[') {
639 	name = Strbuf_alloc();
640 	cleanup_push(name, Strbuf_free);
641 	np = name->s;
642 	for (;;) {
643 	    c = DgetC(DODOL);	/* Allow $ expand within [ ] */
644 	    if (c == ']')
645 		break;
646 	    if (c == '\n' || c == DEOF)
647 		stderror(ERR_INCBR);
648 	    Strbuf_append1(name, (Char) c);
649 	}
650 	Strbuf_terminate(name);
651 	np = name->s;
652 	if (dolp || dolcnt)	/* $ exp must end before ] */
653 	    stderror(ERR_EXPORD);
654 	if (!*np)
655 	    stderror(ERR_SYNTAX);
656 	if (Isdigit(*np)) {
657 	    int     i;
658 
659 	    for (i = 0; Isdigit(*np); i = i * 10 + *np++ - '0')
660 		continue;
661 	    if (i < 0 || (i > upb && !any("-*", *np))) {
662 		cleanup_until(name);
663 		dolerror(vp->v_name);
664 		return;
665 	    }
666 	    lwb = i;
667 	    if (!*np)
668 		upb = lwb, np = STRstar;
669 	}
670 	if (*np == '*')
671 	    np++;
672 	else if (*np != '-')
673 	    stderror(ERR_MISSING, '-');
674 	else {
675 	    int i = upb;
676 
677 	    np++;
678 	    if (Isdigit(*np)) {
679 		i = 0;
680 		while (Isdigit(*np))
681 		    i = i * 10 + *np++ - '0';
682 		if (i < 0 || i > upb) {
683 		    cleanup_until(name);
684 		    dolerror(vp->v_name);
685 		    return;
686 		}
687 	    }
688 	    if (i < lwb)
689 		upb = lwb - 1;
690 	    else
691 		upb = i;
692 	}
693 	if (lwb == 0) {
694 	    if (upb != 0) {
695 		cleanup_until(name);
696 		dolerror(vp->v_name);
697 		return;
698 	    }
699 	    upb = -1;
700 	}
701 	if (*np)
702 	    stderror(ERR_SYNTAX);
703 	cleanup_until(name);
704     }
705     else {
706 	if (subscr > 0) {
707 	    if (subscr > upb)
708 		lwb = 1, upb = 0;
709 	    else
710 		lwb = upb = subscr;
711 	}
712 	unDredc(c);
713     }
714     if (dimen) {
715 	/* this is a kludge. It prevents Dgetdol() from */
716 	/* pushing erroneous ${#<error> values into the labuf. */
717 	if (sc == '{') {
718 	    c = Dredc();
719 	    if (c != '}')
720 		stderror(ERR_MISSING, '}');
721 	    unDredc(c);
722 	}
723 	addla(putn((tcsh_number_t)(upb - lwb + 1)));
724     }
725     else if (length) {
726 	int i;
727 
728 	for (i = lwb - 1, length = 0; i < upb; i++)
729 	    length += Strlen(vp->vec[i]);
730 #ifdef notdef
731 	/* We don't want that, since we can always compute it by adding $#xxx */
732 	length += i - 1;	/* Add the number of spaces in */
733 #endif
734 	addla(putn((tcsh_number_t)length));
735     }
736     else {
737 eatmod:
738 	fixDolMod();
739 	dolnxt = &vp->vec[lwb - 1];
740 	dolcnt = upb - lwb + 1;
741     }
742 eatbrac:
743     if (sc == '{') {
744 	c = Dredc();
745 	if (c != '}')
746 	    stderror(ERR_MISSING, '}');
747     }
748 }
749 
750 static void
751 fixDolMod(void)
752 {
753     eChar c;
754 
755     c = DgetC(0);
756     if (c == ':') {
757 	ndolflags = 0;
758 	do {
759 	    ++ndolflags;
760 	    dolmcnts = xrealloc(dolmcnts, ndolflags * sizeof(int));
761 	    dolaflags = xrealloc(dolaflags, ndolflags * sizeof(int));
762 	    c = DgetC(0), dolmcnts[ndolflags - 1] = 1, dolaflags[ndolflags - 1] = 0;
763 	    if (c == 'g' || c == 'a') {
764 		if (c == 'g') {
765 		    dolmcnts[ndolflags - 1] = INT_MAX;
766 		} else {
767 		    dolaflags[ndolflags - 1] = 1;
768 		}
769 		c = DgetC(0);
770 	    }
771 	    if ((c == 'g' && dolmcnts[ndolflags - 1] != INT_MAX) ||
772 		(c == 'a' && dolaflags[ndolflags - 1] == 0)) {
773 		if (c == 'g') {
774 		    dolmcnts[ndolflags - 1] = INT_MAX;
775 		} else {
776 		    dolaflags[ndolflags - 1] = 1;
777 		}
778 		c = DgetC(0);
779 	    }
780 
781 	    if (c == 's') {	/* [eichin:19910926.0755EST] */
782 		int delimcnt = 2;
783 		int esc = 0;
784 		eChar delim = DgetC(0);
785 		Strbuf_append1(&dolmod, (Char) c);
786 		Strbuf_append1(&dolmod, (Char) delim);
787 
788 		if (delim == DEOF || !delim || letter(delim)
789 		    || Isdigit(delim) || any(" \t\n", delim)) {
790 		    seterror(ERR_BADSUBST);
791 		    break;
792 		}
793 		while ((c = DgetC(0)) != DEOF) {
794 		    if (esc == 0 && c == '\\') {
795 			esc = 1;
796 			continue;
797 		    }
798 		    Strbuf_append1(&dolmod, (Char) c);
799 		    if (!esc && c == delim) delimcnt--;
800 		    if (!delimcnt) break;
801 		    esc = 0;
802 		}
803 		if (delimcnt) {
804 		    seterror(ERR_BADSUBST);
805 		    break;
806 		}
807 		continue;
808 	    }
809 	    if (!any(TCSH_MODIFIERS, c))
810 		stderror(ERR_BADMOD, (int)c);
811 	    Strbuf_append1(&dolmod, (Char) c);
812 	    if (c == 'q') {
813 		dolmcnts[ndolflags - 1] = INT_MAX;
814 	    }
815 	}
816 	while ((c = DgetC(0)) == ':');
817 	unDredc(c);
818     }
819     else
820 	unDredc(c);
821 }
822 
823 static int
824 all_dolmcnts_are_0(void)
825 {
826     int i = 0;
827     for (; i < ndolflags; ++i) {
828 	if (dolmcnts[i] != 0)
829 	    return 0;
830     }
831     return 1;
832 }
833 
834 static void
835 setDolp(Char *cp)
836 {
837     Char *dp;
838     size_t i;
839     int nthMod = 0;
840 
841     if (dolmod.len == 0 || all_dolmcnts_are_0()) {
842 	dolp = cp;
843 	return;
844     }
845     cp = Strsave(cp);
846     for (i = 0; i < dolmod.len; i++) {
847 	int didmod = 0;
848 
849 	/* handle s// [eichin:19910926.0510EST] */
850 	if (dolmod.s[i] == 's') {
851 	    Char delim;
852 	    Char *lhsub, *rhsub, *np;
853 	    size_t lhlen = 0, rhlen = 0;
854 	    /* keep track of where the last :a match hit */
855 	    ptrdiff_t last_match = 0;
856 
857 	    delim = dolmod.s[++i];
858 	    if (!delim || letter(delim)
859 		|| Isdigit(delim) || any(" \t\n", delim)) {
860 		seterror(ERR_BADSUBST);
861 		break;
862 	    }
863 	    lhsub = &dolmod.s[++i];
864 	    while (dolmod.s[i] != delim && dolmod.s[++i]) {
865 		lhlen++;
866 	    }
867 	    dolmod.s[i] = 0;
868 	    rhsub = &dolmod.s[++i];
869 	    while (dolmod.s[i] != delim && dolmod.s[++i]) {
870 		rhlen++;
871 	    }
872 	    dolmod.s[i] = 0;
873 
874 	    strip(lhsub);
875 	    strip(rhsub);
876 	    if (dolmcnts[nthMod] != 0) {
877 	        strip(cp);
878 	        dp = cp;
879 	        do {
880 	            dp = Strstr(dp + last_match, lhsub);
881 	            if (dp) {
882 	                ptrdiff_t diff = dp - cp;
883 	                size_t len = (Strlen(cp) + 1 - lhlen + rhlen);
884 	                np = xmalloc(len * sizeof(Char));
885 	                (void) Strncpy(np, cp, diff);
886 	                (void) Strcpy(np + diff, rhsub);
887 	                (void) Strcpy(np + diff + rhlen, dp + lhlen);
888 			last_match = diff + rhlen;
889 
890 	                xfree(cp);
891 	                dp = cp = np;
892 	                cp[--len] = '\0';
893 	                didmod = 1;
894 	                if (diff >= (ssize_t)len)
895 	            	break;
896 	            } else {
897 	                /* should this do a seterror? */
898 	                break;
899 	            }
900 	        }
901 	        while (dolaflags[nthMod] != 0);
902             }
903 	    /*
904 	     * restore dolmod for additional words
905 	     */
906 	    dolmod.s[i] = rhsub[-1] = (Char) delim;
907 	} else if (dolmcnts[nthMod] != 0) {
908 
909 	    do {
910 		if ((dp = domod(cp, dolmod.s[i])) != NULL) {
911 		    didmod = 1;
912 		    if (Strcmp(cp, dp) == 0) {
913 			xfree(cp);
914 			cp = dp;
915 			break;
916 		    }
917 		    else {
918 			xfree(cp);
919 			cp = dp;
920 		    }
921 		}
922 		else
923 		    break;
924 	    }
925 	    while (dolaflags[nthMod] != 0);
926 	}
927 	if (didmod && dolmcnts[nthMod] != INT_MAX)
928 	    dolmcnts[nthMod]--;
929 #ifdef notdef
930 	else
931 	    break;
932 #endif
933 
934 	++nthMod;
935     }
936 
937     addla(cp);
938 
939     dolp = STRNULL;
940     if (seterr)
941 	stderror(ERR_OLD);
942 }
943 
944 static void
945 unDredc(eChar c)
946 {
947 
948     Dpeekrd = c;
949 }
950 
951 static eChar
952 Dredc(void)
953 {
954     eChar c;
955 
956     if ((c = Dpeekrd) != 0) {
957 	Dpeekrd = 0;
958 	return (c);
959     }
960     if (Dcp && (c = *Dcp++))
961 	return (c & (QUOTE | TRIM));
962     if (*Dvp == 0) {
963 	Dcp = 0;
964 	return (DEOF);
965     }
966     Dcp = *Dvp++;
967     return (' ');
968 }
969 
970 static int gflag;
971 
972 static void
973 Dtestq(Char c)
974 {
975 
976     if (cmap(c, QUOTES))
977 	gflag = 1;
978 }
979 
980 static void
981 inheredoc_cleanup(void *dummy)
982 {
983     USE(dummy);
984     inheredoc = 0;
985 }
986 
987 Char *
988 randsuf(void) {
989 #ifndef WINNT_NATIVE
990 	struct timeval tv;
991 	(void) gettimeofday(&tv, NULL);
992 	return putn((((tcsh_number_t)tv.tv_sec) ^
993 	    ((tcsh_number_t)tv.tv_usec) ^
994 	    ((tcsh_number_t)getpid())) & 0x00ffffff);
995 #else
996     return putn(getpid());
997 #endif
998 }
999 
1000 /*
1001  * Form a shell temporary file (in unit 0) from the words
1002  * of the shell input up to EOF or a line the same as "term".
1003  * Unit 0 should have been closed before this call.
1004  */
1005 void
1006 heredoc(Char *term)
1007 {
1008     eChar  c;
1009     Char   *Dv[2];
1010     struct Strbuf lbuf = Strbuf_INIT, mbuf = Strbuf_INIT;
1011     Char    obuf[BUFSIZE + 1];
1012 #define OBUF_END (obuf + sizeof(obuf) / sizeof (*obuf) - 1)
1013     Char *lbp, *obp, *mbp;
1014     Char  **vp;
1015     int    quoted;
1016 #ifdef HAVE_MKSTEMP
1017     char   *tmp = short2str(shtemp);
1018     char   *dot = strrchr(tmp, '.');
1019 
1020     if (!dot)
1021 	stderror(ERR_NAME | ERR_NOMATCH);
1022     strcpy(dot, TMP_TEMPLATE);
1023 
1024     xclose(0);
1025     if (mkstemp(tmp) == -1)
1026 	stderror(ERR_SYSTEM, tmp, strerror(errno));
1027 #else /* !HAVE_MKSTEMP */
1028     char   *tmp;
1029 # ifndef WINNT_NATIVE
1030 
1031 again:
1032 # endif /* WINNT_NATIVE */
1033     tmp = short2str(shtemp);
1034 # if O_CREAT == 0
1035     if (xcreat(tmp, 0600) < 0)
1036 	stderror(ERR_SYSTEM, tmp, strerror(errno));
1037 # endif
1038     xclose(0);
1039     if (xopen(tmp, O_RDWR|O_CREAT|O_EXCL|O_TEMPORARY|O_LARGEFILE, 0600) ==
1040 	-1) {
1041 	int oerrno = errno;
1042 # ifndef WINNT_NATIVE
1043 	if (errno == EEXIST) {
1044 	    if (unlink(tmp) == -1) {
1045 		xfree(shtemp);
1046 		mbp = randsuf();
1047 		shtemp = Strspl(STRtmpsh, mbp);
1048 		xfree(mbp);
1049 	    }
1050 	    goto again;
1051 	}
1052 # endif /* WINNT_NATIVE */
1053 	(void) unlink(tmp);
1054 	errno = oerrno;
1055  	stderror(ERR_SYSTEM, tmp, strerror(errno));
1056     }
1057 #endif /* HAVE_MKSTEMP */
1058     (void) unlink(tmp);		/* 0 0 inode! */
1059     Dv[0] = term;
1060     Dv[1] = NULL;
1061     gflag = 0;
1062     trim(Dv);
1063     rscan(Dv, Dtestq);
1064     quoted = gflag;
1065     obp = obuf;
1066     obuf[BUFSIZE] = 0;
1067     inheredoc = 1;
1068     cleanup_push(&inheredoc, inheredoc_cleanup);
1069 #ifdef WINNT_NATIVE
1070     __dup_stdin = 1;
1071 #endif /* WINNT_NATIVE */
1072     cleanup_push(&lbuf, Strbuf_cleanup);
1073     cleanup_push(&mbuf, Strbuf_cleanup);
1074     for (;;) {
1075 	Char **words;
1076 
1077 	/*
1078 	 * Read up a line
1079 	 */
1080 	lbuf.len = 0;
1081 	for (;;) {
1082 	    c = readc(1);	/* 1 -> Want EOF returns */
1083 	    if (c == CHAR_ERR || c == '\n')
1084 		break;
1085 	    if ((c &= TRIM) != 0)
1086 		Strbuf_append1(&lbuf, (Char) c);
1087 	}
1088 	Strbuf_terminate(&lbuf);
1089 
1090 	/* Catch EOF in the middle of a line. */
1091 	if (c == CHAR_ERR && lbuf.len != 0)
1092 	    c = '\n';
1093 
1094 	/*
1095 	 * Check for EOF or compare to terminator -- before expansion
1096 	 */
1097 	if (c == CHAR_ERR || eq(lbuf.s, term))
1098 	    break;
1099 
1100 	/*
1101 	 * If term was quoted or -n just pass it on
1102 	 */
1103 	if (quoted || noexec) {
1104 	    Strbuf_append1(&lbuf, '\n');
1105 	    Strbuf_terminate(&lbuf);
1106 	    for (lbp = lbuf.s; (c = *lbp++) != 0;) {
1107 		*obp++ = (Char) c;
1108 		if (obp == OBUF_END) {
1109 		    tmp = short2str(obuf);
1110 		    (void) xwrite(0, tmp, strlen (tmp));
1111 		    obp = obuf;
1112 		}
1113 	    }
1114 	    continue;
1115 	}
1116 
1117 	/*
1118 	 * Term wasn't quoted so variable and then command expand the input
1119 	 * line
1120 	 */
1121 	Dcp = lbuf.s;
1122 	Dvp = Dv + 1;
1123 	mbuf.len = 0;
1124 	for (;;) {
1125 	    c = DgetC(DODOL);
1126 	    if (c == DEOF)
1127 		break;
1128 	    if ((c &= TRIM) == 0)
1129 		continue;
1130 	    /* \ quotes \ $ ` here */
1131 	    if (c == '\\') {
1132 		c = DgetC(0);
1133 		if (!any("$\\`", c))
1134 		    unDgetC(c | QUOTE), c = '\\';
1135 		else
1136 		    c |= QUOTE;
1137 	    }
1138 	    Strbuf_append1(&mbuf, (Char) c);
1139 	}
1140 	Strbuf_terminate(&mbuf);
1141 
1142 	/*
1143 	 * If any ` in line do command substitution
1144 	 */
1145 	mbp = mbuf.s;
1146 	if (Strchr(mbp, '`') != NULL) {
1147 	    /*
1148 	     * 1 arg to dobackp causes substitution to be literal. Words are
1149 	     * broken only at newlines so that all blanks and tabs are
1150 	     * preserved.  Blank lines (null words) are not discarded.
1151 	     */
1152 	    words = dobackp(mbp, 1);
1153 	}
1154 	else
1155 	    /* Setup trivial vector similar to return of dobackp */
1156 	    Dv[0] = mbp, Dv[1] = NULL, words = Dv;
1157 
1158 	/*
1159 	 * Resurrect the words from the command substitution each separated by
1160 	 * a newline.  Note that the last newline of a command substitution
1161 	 * will have been discarded, but we put a newline after the last word
1162 	 * because this represents the newline after the last input line!
1163 	 */
1164 	for (vp= words; *vp; vp++) {
1165 	    for (mbp = *vp; *mbp; mbp++) {
1166 		*obp++ = *mbp & TRIM;
1167 		if (obp == OBUF_END) {
1168 		    tmp = short2str(obuf);
1169 		    (void) xwrite(0, tmp, strlen (tmp));
1170 		    obp = obuf;
1171 		}
1172 	    }
1173 	    *obp++ = '\n';
1174 	    if (obp == OBUF_END) {
1175 	        tmp = short2str(obuf);
1176 		(void) xwrite(0, tmp, strlen (tmp));
1177 		obp = obuf;
1178 	    }
1179 	}
1180 	if (words != Dv)
1181 	    blkfree(words);
1182     }
1183     *obp = 0;
1184     tmp = short2str(obuf);
1185     (void) xwrite(0, tmp, strlen (tmp));
1186     (void) lseek(0, (off_t) 0, L_SET);
1187     cleanup_until(&inheredoc);
1188 }
1189