Magellan Linux

Contents of /trunk/mkinitrd-magellan/klibc/usr/dash/expand.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 815 - (show annotations) (download)
Fri Apr 24 18:32:46 2009 UTC (15 years ago) by niro
File MIME type: text/plain
File size: 33679 byte(s)
-updated to klibc-1.5.15
1 /*-
2 * Copyright (c) 1991, 1993
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1997-2005
5 * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Kenneth Almquist.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35 #include <sys/types.h>
36 #include <sys/time.h>
37 #include <sys/stat.h>
38 #include <dirent.h>
39 #include <unistd.h>
40 #ifdef HAVE_GETPWNAM
41 #include <pwd.h>
42 #endif
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <limits.h>
46 #include <string.h>
47 #if defined(__GLIBC__)
48 #if !defined(FNMATCH_BROKEN)
49 #include <fnmatch.h>
50 #if !defined(GLOB_BROKEN)
51 #include <glob.h>
52 #endif
53 #else
54 #include <ctype.h>
55 #endif
56 #endif
57
58 /*
59 * Routines to expand arguments to commands. We have to deal with
60 * backquotes, shell variables, and file metacharacters.
61 */
62
63 #include "shell.h"
64 #include "main.h"
65 #include "nodes.h"
66 #include "eval.h"
67 #include "expand.h"
68 #include "syntax.h"
69 #include "parser.h"
70 #include "jobs.h"
71 #include "options.h"
72 #include "var.h"
73 #include "output.h"
74 #include "memalloc.h"
75 #include "error.h"
76 #include "mystring.h"
77 #include "show.h"
78 #include "system.h"
79
80 /*
81 * _rmescape() flags
82 */
83 #define RMESCAPE_ALLOC 0x1 /* Allocate a new string */
84 #define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */
85 #define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */
86 #define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */
87 #define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
88
89 /* Add CTLESC when necessary. */
90 #define QUOTES_ESC (EXP_FULL | EXP_CASE)
91 /* Do not skip NUL characters. */
92 #define QUOTES_KEEPNUL EXP_TILDE
93
94 /*
95 * Structure specifying which parts of the string should be searched
96 * for IFS characters.
97 */
98
99 struct ifsregion {
100 struct ifsregion *next; /* next region in list */
101 int begoff; /* offset of start of region */
102 int endoff; /* offset of end of region */
103 int nulonly; /* search for nul bytes only */
104 };
105
106 /* output of current string */
107 static char *expdest;
108 /* list of back quote expressions */
109 static struct nodelist *argbackq;
110 /* first struct in list of ifs regions */
111 static struct ifsregion ifsfirst;
112 /* last struct in list */
113 static struct ifsregion *ifslastp;
114 /* holds expanded arg list */
115 static struct arglist exparg;
116
117 STATIC void argstr(char *, int);
118 STATIC char *exptilde(char *, char *, int);
119 STATIC void expbackq(union node *, int, int);
120 STATIC const char *subevalvar(char *, char *, int, int, int, int, int);
121 STATIC char *evalvar(char *, int);
122 STATIC size_t strtodest(const char *, const char *, int);
123 STATIC void memtodest(const char *, size_t, const char *, int);
124 STATIC ssize_t varvalue(char *, int, int);
125 STATIC void recordregion(int, int, int);
126 STATIC void removerecordregions(int);
127 STATIC void ifsbreakup(char *, struct arglist *);
128 STATIC void ifsfree(void);
129 STATIC void expandmeta(struct strlist *, int);
130 #if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
131 STATIC void addglob(const glob_t *);
132 #else
133 STATIC void expmeta(char *, char *);
134 #endif
135 STATIC void addfname(char *);
136 #if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
137 STATIC struct strlist *expsort(struct strlist *);
138 STATIC struct strlist *msort(struct strlist *, int);
139 #endif
140 STATIC int patmatch(char *, const char *);
141 #if !defined(__GLIBC__) || defined(FNMATCH_BROKEN)
142 STATIC int pmatch(const char *, const char *);
143 #else
144 #define pmatch(a, b) !fnmatch((a), (b), 0)
145 #endif
146 STATIC int cvtnum(long);
147 STATIC size_t esclen(const char *, const char *);
148 STATIC char *scanleft(char *, char *, char *, char *, int, int);
149 STATIC char *scanright(char *, char *, char *, char *, int, int);
150 STATIC void varunset(const char *, const char *, const char *, int)
151 __attribute__((__noreturn__));
152
153
154 /*
155 * Prepare a pattern for a glob(3) call.
156 *
157 * Returns an stalloced string.
158 */
159
160 STATIC inline char *
161 preglob(const char *pattern, int quoted, int flag) {
162 flag |= RMESCAPE_GLOB;
163 if (quoted) {
164 flag |= RMESCAPE_QUOTED;
165 }
166 return _rmescapes((char *)pattern, flag);
167 }
168
169
170 STATIC size_t
171 esclen(const char *start, const char *p) {
172 size_t esc = 0;
173
174 while (p > start && *--p == (char)CTLESC) {
175 esc++;
176 }
177 return esc;
178 }
179
180
181 static inline const char *getpwhome(const char *name)
182 {
183 #ifdef HAVE_GETPWNAM
184 struct passwd *pw = getpwnam(name);
185 return pw ? pw->pw_dir : 0;
186 #else
187 return 0;
188 #endif
189 }
190
191
192 /*
193 * Expand shell variables and backquotes inside a here document.
194 */
195
196 void
197 expandhere(union node *arg, int fd)
198 {
199 herefd = fd;
200 expandarg(arg, (struct arglist *)NULL, 0);
201 xwrite(fd, stackblock(), expdest - (char *)stackblock());
202 }
203
204
205 /*
206 * Perform variable substitution and command substitution on an argument,
207 * placing the resulting list of arguments in arglist. If EXP_FULL is true,
208 * perform splitting and file name expansion. When arglist is NULL, perform
209 * here document expansion.
210 */
211
212 void
213 expandarg(union node *arg, struct arglist *arglist, int flag)
214 {
215 struct strlist *sp;
216 char *p;
217
218 argbackq = arg->narg.backquote;
219 STARTSTACKSTR(expdest);
220 ifsfirst.next = NULL;
221 ifslastp = NULL;
222 argstr(arg->narg.text, flag);
223 p = _STPUTC('\0', expdest);
224 expdest = p - 1;
225 if (arglist == NULL) {
226 return; /* here document expanded */
227 }
228 p = grabstackstr(p);
229 exparg.lastp = &exparg.list;
230 /*
231 * TODO - EXP_REDIR
232 */
233 if (flag & EXP_FULL) {
234 ifsbreakup(p, &exparg);
235 *exparg.lastp = NULL;
236 exparg.lastp = &exparg.list;
237 expandmeta(exparg.list, flag);
238 } else {
239 if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
240 rmescapes(p);
241 sp = (struct strlist *)stalloc(sizeof (struct strlist));
242 sp->text = p;
243 *exparg.lastp = sp;
244 exparg.lastp = &sp->next;
245 }
246 if (ifsfirst.next)
247 ifsfree();
248 *exparg.lastp = NULL;
249 if (exparg.list) {
250 *arglist->lastp = exparg.list;
251 arglist->lastp = exparg.lastp;
252 }
253 }
254
255
256
257 /*
258 * Perform variable and command substitution. If EXP_FULL is set, output CTLESC
259 * characters to allow for further processing. Otherwise treat
260 * $@ like $* since no splitting will be performed.
261 */
262
263 STATIC void
264 argstr(char *p, int flag)
265 {
266 static const char spclchars[] = {
267 '=',
268 ':',
269 CTLQUOTEMARK,
270 CTLENDVAR,
271 CTLESC,
272 CTLVAR,
273 CTLBACKQ,
274 CTLBACKQ | CTLQUOTE,
275 CTLENDARI,
276 0
277 };
278 const char *reject = spclchars;
279 int c;
280 int quotes = flag & QUOTES_ESC;
281 int breakall = flag & EXP_WORD;
282 int inquotes;
283 size_t length;
284 int startloc;
285
286 if (!(flag & EXP_VARTILDE)) {
287 reject += 2;
288 } else if (flag & EXP_VARTILDE2) {
289 reject++;
290 }
291 inquotes = 0;
292 length = 0;
293 if (flag & EXP_TILDE) {
294 char *q;
295
296 flag &= ~EXP_TILDE;
297 tilde:
298 q = p;
299 if (*q == (char)CTLESC && (flag & EXP_QWORD))
300 q++;
301 if (*q == '~')
302 p = exptilde(p, q, flag);
303 }
304 start:
305 startloc = expdest - (char *)stackblock();
306 for (;;) {
307 length += strcspn(p + length, reject);
308 c = (signed char)p[length];
309 if (c && (!(c & 0x80) || c == CTLENDARI)) {
310 /* c == '=' || c == ':' || c == CTLENDARI */
311 length++;
312 }
313 if (length > 0) {
314 int newloc;
315 expdest = stnputs(p, length, expdest);
316 newloc = expdest - (char *)stackblock();
317 if (breakall && !inquotes && newloc > startloc) {
318 recordregion(startloc, newloc, 0);
319 }
320 startloc = newloc;
321 }
322 p += length + 1;
323 length = 0;
324
325 switch (c) {
326 case '\0':
327 goto breakloop;
328 case '=':
329 if (flag & EXP_VARTILDE2) {
330 p--;
331 continue;
332 }
333 flag |= EXP_VARTILDE2;
334 reject++;
335 /* fall through */
336 case ':':
337 /*
338 * sort of a hack - expand tildes in variable
339 * assignments (after the first '=' and after ':'s).
340 */
341 if (*--p == '~') {
342 goto tilde;
343 }
344 continue;
345 }
346
347 switch (c) {
348 case CTLENDVAR: /* ??? */
349 goto breakloop;
350 case CTLQUOTEMARK:
351 /* "$@" syntax adherence hack */
352 if (
353 !inquotes &&
354 !memcmp(p, dolatstr, DOLATSTRLEN) &&
355 (p[4] == (char)CTLQUOTEMARK || (
356 p[4] == (char)CTLENDVAR &&
357 p[5] == (char)CTLQUOTEMARK
358 ))
359 ) {
360 p = evalvar(p + 1, flag) + 1;
361 goto start;
362 }
363 inquotes = !inquotes;
364 addquote:
365 if (quotes) {
366 p--;
367 length++;
368 startloc++;
369 }
370 break;
371 case CTLESC:
372 startloc++;
373 length++;
374 goto addquote;
375 case CTLVAR:
376 p = evalvar(p, flag);
377 goto start;
378 case CTLBACKQ:
379 c = 0;
380 case CTLBACKQ|CTLQUOTE:
381 expbackq(argbackq->n, c, quotes);
382 argbackq = argbackq->next;
383 goto start;
384 case CTLENDARI:
385 p--;
386 expari(quotes);
387 goto start;
388 }
389 }
390 breakloop:
391 ;
392 }
393
394 STATIC char *
395 exptilde(char *startp, char *p, int flag)
396 {
397 signed char c;
398 char *name;
399 const char *home;
400 int quotes = flag & QUOTES_ESC;
401 int startloc;
402
403 name = p + 1;
404
405 while ((c = *++p) != '\0') {
406 switch(c) {
407 case CTLESC:
408 return (startp);
409 case CTLQUOTEMARK:
410 return (startp);
411 case ':':
412 if (flag & EXP_VARTILDE)
413 goto done;
414 break;
415 case '/':
416 case CTLENDVAR:
417 goto done;
418 }
419 }
420 done:
421 *p = '\0';
422 if (*name == '\0') {
423 home = lookupvar(homestr);
424 } else {
425 home = getpwhome(name);
426 }
427 if (!home || !*home)
428 goto lose;
429 *p = c;
430 startloc = expdest - (char *)stackblock();
431 strtodest(home, SQSYNTAX, quotes);
432 recordregion(startloc, expdest - (char *)stackblock(), 0);
433 return (p);
434 lose:
435 *p = c;
436 return (startp);
437 }
438
439
440 STATIC void
441 removerecordregions(int endoff)
442 {
443 if (ifslastp == NULL)
444 return;
445
446 if (ifsfirst.endoff > endoff) {
447 while (ifsfirst.next != NULL) {
448 struct ifsregion *ifsp;
449 INTOFF;
450 ifsp = ifsfirst.next->next;
451 ckfree(ifsfirst.next);
452 ifsfirst.next = ifsp;
453 INTON;
454 }
455 if (ifsfirst.begoff > endoff)
456 ifslastp = NULL;
457 else {
458 ifslastp = &ifsfirst;
459 ifsfirst.endoff = endoff;
460 }
461 return;
462 }
463
464 ifslastp = &ifsfirst;
465 while (ifslastp->next && ifslastp->next->begoff < endoff)
466 ifslastp=ifslastp->next;
467 while (ifslastp->next != NULL) {
468 struct ifsregion *ifsp;
469 INTOFF;
470 ifsp = ifslastp->next->next;
471 ckfree(ifslastp->next);
472 ifslastp->next = ifsp;
473 INTON;
474 }
475 if (ifslastp->endoff > endoff)
476 ifslastp->endoff = endoff;
477 }
478
479
480 /*
481 * Expand arithmetic expression. Backup to start of expression,
482 * evaluate, place result in (backed up) result, adjust string position.
483 */
484 void
485 expari(int quotes)
486 {
487 char *p, *start;
488 int begoff;
489 int flag;
490 int len;
491
492 /* ifsfree(); */
493
494 /*
495 * This routine is slightly over-complicated for
496 * efficiency. Next we scan backwards looking for the
497 * start of arithmetic.
498 */
499 start = stackblock();
500 p = expdest - 1;
501 *p = '\0';
502 p--;
503 do {
504 int esc;
505
506 while (*p != (char)CTLARI) {
507 p--;
508 #ifdef DEBUG
509 if (p < start) {
510 sh_error("missing CTLARI (shouldn't happen)");
511 }
512 #endif
513 }
514
515 esc = esclen(start, p);
516 if (!(esc % 2)) {
517 break;
518 }
519
520 p -= esc + 1;
521 } while (1);
522
523 begoff = p - start;
524
525 removerecordregions(begoff);
526
527 flag = p[1];
528
529 expdest = p;
530
531 if (quotes)
532 rmescapes(p + 2);
533
534 len = cvtnum(arith(p + 2));
535
536 if (flag != '"')
537 recordregion(begoff, begoff + len, 0);
538 }
539
540
541 /*
542 * Expand stuff in backwards quotes.
543 */
544
545 STATIC void
546 expbackq(union node *cmd, int quoted, int quotes)
547 {
548 struct backcmd in;
549 int i;
550 char buf[128];
551 char *p;
552 char *dest;
553 int startloc;
554 char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
555 struct stackmark smark;
556
557 INTOFF;
558 setstackmark(&smark);
559 dest = expdest;
560 startloc = dest - (char *)stackblock();
561 grabstackstr(dest);
562 evalbackcmd(cmd, (struct backcmd *) &in);
563 popstackmark(&smark);
564
565 p = in.buf;
566 i = in.nleft;
567 if (i == 0)
568 goto read;
569 for (;;) {
570 memtodest(p, i, syntax, quotes);
571 read:
572 if (in.fd < 0)
573 break;
574 do {
575 i = read(in.fd, buf, sizeof buf);
576 } while (i < 0 && errno == EINTR);
577 TRACE(("expbackq: read returns %d\n", i));
578 if (i <= 0)
579 break;
580 p = buf;
581 }
582
583 if (in.buf)
584 ckfree(in.buf);
585 if (in.fd >= 0) {
586 close(in.fd);
587 back_exitstatus = waitforjob(in.jp);
588 }
589 INTON;
590
591 /* Eat all trailing newlines */
592 dest = expdest;
593 for (; dest > (char *)stackblock() && dest[-1] == '\n';)
594 STUNPUTC(dest);
595 expdest = dest;
596
597 if (quoted == 0)
598 recordregion(startloc, dest - (char *)stackblock(), 0);
599 TRACE(("evalbackq: size=%d: \"%.*s\"\n",
600 (dest - (char *)stackblock()) - startloc,
601 (dest - (char *)stackblock()) - startloc,
602 stackblock() + startloc));
603 }
604
605
606 STATIC char *
607 scanleft(
608 char *startp, char *rmesc, char *rmescend, char *str, int quotes,
609 int zero
610 ) {
611 char *loc;
612 char *loc2;
613 char c;
614
615 loc = startp;
616 loc2 = rmesc;
617 do {
618 int match;
619 const char *s = loc2;
620 c = *loc2;
621 if (zero) {
622 *loc2 = '\0';
623 s = rmesc;
624 }
625 match = pmatch(str, s);
626 *loc2 = c;
627 if (match)
628 return loc;
629 if (quotes && *loc == (char)CTLESC)
630 loc++;
631 loc++;
632 loc2++;
633 } while (c);
634 return 0;
635 }
636
637
638 STATIC char *
639 scanright(
640 char *startp, char *rmesc, char *rmescend, char *str, int quotes,
641 int zero
642 ) {
643 int esc = 0;
644 char *loc;
645 char *loc2;
646
647 for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
648 int match;
649 char c = *loc2;
650 const char *s = loc2;
651 if (zero) {
652 *loc2 = '\0';
653 s = rmesc;
654 }
655 match = pmatch(str, s);
656 *loc2 = c;
657 if (match)
658 return loc;
659 loc--;
660 if (quotes) {
661 if (--esc < 0) {
662 esc = esclen(startp, loc);
663 }
664 if (esc % 2) {
665 esc--;
666 loc--;
667 }
668 }
669 }
670 return 0;
671 }
672
673 STATIC const char *
674 subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes)
675 {
676 char *startp;
677 char *loc;
678 int saveherefd = herefd;
679 struct nodelist *saveargbackq = argbackq;
680 int amount;
681 char *rmesc, *rmescend;
682 int zero;
683 char *(*scan)(char *, char *, char *, char *, int , int);
684
685 herefd = -1;
686 argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
687 STPUTC('\0', expdest);
688 herefd = saveherefd;
689 argbackq = saveargbackq;
690 startp = stackblock() + startloc;
691
692 switch (subtype) {
693 case VSASSIGN:
694 setvar(str, startp, 0);
695 amount = startp - expdest;
696 STADJUST(amount, expdest);
697 return startp;
698
699 case VSQUESTION:
700 varunset(p, str, startp, varflags);
701 /* NOTREACHED */
702 }
703
704 subtype -= VSTRIMRIGHT;
705 #ifdef DEBUG
706 if (subtype < 0 || subtype > 3)
707 abort();
708 #endif
709
710 rmesc = startp;
711 rmescend = stackblock() + strloc;
712 if (quotes) {
713 rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
714 if (rmesc != startp) {
715 rmescend = expdest;
716 startp = stackblock() + startloc;
717 }
718 }
719 rmescend--;
720 str = stackblock() + strloc;
721 preglob(str, varflags & VSQUOTE, 0);
722
723 /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
724 zero = subtype >> 1;
725 /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
726 scan = (subtype & 1) ^ zero ? scanleft : scanright;
727
728 loc = scan(startp, rmesc, rmescend, str, quotes, zero);
729 if (loc) {
730 if (zero) {
731 memmove(startp, loc, str - loc);
732 loc = startp + (str - loc) - 1;
733 }
734 *loc = '\0';
735 amount = loc - expdest;
736 STADJUST(amount, expdest);
737 }
738 return loc;
739 }
740
741
742 /*
743 * Expand a variable, and return a pointer to the next character in the
744 * input string.
745 */
746 STATIC char *
747 evalvar(char *p, int flag)
748 {
749 int subtype;
750 int varflags;
751 char *var;
752 int patloc;
753 int c;
754 int startloc;
755 ssize_t varlen;
756 int easy;
757 int quotes;
758 int quoted;
759
760 quotes = flag & QUOTES_ESC;
761 varflags = *p++;
762 subtype = varflags & VSTYPE;
763 quoted = varflags & VSQUOTE;
764 var = p;
765 easy = (!quoted || (*var == '@' && shellparam.nparam));
766 startloc = expdest - (char *)stackblock();
767 p = strchr(p, '=') + 1;
768
769 again:
770 varlen = varvalue(var, varflags, flag);
771 if (varflags & VSNUL)
772 varlen--;
773
774 if (subtype == VSPLUS) {
775 varlen = -1 - varlen;
776 goto vsplus;
777 }
778
779 if (subtype == VSMINUS) {
780 vsplus:
781 if (varlen < 0) {
782 argstr(
783 p, flag | EXP_TILDE |
784 (quoted ? EXP_QWORD : EXP_WORD)
785 );
786 goto end;
787 }
788 if (easy)
789 goto record;
790 goto end;
791 }
792
793 if (subtype == VSASSIGN || subtype == VSQUESTION) {
794 if (varlen < 0) {
795 if (subevalvar(p, var, 0, subtype, startloc,
796 varflags, 0)) {
797 varflags &= ~VSNUL;
798 /*
799 * Remove any recorded regions beyond
800 * start of variable
801 */
802 removerecordregions(startloc);
803 goto again;
804 }
805 goto end;
806 }
807 if (easy)
808 goto record;
809 goto end;
810 }
811
812 if (varlen < 0 && uflag)
813 varunset(p, var, 0, 0);
814
815 if (subtype == VSLENGTH) {
816 cvtnum(varlen > 0 ? varlen : 0);
817 goto record;
818 }
819
820 if (subtype == VSNORMAL) {
821 if (!easy)
822 goto end;
823 record:
824 recordregion(startloc, expdest - (char *)stackblock(), quoted);
825 goto end;
826 }
827
828 #ifdef DEBUG
829 switch (subtype) {
830 case VSTRIMLEFT:
831 case VSTRIMLEFTMAX:
832 case VSTRIMRIGHT:
833 case VSTRIMRIGHTMAX:
834 break;
835 default:
836 abort();
837 }
838 #endif
839
840 if (varlen >= 0) {
841 /*
842 * Terminate the string and start recording the pattern
843 * right after it
844 */
845 STPUTC('\0', expdest);
846 patloc = expdest - (char *)stackblock();
847 if (subevalvar(p, NULL, patloc, subtype,
848 startloc, varflags, quotes) == 0) {
849 int amount = expdest - (
850 (char *)stackblock() + patloc - 1
851 );
852 STADJUST(-amount, expdest);
853 }
854 /* Remove any recorded regions beyond start of variable */
855 removerecordregions(startloc);
856 goto record;
857 }
858
859 end:
860 if (subtype != VSNORMAL) { /* skip to end of alternative */
861 int nesting = 1;
862 for (;;) {
863 if ((c = (signed char)*p++) == CTLESC)
864 p++;
865 else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
866 if (varlen >= 0)
867 argbackq = argbackq->next;
868 } else if (c == CTLVAR) {
869 if ((*p++ & VSTYPE) != VSNORMAL)
870 nesting++;
871 } else if (c == CTLENDVAR) {
872 if (--nesting == 0)
873 break;
874 }
875 }
876 }
877 return p;
878 }
879
880
881 /*
882 * Put a string on the stack.
883 */
884
885 STATIC void
886 memtodest(const char *p, size_t len, const char *syntax, int quotes) {
887 char *q;
888
889 if (unlikely(!len))
890 return;
891
892 q = makestrspace(len * 2, expdest);
893
894 do {
895 int c = (signed char)*p++;
896 if (c) {
897 if ((quotes & QUOTES_ESC) &&
898 (syntax[c] == CCTL || syntax[c] == CBACK))
899 USTPUTC(CTLESC, q);
900 } else if (!(quotes & QUOTES_KEEPNUL))
901 continue;
902 USTPUTC(c, q);
903 } while (--len);
904
905 expdest = q;
906 }
907
908
909 STATIC size_t
910 strtodest(p, syntax, quotes)
911 const char *p;
912 const char *syntax;
913 int quotes;
914 {
915 size_t len = strlen(p);
916 memtodest(p, len, syntax, quotes);
917 return len;
918 }
919
920
921
922 /*
923 * Add the value of a specialized variable to the stack string.
924 */
925
926 STATIC ssize_t
927 varvalue(char *name, int varflags, int flags)
928 {
929 int num;
930 char *p;
931 int i;
932 int sep;
933 char sepc;
934 char **ap;
935 char const *syntax;
936 int quoted = varflags & VSQUOTE;
937 int subtype = varflags & VSTYPE;
938 int discard = subtype == VSPLUS || subtype == VSLENGTH;
939 int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
940 ssize_t len = 0;
941
942 sep = quoted ? ((flags & EXP_FULL) << CHAR_BIT) : 0;
943 syntax = quoted ? DQSYNTAX : BASESYNTAX;
944
945 switch (*name) {
946 case '$':
947 num = rootpid;
948 goto numvar;
949 case '?':
950 num = exitstatus;
951 goto numvar;
952 case '#':
953 num = shellparam.nparam;
954 goto numvar;
955 case '!':
956 num = backgndpid;
957 if (num == 0)
958 return -1;
959 numvar:
960 len = cvtnum(num);
961 break;
962 case '-':
963 p = makestrspace(NOPTS, expdest);
964 for (i = NOPTS - 1; i >= 0; i--) {
965 if (optlist[i]) {
966 USTPUTC(optletters[i], p);
967 len++;
968 }
969 }
970 expdest = p;
971 break;
972 case '@':
973 if (sep)
974 goto param;
975 /* fall through */
976 case '*':
977 sep = ifsset() ? ifsval()[0] : ' ';
978 param:
979 if (!(ap = shellparam.p))
980 return -1;
981 sepc = sep;
982 while ((p = *ap++)) {
983 len += strtodest(p, syntax, quotes);
984
985 if (*ap && sep) {
986 len++;
987 memtodest(&sepc, 1, syntax, quotes);
988 }
989 }
990 break;
991 case '0':
992 case '1':
993 case '2':
994 case '3':
995 case '4':
996 case '5':
997 case '6':
998 case '7':
999 case '8':
1000 case '9':
1001 num = atoi(name);
1002 if (num < 0 || num > shellparam.nparam)
1003 return -1;
1004 p = num ? shellparam.p[num - 1] : arg0;
1005 goto value;
1006 default:
1007 p = lookupvar(name);
1008 value:
1009 if (!p)
1010 return -1;
1011
1012 len = strtodest(p, syntax, quotes);
1013 break;
1014 }
1015
1016 if (discard)
1017 STADJUST(-len, expdest);
1018 return len;
1019 }
1020
1021
1022
1023 /*
1024 * Record the fact that we have to scan this region of the
1025 * string for IFS characters.
1026 */
1027
1028 STATIC void
1029 recordregion(int start, int end, int nulonly)
1030 {
1031 struct ifsregion *ifsp;
1032
1033 if (ifslastp == NULL) {
1034 ifsp = &ifsfirst;
1035 } else {
1036 INTOFF;
1037 ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
1038 ifsp->next = NULL;
1039 ifslastp->next = ifsp;
1040 INTON;
1041 }
1042 ifslastp = ifsp;
1043 ifslastp->begoff = start;
1044 ifslastp->endoff = end;
1045 ifslastp->nulonly = nulonly;
1046 }
1047
1048
1049
1050 /*
1051 * Break the argument string into pieces based upon IFS and add the
1052 * strings to the argument list. The regions of the string to be
1053 * searched for IFS characters have been stored by recordregion.
1054 */
1055 STATIC void
1056 ifsbreakup(char *string, struct arglist *arglist)
1057 {
1058 struct ifsregion *ifsp;
1059 struct strlist *sp;
1060 char *start;
1061 char *p;
1062 char *q;
1063 const char *ifs, *realifs;
1064 int ifsspc;
1065 int nulonly;
1066
1067
1068 start = string;
1069 if (ifslastp != NULL) {
1070 ifsspc = 0;
1071 nulonly = 0;
1072 realifs = ifsset() ? ifsval() : defifs;
1073 ifsp = &ifsfirst;
1074 do {
1075 p = string + ifsp->begoff;
1076 nulonly = ifsp->nulonly;
1077 ifs = nulonly ? nullstr : realifs;
1078 ifsspc = 0;
1079 while (p < string + ifsp->endoff) {
1080 q = p;
1081 if (*p == (char)CTLESC)
1082 p++;
1083 if (strchr(ifs, *p)) {
1084 if (!nulonly)
1085 ifsspc = (strchr(defifs, *p) != NULL);
1086 /* Ignore IFS whitespace at start */
1087 if (q == start && ifsspc) {
1088 p++;
1089 start = p;
1090 continue;
1091 }
1092 *q = '\0';
1093 sp = (struct strlist *)stalloc(sizeof *sp);
1094 sp->text = start;
1095 *arglist->lastp = sp;
1096 arglist->lastp = &sp->next;
1097 p++;
1098 if (!nulonly) {
1099 for (;;) {
1100 if (p >= string + ifsp->endoff) {
1101 break;
1102 }
1103 q = p;
1104 if (*p == (char)CTLESC)
1105 p++;
1106 if (strchr(ifs, *p) == NULL ) {
1107 p = q;
1108 break;
1109 } else if (strchr(defifs, *p) == NULL) {
1110 if (ifsspc) {
1111 p++;
1112 ifsspc = 0;
1113 } else {
1114 p = q;
1115 break;
1116 }
1117 } else
1118 p++;
1119 }
1120 }
1121 start = p;
1122 } else
1123 p++;
1124 }
1125 } while ((ifsp = ifsp->next) != NULL);
1126 if (nulonly)
1127 goto add;
1128 }
1129
1130 if (!*start)
1131 return;
1132
1133 add:
1134 sp = (struct strlist *)stalloc(sizeof *sp);
1135 sp->text = start;
1136 *arglist->lastp = sp;
1137 arglist->lastp = &sp->next;
1138 }
1139
1140 STATIC void
1141 ifsfree(void)
1142 {
1143 struct ifsregion *p;
1144
1145 INTOFF;
1146 p = ifsfirst.next;
1147 do {
1148 struct ifsregion *ifsp;
1149 ifsp = p->next;
1150 ckfree(p);
1151 p = ifsp;
1152 } while (p);
1153 ifslastp = NULL;
1154 ifsfirst.next = NULL;
1155 INTON;
1156 }
1157
1158
1159
1160 /*
1161 * Expand shell metacharacters. At this point, the only control characters
1162 * should be escapes. The results are stored in the list exparg.
1163 */
1164
1165 #if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)
1166 STATIC void
1167 expandmeta(str, flag)
1168 struct strlist *str;
1169 int flag;
1170 {
1171 /* TODO - EXP_REDIR */
1172
1173 while (str) {
1174 const char *p;
1175 glob_t pglob;
1176 int i;
1177
1178 if (fflag)
1179 goto nometa;
1180 INTOFF;
1181 p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
1182 i = glob(p, GLOB_NOMAGIC, 0, &pglob);
1183 if (p != str->text)
1184 ckfree(p);
1185 switch (i) {
1186 case 0:
1187 if (!(pglob.gl_flags & GLOB_MAGCHAR))
1188 goto nometa2;
1189 addglob(&pglob);
1190 globfree(&pglob);
1191 INTON;
1192 break;
1193 case GLOB_NOMATCH:
1194 nometa2:
1195 globfree(&pglob);
1196 INTON;
1197 nometa:
1198 *exparg.lastp = str;
1199 rmescapes(str->text);
1200 exparg.lastp = &str->next;
1201 break;
1202 default: /* GLOB_NOSPACE */
1203 sh_error("Out of space");
1204 }
1205 str = str->next;
1206 }
1207 }
1208
1209
1210 /*
1211 * Add the result of glob(3) to the list.
1212 */
1213
1214 STATIC void
1215 addglob(pglob)
1216 const glob_t *pglob;
1217 {
1218 char **p = pglob->gl_pathv;
1219
1220 do {
1221 addfname(*p);
1222 } while (*++p);
1223 }
1224
1225
1226 #else /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
1227 STATIC char *expdir;
1228
1229
1230 STATIC void
1231 expandmeta(struct strlist *str, int flag)
1232 {
1233 static const char metachars[] = {
1234 '*', '?', '[', 0
1235 };
1236 /* TODO - EXP_REDIR */
1237
1238 while (str) {
1239 struct strlist **savelastp;
1240 struct strlist *sp;
1241 char *p;
1242
1243 if (fflag)
1244 goto nometa;
1245 if (!strpbrk(str->text, metachars))
1246 goto nometa;
1247 savelastp = exparg.lastp;
1248
1249 INTOFF;
1250 p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
1251 {
1252 int i = strlen(str->text);
1253 expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
1254 }
1255
1256 expmeta(expdir, p);
1257 ckfree(expdir);
1258 if (p != str->text)
1259 ckfree(p);
1260 INTON;
1261 if (exparg.lastp == savelastp) {
1262 /*
1263 * no matches
1264 */
1265 nometa:
1266 *exparg.lastp = str;
1267 rmescapes(str->text);
1268 exparg.lastp = &str->next;
1269 } else {
1270 *exparg.lastp = NULL;
1271 *savelastp = sp = expsort(*savelastp);
1272 while (sp->next != NULL)
1273 sp = sp->next;
1274 exparg.lastp = &sp->next;
1275 }
1276 str = str->next;
1277 }
1278 }
1279
1280
1281 /*
1282 * Do metacharacter (i.e. *, ?, [...]) expansion.
1283 */
1284
1285 STATIC void
1286 expmeta(char *enddir, char *name)
1287 {
1288 char *p;
1289 const char *cp;
1290 char *start;
1291 char *endname;
1292 int metaflag;
1293 struct stat64 statb;
1294 DIR *dirp;
1295 struct dirent *dp;
1296 int atend;
1297 int matchdot;
1298
1299 metaflag = 0;
1300 start = name;
1301 for (p = name; *p; p++) {
1302 if (*p == '*' || *p == '?')
1303 metaflag = 1;
1304 else if (*p == '[') {
1305 char *q = p + 1;
1306 if (*q == '!')
1307 q++;
1308 for (;;) {
1309 if (*q == '\\')
1310 q++;
1311 if (*q == '/' || *q == '\0')
1312 break;
1313 if (*++q == ']') {
1314 metaflag = 1;
1315 break;
1316 }
1317 }
1318 } else if (*p == '\\')
1319 p++;
1320 else if (*p == '/') {
1321 if (metaflag)
1322 goto out;
1323 start = p + 1;
1324 }
1325 }
1326 out:
1327 if (metaflag == 0) { /* we've reached the end of the file name */
1328 if (enddir != expdir)
1329 metaflag++;
1330 p = name;
1331 do {
1332 if (*p == '\\')
1333 p++;
1334 *enddir++ = *p;
1335 } while (*p++);
1336 if (metaflag == 0 || lstat64(expdir, &statb) >= 0)
1337 addfname(expdir);
1338 return;
1339 }
1340 endname = p;
1341 if (name < start) {
1342 p = name;
1343 do {
1344 if (*p == '\\')
1345 p++;
1346 *enddir++ = *p++;
1347 } while (p < start);
1348 }
1349 if (enddir == expdir) {
1350 cp = ".";
1351 } else if (enddir == expdir + 1 && *expdir == '/') {
1352 cp = "/";
1353 } else {
1354 cp = expdir;
1355 enddir[-1] = '\0';
1356 }
1357 if ((dirp = opendir(cp)) == NULL)
1358 return;
1359 if (enddir != expdir)
1360 enddir[-1] = '/';
1361 if (*endname == 0) {
1362 atend = 1;
1363 } else {
1364 atend = 0;
1365 *endname++ = '\0';
1366 }
1367 matchdot = 0;
1368 p = start;
1369 if (*p == '\\')
1370 p++;
1371 if (*p == '.')
1372 matchdot++;
1373 while (! int_pending() && (dp = readdir(dirp)) != NULL) {
1374 if (dp->d_name[0] == '.' && ! matchdot)
1375 continue;
1376 if (pmatch(start, dp->d_name)) {
1377 if (atend) {
1378 scopy(dp->d_name, enddir);
1379 addfname(expdir);
1380 } else {
1381 for (p = enddir, cp = dp->d_name;
1382 (*p++ = *cp++) != '\0';)
1383 continue;
1384 p[-1] = '/';
1385 expmeta(p, endname);
1386 }
1387 }
1388 }
1389 closedir(dirp);
1390 if (! atend)
1391 endname[-1] = '/';
1392 }
1393 #endif /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */
1394
1395
1396 /*
1397 * Add a file name to the list.
1398 */
1399
1400 STATIC void
1401 addfname(char *name)
1402 {
1403 struct strlist *sp;
1404
1405 sp = (struct strlist *)stalloc(sizeof *sp);
1406 sp->text = sstrdup(name);
1407 *exparg.lastp = sp;
1408 exparg.lastp = &sp->next;
1409 }
1410
1411
1412 #if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN))
1413 /*
1414 * Sort the results of file name expansion. It calculates the number of
1415 * strings to sort and then calls msort (short for merge sort) to do the
1416 * work.
1417 */
1418
1419 STATIC struct strlist *
1420 expsort(struct strlist *str)
1421 {
1422 int len;
1423 struct strlist *sp;
1424
1425 len = 0;
1426 for (sp = str ; sp ; sp = sp->next)
1427 len++;
1428 return msort(str, len);
1429 }
1430
1431
1432 STATIC struct strlist *
1433 msort(struct strlist *list, int len)
1434 {
1435 struct strlist *p, *q = NULL;
1436 struct strlist **lpp;
1437 int half;
1438 int n;
1439
1440 if (len <= 1)
1441 return list;
1442 half = len >> 1;
1443 p = list;
1444 for (n = half ; --n >= 0 ; ) {
1445 q = p;
1446 p = p->next;
1447 }
1448 q->next = NULL; /* terminate first half of list */
1449 q = msort(list, half); /* sort first half of list */
1450 p = msort(p, len - half); /* sort second half */
1451 lpp = &list;
1452 for (;;) {
1453 if (strcmp(p->text, q->text) < 0) {
1454 *lpp = p;
1455 lpp = &p->next;
1456 if ((p = *lpp) == NULL) {
1457 *lpp = q;
1458 break;
1459 }
1460 } else {
1461 *lpp = q;
1462 lpp = &q->next;
1463 if ((q = *lpp) == NULL) {
1464 *lpp = p;
1465 break;
1466 }
1467 }
1468 }
1469 return list;
1470 }
1471 #endif
1472
1473
1474 /*
1475 * Returns true if the pattern matches the string.
1476 */
1477
1478 STATIC inline int
1479 patmatch(char *pattern, const char *string)
1480 {
1481 return pmatch(preglob(pattern, 0, 0), string);
1482 }
1483
1484
1485 #if !defined(__GLIBC__) || defined(FNMATCH_BROKEN)
1486 STATIC int ccmatch(const char *p, int chr, const char **r)
1487 {
1488 static const struct class {
1489 char name[10];
1490 int (*fn)(int);
1491 } classes[] = {
1492 { .name = ":alnum:]", .fn = isalnum },
1493 { .name = ":cntrl:]", .fn = iscntrl },
1494 { .name = ":lower:]", .fn = islower },
1495 { .name = ":space:]", .fn = isspace },
1496 { .name = ":alpha:]", .fn = isalpha },
1497 { .name = ":digit:]", .fn = isdigit },
1498 { .name = ":print:]", .fn = isprint },
1499 { .name = ":upper:]", .fn = isupper },
1500 { .name = ":blank:]", .fn = isblank },
1501 { .name = ":graph:]", .fn = isgraph },
1502 { .name = ":punct:]", .fn = ispunct },
1503 { .name = ":xdigit:]", .fn = isxdigit },
1504 };
1505 const struct class *class, *end;
1506
1507 end = classes + sizeof(classes) / sizeof(classes[0]);
1508 for (class = classes; class < end; class++) {
1509 const char *q;
1510
1511 q = prefix(p, class->name);
1512 if (!q)
1513 continue;
1514 *r = q;
1515 return class->fn(chr);
1516 }
1517
1518 *r = 0;
1519 return 0;
1520 }
1521
1522 STATIC int
1523 pmatch(const char *pattern, const char *string)
1524 {
1525 const char *p, *q;
1526 char c;
1527
1528 p = pattern;
1529 q = string;
1530 for (;;) {
1531 switch (c = *p++) {
1532 case '\0':
1533 goto breakloop;
1534 case '\\':
1535 if (*p) {
1536 c = *p++;
1537 }
1538 goto dft;
1539 case '?':
1540 if (*q++ == '\0')
1541 return 0;
1542 break;
1543 case '*':
1544 c = *p;
1545 while (c == '*')
1546 c = *++p;
1547 if (c != '\\' && c != '?' && c != '*' && c != '[') {
1548 while (*q != c) {
1549 if (*q == '\0')
1550 return 0;
1551 q++;
1552 }
1553 }
1554 do {
1555 if (pmatch(p, q))
1556 return 1;
1557 } while (*q++ != '\0');
1558 return 0;
1559 case '[': {
1560 const char *startp;
1561 int invert, found;
1562 char chr;
1563
1564 startp = p;
1565 invert = 0;
1566 if (*p == '!') {
1567 invert++;
1568 p++;
1569 }
1570 found = 0;
1571 chr = *q++;
1572 if (chr == '\0')
1573 return 0;
1574 c = *p++;
1575 do {
1576 if (!c) {
1577 p = startp;
1578 c = *p;
1579 goto dft;
1580 }
1581 if (c == '[') {
1582 const char *r;
1583
1584 found |= !!ccmatch(p, chr, &r);
1585 if (r) {
1586 p = r;
1587 continue;
1588 }
1589 } else if (c == '\\')
1590 c = *p++;
1591 if (*p == '-' && p[1] != ']') {
1592 p++;
1593 if (*p == '\\')
1594 p++;
1595 if (chr >= c && chr <= *p)
1596 found = 1;
1597 p++;
1598 } else {
1599 if (chr == c)
1600 found = 1;
1601 }
1602 } while ((c = *p++) != ']');
1603 if (found == invert)
1604 return 0;
1605 break;
1606 }
1607 dft: default:
1608 if (*q++ != c)
1609 return 0;
1610 break;
1611 }
1612 }
1613 breakloop:
1614 if (*q != '\0')
1615 return 0;
1616 return 1;
1617 }
1618 #endif
1619
1620
1621
1622 /*
1623 * Remove any CTLESC characters from a string.
1624 */
1625
1626 char *
1627 _rmescapes(char *str, int flag)
1628 {
1629 char *p, *q, *r;
1630 static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
1631 unsigned inquotes;
1632 int notescaped;
1633 int globbing;
1634
1635 p = strpbrk(str, qchars);
1636 if (!p) {
1637 return str;
1638 }
1639 q = p;
1640 r = str;
1641 if (flag & RMESCAPE_ALLOC) {
1642 size_t len = p - str;
1643 size_t fulllen = len + strlen(p) + 1;
1644
1645 if (flag & RMESCAPE_GROW) {
1646 r = makestrspace(fulllen, expdest);
1647 } else if (flag & RMESCAPE_HEAP) {
1648 r = ckmalloc(fulllen);
1649 } else {
1650 r = stalloc(fulllen);
1651 }
1652 q = r;
1653 if (len > 0) {
1654 q = mempcpy(q, str, len);
1655 }
1656 }
1657 inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED;
1658 globbing = flag & RMESCAPE_GLOB;
1659 notescaped = globbing;
1660 while (*p) {
1661 if (*p == (char)CTLQUOTEMARK) {
1662 inquotes = ~inquotes;
1663 p++;
1664 notescaped = globbing;
1665 continue;
1666 }
1667 if (*p == '\\') {
1668 /* naked back slash */
1669 notescaped = 0;
1670 goto copy;
1671 }
1672 if (*p == (char)CTLESC) {
1673 p++;
1674 if (notescaped && inquotes && *p != '/') {
1675 *q++ = '\\';
1676 }
1677 }
1678 notescaped = globbing;
1679 copy:
1680 *q++ = *p++;
1681 }
1682 *q = '\0';
1683 if (flag & RMESCAPE_GROW) {
1684 expdest = r;
1685 STADJUST(q - r + 1, expdest);
1686 }
1687 return r;
1688 }
1689
1690
1691
1692 /*
1693 * See if a pattern matches in a case statement.
1694 */
1695
1696 int
1697 casematch(union node *pattern, char *val)
1698 {
1699 struct stackmark smark;
1700 int result;
1701
1702 setstackmark(&smark);
1703 argbackq = pattern->narg.backquote;
1704 STARTSTACKSTR(expdest);
1705 ifslastp = NULL;
1706 argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
1707 STACKSTRNUL(expdest);
1708 result = patmatch(stackblock(), val);
1709 popstackmark(&smark);
1710 return result;
1711 }
1712
1713 /*
1714 * Our own itoa().
1715 */
1716
1717 STATIC int
1718 cvtnum(long num)
1719 {
1720 int len;
1721
1722 expdest = makestrspace(32, expdest);
1723 len = fmtstr(expdest, 32, "%ld", num);
1724 STADJUST(len, expdest);
1725 return len;
1726 }
1727
1728 STATIC void
1729 varunset(const char *end, const char *var, const char *umsg, int varflags)
1730 {
1731 const char *msg;
1732 const char *tail;
1733
1734 tail = nullstr;
1735 msg = "parameter not set";
1736 if (umsg) {
1737 if (*end == (char)CTLENDVAR) {
1738 if (varflags & VSNUL)
1739 tail = " or null";
1740 } else
1741 msg = umsg;
1742 }
1743 sh_error("%.*s: %s%s", end - var - 1, var, msg, tail);
1744 }