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