Commit | Line | Data |
---|---|---|
542541d1 BJ |
1 | /* Copyright (c) 1979 Regents of the University of California */ |
2 | #include "sh.h" | |
3 | ||
4 | /* | |
5 | * C shell | |
6 | */ | |
7 | ||
8 | /* | |
9 | * These routines perform variable substitution and quoting via ' and ". | |
10 | * To this point these constructs have been preserved in the divided | |
11 | * input words. Here we expand variables and turn quoting via ' and " into | |
12 | * QUOTE bits on characters (which prevent further interpretation). | |
13 | * If the `:q' modifier was applied during history expansion, then | |
14 | * some QUOTEing may have occurred already, so we dont "scan(,&trim)" here. | |
15 | */ | |
16 | ||
17 | int Dpeekc, Dpeekrd; /* Peeks for DgetC and Dreadc */ | |
18 | char *Dcp, **Dvp; /* Input vector for Dreadc */ | |
19 | ||
20 | #define DEOF -1 | |
21 | ||
22 | #define unDgetC(c) Dpeekc = c | |
23 | ||
24 | char *QUOTES = "\\'`\""; | |
25 | ||
26 | /* | |
27 | * The following variables give the information about the current | |
28 | * $ expansion, recording the current word position, the remaining | |
29 | * words within this expansion, the count of remaining words, and the | |
30 | * information about any : modifier which is being applied. | |
31 | */ | |
32 | char *dolp; /* Remaining chars from this word */ | |
33 | char **dolnxt; /* Further words */ | |
34 | int dolcnt; /* Count of further words */ | |
35 | char dolmod; /* : modifier character */ | |
36 | int dolmcnt; /* :gx -> 10000, else 1 */ | |
37 | ||
38 | int Dtest(); /* Test for \ " ` or ' */ | |
39 | ||
40 | /* | |
41 | * Fix up the $ expansions and quotations in the | |
42 | * argument list to command t. | |
43 | */ | |
44 | Dfix(t) | |
45 | register struct command *t; | |
46 | { | |
47 | ||
48 | if (noexec) | |
49 | return; | |
50 | gflag = 0, rscan(t->t_dcom, Dtest); | |
51 | if (gflag == 0) | |
52 | return; | |
53 | Dfix2(t->t_dcom); | |
54 | blkfree(t->t_dcom), t->t_dcom = gargv, gargv = 0; | |
55 | } | |
56 | ||
57 | /* | |
58 | * $ substitute one word, for i/o redirection | |
59 | */ | |
60 | char * | |
61 | Dfix1(cp) | |
62 | register char *cp; | |
63 | { | |
64 | char *Dv[2]; | |
65 | ||
66 | if (noexec) | |
67 | return (0); | |
68 | Dv[0] = cp; Dv[1] = NOSTR; | |
69 | Dfix2(Dv); | |
70 | if (gargc != 1) { | |
71 | setname(cp); | |
72 | bferr("Ambiguous"); | |
73 | } | |
74 | cp = savestr(gargv[0]); | |
75 | blkfree(gargv), gargv = 0; | |
76 | return (cp); | |
77 | } | |
78 | ||
79 | /* | |
80 | * Subroutine to do actual fixing after state initialization. | |
81 | */ | |
82 | Dfix2(v) | |
83 | char **v; | |
84 | { | |
85 | char *agargv[GAVSIZ]; | |
86 | ||
87 | ginit(agargv); /* Initialize glob's area pointers */ | |
88 | Dvp = v; Dcp = ""; /* Setup input vector for Dreadc */ | |
89 | unDgetC(0); unDredc(0); /* Clear out any old peeks (at error) */ | |
90 | dolp = 0; dolcnt = 0; /* Clear out residual $ expands (...) */ | |
91 | while (Dword()) | |
92 | continue; | |
93 | gargv = copyblk(gargv); | |
94 | } | |
95 | ||
96 | /* | |
97 | * Get a word. This routine is analogous to the routine | |
98 | * word() in sh.lex.c for the main lexical input. One difference | |
99 | * here is that we don't get a newline to terminate our expansion. | |
100 | * Rather, DgetC will return a DEOF when we hit the end-of-input. | |
101 | */ | |
102 | Dword() | |
103 | { | |
104 | register int c, c1; | |
105 | char wbuf[BUFSIZ]; | |
106 | register char *wp = wbuf; | |
107 | register int i = BUFSIZ - 4; | |
108 | register bool dolflg; | |
109 | bool sofar = 0; | |
110 | ||
111 | loop: | |
112 | c = DgetC(DODOL); | |
113 | switch (c) { | |
114 | ||
115 | case DEOF: | |
116 | deof: | |
117 | if (sofar == 0) | |
118 | return (0); | |
119 | /* finish this word and catch the code above the next time */ | |
120 | unDredc(c); | |
121 | /* fall into ... */ | |
122 | ||
123 | case '\n': | |
124 | *wp = 0; | |
125 | goto ret; | |
126 | ||
127 | case ' ': | |
128 | case '\t': | |
129 | goto loop; | |
130 | ||
131 | case '`': | |
132 | /* We preserve ` quotations which are done yet later */ | |
133 | *wp++ = c, --i; | |
134 | case '\'': | |
135 | case '"': | |
136 | /* | |
137 | * Note that DgetC never returns a QUOTES character | |
138 | * from an expansion, so only true input quotes will | |
139 | * get us here or out. | |
140 | */ | |
141 | c1 = c; | |
142 | dolflg = c1 == '"' ? DODOL : 0; | |
143 | for (;;) { | |
144 | c = DgetC(dolflg); | |
145 | if (c == c1) | |
146 | break; | |
147 | if (c == '\n' || c == DEOF) | |
148 | error("Unmatched %c", c1); | |
149 | if ((c & (QUOTE|TRIM)) == ('\n' | QUOTE)) | |
150 | --wp, ++i; | |
151 | if (--i <= 0) | |
152 | goto toochars; | |
153 | switch (c1) { | |
154 | ||
155 | case '"': | |
156 | /* | |
157 | * Leave any `s alone for later. | |
158 | * Other chars are all quoted, thus `...` | |
159 | * can tell it was within "...". | |
160 | */ | |
161 | *wp++ = c == '`' ? '`' : c | QUOTE; | |
162 | break; | |
163 | ||
164 | case '\'': | |
165 | /* Prevent all further interpretation */ | |
166 | *wp++ = c | QUOTE; | |
167 | break; | |
168 | ||
169 | case '`': | |
170 | /* Leave all text alone for later */ | |
171 | *wp++ = c; | |
172 | break; | |
173 | } | |
174 | } | |
175 | if (c1 == '`') | |
176 | *wp++ = '`', --i; | |
177 | goto pack; /* continue the word */ | |
178 | ||
179 | case '\\': | |
180 | c = DgetC(0); /* No $ subst! */ | |
181 | if (c == '\n' || c == DEOF) | |
182 | goto loop; | |
183 | c |= QUOTE; | |
184 | break; | |
185 | } | |
186 | unDgetC(c); | |
187 | pack: | |
188 | sofar = 1; | |
189 | /* pack up more characters in this word */ | |
190 | for (;;) { | |
191 | c = DgetC(DODOL); | |
192 | if (c == '\\') { | |
193 | c = DgetC(0); | |
194 | if (c == DEOF) | |
195 | goto deof; | |
196 | if (c == '\n') | |
197 | c = ' '; | |
198 | else | |
199 | c |= QUOTE; | |
200 | } | |
201 | if (c == DEOF) | |
202 | goto deof; | |
203 | if (any(c, " '`\"\t\n")) { | |
204 | unDgetC(c); | |
205 | if (any(c, QUOTES)) | |
206 | goto loop; | |
207 | *wp++ = 0; | |
208 | goto ret; | |
209 | } | |
210 | if (--i <= 0) | |
211 | toochars: | |
212 | error("Word too long"); | |
213 | *wp++ = c; | |
214 | } | |
215 | ret: | |
216 | Gcat("", wbuf); | |
217 | return (1); | |
218 | } | |
219 | ||
220 | /* | |
221 | * Get a character, performing $ substitution unless flag is 0. | |
222 | * Any QUOTES character which is returned from a $ expansion is | |
223 | * QUOTEd so that it will not be recognized above. | |
224 | */ | |
225 | DgetC(flag) | |
226 | register int flag; | |
227 | { | |
228 | register int c; | |
229 | ||
230 | top: | |
231 | if (c = Dpeekc) { | |
232 | Dpeekc = 0; | |
233 | return (c); | |
234 | } | |
235 | if (lap) { | |
236 | c = *lap++; | |
237 | if (c == 0) { | |
238 | lap = 0; | |
239 | goto top; | |
240 | } | |
241 | quotspec: | |
242 | if (any(c, QUOTES)) | |
243 | return (c | QUOTE); | |
244 | return (c); | |
245 | } | |
246 | if (dolp) { | |
247 | if (c = *dolp++) | |
248 | goto quotspec; | |
249 | if (dolcnt > 0) { | |
250 | setDolp(*dolnxt++); | |
251 | --dolcnt; | |
252 | return (' '); | |
253 | } | |
254 | dolp = 0; | |
255 | } | |
256 | if (dolcnt > 0) { | |
257 | setDolp(*dolnxt++); | |
258 | --dolcnt; | |
259 | goto top; | |
260 | } | |
261 | c = Dredc(); | |
262 | if (c == '$' && flag) { | |
263 | Dgetdol(); | |
264 | goto top; | |
265 | } | |
266 | return (c); | |
267 | } | |
268 | ||
269 | char *nulvec[] = { 0 }; | |
270 | struct varent nulargv = { nulvec, "argv", 0 }; | |
271 | ||
272 | /* | |
273 | * Handle the multitudinous $ expansion forms. | |
274 | * Ugh. | |
275 | */ | |
276 | Dgetdol() | |
277 | { | |
278 | register char *np; | |
279 | register struct varent *vp; | |
280 | char name[20]; | |
281 | int c, sc; | |
282 | int subscr = 0, lwb = 1, upb = 0; | |
283 | bool dimen = 0, isset = 0; | |
284 | ||
285 | dolmod = dolmcnt = 0; | |
286 | c = sc = DgetC(0); | |
287 | if (c == '{') | |
288 | c = DgetC(0); /* sc is { to take } later */ | |
289 | if ((c & TRIM) == '#') | |
290 | dimen++, c = DgetC(0); /* $# takes dimension */ | |
291 | else if (c == '?') | |
292 | isset++, c = DgetC(0); /* $? tests existence */ | |
293 | switch (c) { | |
294 | ||
295 | case '$': | |
296 | if (dimen || isset) | |
297 | goto syntax; /* No $?$, $#$ */ | |
298 | setDolp(doldol); | |
299 | goto eatbrac; | |
300 | ||
301 | case DEOF: | |
302 | case '\n': | |
303 | goto syntax; | |
304 | ||
305 | case '*': | |
306 | strcpy(name, "argv"); | |
307 | vp = adrof("argv"); | |
308 | subscr = -1; /* Prevent eating [...] */ | |
309 | break; | |
310 | ||
311 | default: | |
312 | np = name; | |
313 | if (digit(c)) { | |
314 | if (dimen) | |
315 | goto syntax; /* No $#1, e.g. */ | |
316 | subscr = 0; | |
317 | do { | |
318 | subscr = subscr * 10 + c - '0'; | |
319 | c = DgetC(0); | |
320 | } while (digit(c)); | |
321 | unDredc(c); | |
322 | if (subscr < 0) | |
323 | goto oob; | |
324 | if (subscr == 0) { | |
325 | if (isset) { | |
326 | dolp = file ? "1" : "0"; | |
327 | goto eatbrac; | |
328 | } | |
329 | if (file == 0) | |
330 | error("No file for $0"); | |
331 | setDolp(file); | |
332 | goto eatbrac; | |
333 | } | |
334 | if (isset) | |
335 | goto syntax; | |
336 | vp = adrof("argv"); | |
337 | if (vp == 0) { | |
338 | vp = &nulargv; | |
339 | goto eatmod; | |
340 | } | |
341 | break; | |
342 | } | |
343 | if (!letter(c)) | |
344 | goto syntax; | |
345 | for (;;) { | |
346 | *np++ = c; | |
347 | c = DgetC(0); | |
348 | if (!letter(c)) | |
349 | break; | |
350 | if (np >= &name[sizeof name - 2]) | |
351 | syntax: | |
352 | error("Variable syntax"); | |
353 | } | |
354 | *np++ = 0; | |
355 | unDredc(c); | |
356 | vp = adrof(name); | |
357 | } | |
358 | if (isset) { | |
359 | dolp = vp ? "1" : "0"; | |
360 | goto eatbrac; | |
361 | } | |
362 | if (vp == 0) | |
363 | udvar(name); | |
364 | c = DgetC(0); | |
365 | upb = blklen(vp->vec); | |
366 | if (dimen == 0 && subscr == 0 && c == '[') { | |
367 | np = name; | |
368 | for (;;) { | |
369 | c = DgetC(DODOL); /* Allow $ expand within [ ] */ | |
370 | if (c == ']') | |
371 | break; | |
372 | if (c == '\n' || c == DEOF) | |
373 | goto syntax; | |
374 | if (np >= &name[sizeof name - 2]) | |
375 | goto syntax; | |
376 | *np++ = c; | |
377 | } | |
378 | *np = 0, np = name; | |
379 | if (dolp || dolcnt) /* $ exp must end before ] */ | |
380 | goto syntax; | |
381 | if (!*np) | |
382 | goto syntax; | |
383 | if (digit(*np)) { | |
384 | register int i = 0; | |
385 | ||
386 | while (digit(*np)) | |
387 | i = i * 10 + *np++ - '0'; | |
388 | if ((i < 0 || i > upb) && !any(*np, "-*")) { | |
389 | oob: | |
390 | setname(vp->name); | |
391 | error("Subscript out of range"); | |
392 | } | |
393 | lwb = i; | |
394 | if (!*np) | |
395 | upb = lwb, np = "*"; | |
396 | } | |
397 | if (*np == '*') | |
398 | np++; | |
399 | else if (*np != '-') | |
400 | goto syntax; | |
401 | else { | |
402 | register int i = upb; | |
403 | ||
404 | np++; | |
405 | if (digit(*np)) { | |
406 | i = 0; | |
407 | while (digit(*np)) | |
408 | i = i * 10 + *np++ - '0'; | |
409 | if (i < 0 || i > upb) | |
410 | goto oob; | |
411 | } | |
412 | if (i < lwb) | |
413 | upb = lwb - 1; | |
414 | else | |
415 | upb = i; | |
416 | } | |
417 | if (lwb == 0) { | |
418 | if (upb != 0) | |
419 | goto oob; | |
420 | upb = -1; | |
421 | } | |
422 | if (*np) | |
423 | goto syntax; | |
424 | } else { | |
425 | if (subscr > 0) | |
426 | if (subscr > upb) | |
427 | lwb = 1, upb = 0; | |
428 | else | |
429 | lwb = upb = subscr; | |
430 | unDredc(c); | |
431 | } | |
432 | if (dimen) { | |
433 | char *cp = putn(upb - lwb + 1); | |
434 | ||
435 | addla(cp); | |
436 | xfree(cp); | |
437 | } else { | |
438 | eatmod: | |
439 | c = DgetC(0); | |
440 | if (c == ':') { | |
441 | c = DgetC(0), dolmcnt = 1; | |
442 | if (c == 'g') | |
443 | c = DgetC(0), dolmcnt = 10000; | |
444 | if (!any(c, "htrqx")) | |
445 | error("Bad : mod in $"); | |
446 | dolmod = c; | |
447 | if (c == 'q') | |
448 | dolmcnt = 10000; | |
449 | } else | |
450 | unDredc(c); | |
451 | dolnxt = &vp->vec[lwb - 1]; | |
452 | dolcnt = upb - lwb + 1; | |
453 | } | |
454 | eatbrac: | |
455 | if (sc == '{') { | |
456 | c = Dredc(); | |
457 | if (c != '}') | |
458 | goto syntax; | |
459 | } | |
460 | } | |
461 | ||
462 | setDolp(cp) | |
463 | register char *cp; | |
464 | { | |
465 | register char *dp; | |
466 | ||
467 | if (dolmod == 0 || dolmcnt == 0) { | |
468 | dolp = cp; | |
469 | return; | |
470 | } | |
471 | dp = domod(cp, dolmod); | |
472 | if (dp) { | |
473 | dolmcnt--; | |
474 | addla(dp); | |
475 | xfree(dp); | |
476 | } else | |
477 | addla(cp); | |
478 | dolp = ""; | |
479 | } | |
480 | ||
481 | unDredc(c) | |
482 | int c; | |
483 | { | |
484 | ||
485 | Dpeekrd = c; | |
486 | } | |
487 | ||
488 | Dredc() | |
489 | { | |
490 | register int c; | |
491 | ||
492 | if (c = Dpeekrd) { | |
493 | Dpeekrd = 0; | |
494 | return (c); | |
495 | } | |
496 | if (Dcp && (c = *Dcp++)) | |
497 | return (c); | |
498 | if (*Dvp == 0) { | |
499 | Dcp = 0; | |
500 | return (DEOF); | |
501 | } | |
502 | Dcp = *Dvp++; | |
503 | return (' '); | |
504 | } | |
505 | ||
506 | Dtest(c) | |
507 | register int c; | |
508 | { | |
509 | ||
510 | /* Note that c isn't trimmed thus !...:q's aren't lost */ | |
511 | if (any(c, "$\\'`\"")) | |
512 | gflag = 1; | |
513 | } | |
514 | ||
515 | Dtestq(c) | |
516 | register int c; | |
517 | { | |
518 | ||
519 | if (any(c, "\\'`\"")) | |
520 | gflag = 1; | |
521 | } | |
522 | ||
523 | /* | |
524 | * Form a shell temporary file (in unit 0) from the words | |
525 | * of the shell input up to a line the same as "term". | |
526 | * Unit 0 should have been closed before this call. | |
527 | */ | |
528 | heredoc(term) | |
529 | char *term; | |
530 | { | |
531 | register int c; | |
532 | char *Dv[2]; | |
533 | char obuf[BUFSIZ], lbuf[BUFSIZ], mbuf[BUFSIZ]; | |
534 | int ocnt, lcnt, mcnt; | |
535 | register char *lbp, *obp, *mbp; | |
536 | char **vp; | |
537 | bool quoted; | |
538 | ||
539 | if (creat(shtemp, 0600) < 0) | |
540 | Perror(shtemp); | |
541 | close(0); | |
542 | if (open(shtemp, 2) < 0) { | |
543 | int oerrno = errno; | |
544 | ||
545 | unlink(shtemp); | |
546 | errno = oerrno; | |
547 | Perror(shtemp); | |
548 | } | |
549 | unlink(shtemp); /* 0 0 inode! */ | |
550 | Dv[0] = term; Dv[1] = NOSTR; gflag = 0; | |
551 | scan(Dv, trim); rscan(Dv, Dtestq); quoted = gflag; | |
552 | ocnt = BUFSIZ; obp = obuf; | |
553 | for (;;) { | |
554 | /* | |
555 | * Read up a line | |
556 | */ | |
557 | lbp = lbuf; lcnt = BUFSIZ - 4; | |
558 | for (;;) { | |
559 | c = readc(1); /* 1 -> Want EOF returns */ | |
560 | if (c < 0) { | |
561 | setname(term); | |
562 | bferr("<< terminator not found"); | |
563 | } | |
564 | if (c == '\n') | |
565 | break; | |
566 | if (c &= TRIM) { | |
567 | *lbp++ = c; | |
568 | if (--lcnt < 0) { | |
569 | setname("<<"); | |
570 | error("Line overflow"); | |
571 | } | |
572 | } | |
573 | } | |
574 | *lbp = 0; | |
575 | ||
576 | /* | |
577 | * Compare to terminator -- before expansion | |
578 | */ | |
579 | if (eq(lbuf, term)) { | |
580 | write(0, obuf, BUFSIZ - ocnt); | |
581 | lseek(0, 0l, 0); | |
582 | return; | |
583 | } | |
584 | ||
585 | /* | |
586 | * If term was quoted or -n just pass it on | |
587 | */ | |
588 | if (quoted || noexec) { | |
589 | *lbp++ = '\n'; *lbp = 0; | |
590 | for (lbp = lbuf; c = *lbp++;) { | |
591 | *obp++ = c; | |
592 | if (--ocnt == 0) { | |
593 | write(0, obuf, BUFSIZ); | |
594 | obp = obuf; ocnt = BUFSIZ; | |
595 | } | |
596 | } | |
597 | continue; | |
598 | } | |
599 | ||
600 | /* | |
601 | * Term wasn't quoted so variable and then command | |
602 | * expand the input line | |
603 | */ | |
604 | Dcp = lbuf; Dvp = Dv + 1; mbp = mbuf; mcnt = BUFSIZ - 4; | |
605 | for (;;) { | |
606 | c = DgetC(DODOL); | |
607 | if (c == DEOF) | |
608 | break; | |
609 | if ((c &= TRIM) == 0) | |
610 | continue; | |
611 | /* \ quotes \ $ ` here */ | |
612 | if (c =='\\') { | |
613 | c = DgetC(0); | |
614 | if (!any(c, "$\\`")) | |
615 | unDgetC(c | QUOTE), c = '\\'; | |
616 | else | |
617 | c |= QUOTE; | |
618 | } | |
619 | *mbp++ = c; | |
620 | if (--mcnt == 0) { | |
621 | setname("<<"); | |
622 | bferr("Line overflow"); | |
623 | } | |
624 | } | |
625 | *mbp++ = 0; | |
626 | ||
627 | /* | |
628 | * If any ` in line do command substitution | |
629 | */ | |
630 | mbp = mbuf; | |
631 | if (any('`', mbp)) { | |
632 | /* | |
633 | * 1 arg to dobackp causes substitution to be literal. | |
634 | * Words are broken only at newlines so that all blanks | |
635 | * and tabs are preserved. Blank lines (null words) | |
636 | * are not discarded. | |
637 | */ | |
638 | vp = dobackp(mbuf, 1); | |
639 | } else | |
640 | /* Setup trivial vector similar to return of dobackp */ | |
641 | Dv[0] = mbp, Dv[1] = NOSTR, vp = Dv; | |
642 | ||
643 | /* | |
644 | * Resurrect the words from the command substitution | |
645 | * each separated by a newline. Note that the last | |
646 | * newline of a command substitution will have been | |
647 | * discarded, but we put a newline after the last word | |
648 | * because this represents the newline after the last | |
649 | * input line! | |
650 | */ | |
651 | for (; *vp; vp++) { | |
652 | for (mbp = *vp; *mbp; mbp++) { | |
653 | *obp++ = *mbp & TRIM; | |
654 | if (--ocnt == 0) { | |
655 | write(0, obuf, BUFSIZ); | |
656 | obp = obuf; ocnt = BUFSIZ; | |
657 | } | |
658 | } | |
659 | *obp++ = '\n'; | |
660 | if (--ocnt == 0) { | |
661 | write(0, obuf, BUFSIZ); | |
662 | obp = obuf; ocnt = BUFSIZ; | |
663 | } | |
664 | } | |
665 | if (pargv) | |
666 | blkfree(pargv), pargv = 0; | |
667 | } | |
668 | } |