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