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