VirtualBox

source: kBuild/trunk/src/kash/expand.c@ 3477

Last change on this file since 3477 was 3477, checked in by bird, 4 years ago

kash: Use kHlpAssert instead of assert.h (debugger stops on the assertion rather than at exit process code).

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id
File size: 33.9 KB
Line 
1/* $NetBSD: expand.c,v 1.71 2005/06/01 15:41:19 lukem Exp $ */
2
3/*-
4 * Copyright (c) 1991, 1993
5 * The Regents of the University of California. 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#if 0
36#ifndef lint
37static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95";
38#else
39__RCSID("$NetBSD: expand.c,v 1.71 2005/06/01 15:41:19 lukem Exp $");
40#endif /* not lint */
41#endif
42
43#include <sys/types.h>
44#include <errno.h>
45#include <stdlib.h>
46#include <stdio.h>
47
48/*
49 * Routines to expand arguments to commands. We have to deal with
50 * backquotes, shell variables, and file metacharacters.
51 */
52
53#include "shell.h"
54#include "main.h"
55#include "nodes.h"
56#include "eval.h"
57#include "expand.h"
58#include "syntax.h"
59#include "parser.h"
60#include "jobs.h"
61#include "options.h"
62#include "var.h"
63#include "input.h"
64#include "output.h"
65#include "memalloc.h"
66#include "error.h"
67#include "mystring.h"
68#include "show.h"
69#include "shinstance.h"
70
71///*
72// * Structure specifying which parts of the string should be searched
73// * for IFS characters.
74// */
75//
76//struct ifsregion {
77// struct ifsregion *next; /* next region in list */
78// int begoff; /* offset of start of region */
79// int endoff; /* offset of end of region */
80// int inquotes; /* search for nul bytes only */
81//};
82//
83//
84//char *expdest; /* output of current string */
85//struct nodelist *argbackq; /* list of back quote expressions */
86//struct ifsregion ifsfirst; /* first struct in list of ifs regions */
87//struct ifsregion *ifslastp; /* last struct in list */
88//struct arglist exparg; /* holds expanded arg list */
89
90STATIC void argstr(shinstance *, char *, int);
91STATIC void expari(shinstance *, int);
92STATIC char *exptilde(shinstance *, char *, int);
93STATIC void expbackq(shinstance *, union node *, int, int);
94STATIC int subevalvar(shinstance *, char *, char *, int, int, int, int);
95STATIC char *evalvar(shinstance *, char *, int);
96STATIC int varisset(shinstance *, char *, int);
97STATIC void varvalue(shinstance *, char *, int, int, int);
98STATIC void recordregion(shinstance *, int, int, int);
99STATIC void removerecordregions(shinstance *, int);
100STATIC void ifsbreakup(shinstance *, char *, struct arglist *);
101STATIC void ifsfree(shinstance *);
102STATIC void expandmeta(shinstance *, struct strlist *, int);
103STATIC void expmeta(shinstance *, char *, char *);
104STATIC void addfname(shinstance *, char *);
105STATIC struct strlist *expsort(struct strlist *);
106STATIC struct strlist *msort(struct strlist *, int);
107STATIC int pmatch(char *, char *, int);
108STATIC char *cvtnum(shinstance *, int, char *);
109STATIC char *cvtnum64(shinstance *, KI64, char *);
110
111/*
112 * Expand shell variables and backquotes inside a here document.
113 */
114
115void
116expandhere(shinstance *psh, union node *arg, int fd)
117{
118 psh->herefd = fd;
119 expandarg(psh, arg, (struct arglist *)NULL, 0);
120 xwrite(psh, fd, stackblock(psh), psh->expdest - stackblock(psh));
121}
122
123
124/*
125 * Perform variable substitution and command substitution on an argument,
126 * placing the resulting list of arguments in arglist. If EXP_FULL is true,
127 * perform splitting and file name expansion. When arglist is NULL, perform
128 * here document expansion.
129 */
130
131void
132expandarg(shinstance *psh, union node *arg, struct arglist *arglist, int flag)
133{
134 struct strlist *sp;
135 char *p;
136
137 psh->argbackq = arg->narg.backquote;
138 STARTSTACKSTR(psh, psh->expdest);
139 psh->ifsfirst.next = NULL;
140 psh->ifslastp = NULL;
141 argstr(psh, arg->narg.text, flag);
142 if (arglist == NULL) {
143 return; /* here document expanded */
144 }
145 STPUTC(psh, '\0', psh->expdest);
146 p = grabstackstr(psh, psh->expdest);
147 TRACE2((psh, "expandarg: p='%s'\n", p));
148 psh->exparg.lastp = &psh->exparg.list;
149 /*
150 * TODO - EXP_REDIR
151 */
152 if (flag & EXP_FULL) {
153 ifsbreakup(psh, p, &psh->exparg);
154 *psh->exparg.lastp = NULL;
155 psh->exparg.lastp = &psh->exparg.list;
156 expandmeta(psh, psh->exparg.list, flag);
157 } else {
158 if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
159 rmescapes(psh, p);
160 sp = (struct strlist *)stalloc(psh, sizeof (struct strlist));
161 sp->text = p;
162 *psh->exparg.lastp = sp;
163 psh->exparg.lastp = &sp->next;
164 }
165 ifsfree(psh);
166 *psh->exparg.lastp = NULL;
167 if (psh->exparg.list) {
168 *arglist->lastp = psh->exparg.list;
169 arglist->lastp = psh->exparg.lastp;
170 }
171}
172
173
174
175/*
176 * Perform variable and command substitution.
177 * If EXP_FULL is set, output CTLESC characters to allow for further processing.
178 * Otherwise treat $@ like $* since no splitting will be performed.
179 */
180
181STATIC void
182argstr(shinstance *psh, char *p, int flag)
183{
184 char c;
185 int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */
186 int firsteq = 1;
187 const char *ifs = NULL;
188 int ifs_split = EXP_IFS_SPLIT;
189
190 if (flag & EXP_IFS_SPLIT)
191 ifs = ifsset(psh) ? ifsval(psh) : " \t\n";
192
193 if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
194 p = exptilde(psh, p, flag);
195 for (;;) {
196 switch (c = *p++) {
197 case '\0':
198 case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */
199 return;
200 case CTLQUOTEMARK:
201 /* "$@" syntax adherence hack */
202 if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
203 break;
204 if ((flag & EXP_FULL) != 0)
205 STPUTC(psh, c, psh->expdest);
206 ifs_split = 0;
207 break;
208 case CTLQUOTEEND:
209 ifs_split = EXP_IFS_SPLIT;
210 break;
211 case CTLESC:
212 if (quotes)
213 STPUTC(psh, c, psh->expdest);
214 c = *p++;
215 STPUTC(psh, c, psh->expdest);
216 break;
217 case CTLVAR:
218 p = evalvar(psh, p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split));
219 break;
220 case CTLBACKQ:
221 case CTLBACKQ|CTLQUOTE:
222 expbackq(psh, psh->argbackq->n, c & CTLQUOTE, flag);
223 psh->argbackq = psh->argbackq->next;
224 break;
225 case CTLENDARI:
226 expari(psh, flag);
227 break;
228 case ':':
229 case '=':
230 /*
231 * sort of a hack - expand tildes in variable
232 * assignments (after the first '=' and after ':'s).
233 */
234 STPUTC(psh, c, psh->expdest);
235 if (flag & EXP_VARTILDE && *p == '~') {
236 if (c == '=') {
237 if (firsteq)
238 firsteq = 0;
239 else
240 break;
241 }
242 p = exptilde(psh, p, flag);
243 }
244 break;
245 default:
246 STPUTC(psh, c, psh->expdest);
247 if (flag & EXP_IFS_SPLIT & ifs_split && strchr(ifs, c) != NULL) {
248 /* We need to get the output split here... */
249 recordregion(psh, (int)(psh->expdest - stackblock(psh) - 1),
250 (int)(psh->expdest - stackblock(psh)), 0);
251 }
252 break;
253 }
254 }
255}
256
257STATIC char *
258exptilde(shinstance *psh, char *p, int flag)
259{
260 char c, *startp = p;
261 const char *home;
262 int quotes = flag & (EXP_FULL | EXP_CASE);
263
264 while ((c = *p) != '\0') {
265 switch(c) {
266 case CTLESC:
267 return (startp);
268 case CTLQUOTEMARK:
269 return (startp);
270 case ':':
271 if (flag & EXP_VARTILDE)
272 goto done;
273 break;
274 case '/':
275 goto done;
276 }
277 p++;
278 }
279done:
280 *p = '\0';
281 if (*(startp+1) == '\0') {
282 if ((home = lookupvar(psh, "HOME")) == NULL)
283 goto lose;
284 } else {
285 if ((home = sh_gethomedir(psh, startp+1)) == NULL)
286 goto lose;
287 }
288 if (*home == '\0')
289 goto lose;
290 *p = c;
291 while ((c = *home++) != '\0') {
292 if (quotes && SQSYNTAX[(int)c] == CCTL)
293 STPUTC(psh, CTLESC, psh->expdest);
294 STPUTC(psh, c, psh->expdest);
295 }
296 return (p);
297lose:
298 *p = c;
299 return (startp);
300}
301
302
303STATIC void
304removerecordregions(shinstance *psh, int endoff)
305{
306 if (psh->ifslastp == NULL)
307 return;
308
309 if (psh->ifsfirst.endoff > endoff) {
310 while (psh->ifsfirst.next != NULL) {
311 struct ifsregion *ifsp;
312 INTOFF;
313 ifsp = psh->ifsfirst.next->next;
314 ckfree(psh, psh->ifsfirst.next);
315 psh->ifsfirst.next = ifsp;
316 INTON;
317 }
318 if (psh->ifsfirst.begoff > endoff)
319 psh->ifslastp = NULL;
320 else {
321 psh->ifslastp = &psh->ifsfirst;
322 psh->ifsfirst.endoff = endoff;
323 }
324 return;
325 }
326
327 psh->ifslastp = &psh->ifsfirst;
328 while (psh->ifslastp->next && psh->ifslastp->next->begoff < endoff)
329 psh->ifslastp=psh->ifslastp->next;
330 while (psh->ifslastp->next != NULL) {
331 struct ifsregion *ifsp;
332 INTOFF;
333 ifsp = psh->ifslastp->next->next;
334 ckfree(psh, psh->ifslastp->next);
335 psh->ifslastp->next = ifsp;
336 INTON;
337 }
338 if (psh->ifslastp->endoff > endoff)
339 psh->ifslastp->endoff = endoff;
340}
341
342
343/*
344 * Expand arithmetic expression. Backup to start of expression,
345 * evaluate, place result in (backed up) result, adjust string position.
346 */
347STATIC void
348expari(shinstance *psh, int flag)
349{
350 char *p, *start;
351 int result;
352 int begoff;
353 int quotes = flag & (EXP_FULL | EXP_CASE);
354 int quoted;
355
356 /* ifsfree(); */
357
358 /*
359 * This routine is slightly over-complicated for
360 * efficiency. First we make sure there is
361 * enough space for the result, which may be bigger
362 * than the expression if we add exponentation. Next we
363 * scan backwards looking for the start of arithmetic. If the
364 * next previous character is a CTLESC character, then we
365 * have to rescan starting from the beginning since CTLESC
366 * characters have to be processed left to right.
367 */
368#if INT_MAX / 1000000000 >= 10 || INT_MIN / 1000000000 <= -10
369#error "integers with more than 10 digits are not supported"
370#endif
371 CHECKSTRSPACE(psh, 12 - 2, psh->expdest);
372 USTPUTC(psh, '\0', psh->expdest);
373 start = stackblock(psh);
374 p = psh->expdest - 1;
375 while (*p != CTLARI && p >= start)
376 --p;
377 if (*p != CTLARI)
378 error(psh, "missing CTLARI (shouldn't happen)");
379 if (p > start && *(p-1) == CTLESC)
380 for (p = start; *p != CTLARI; p++)
381 if (*p == CTLESC)
382 p++;
383
384 if (p[1] == '"')
385 quoted=1;
386 else
387 quoted=0;
388 begoff = (int)(p - start);
389 removerecordregions(psh, begoff);
390 if (quotes)
391 rmescapes(psh, p+2);
392 result = arith(psh, p+2);
393 fmtstr(p, 12, "%d", result);
394
395 while (*p++)
396 ;
397
398 if (quoted == 0)
399 recordregion(psh, begoff, (int)(p - 1 - start), 0);
400 result = (int)(psh->expdest - p + 1);
401 STADJUST(psh, -result, psh->expdest);
402}
403
404
405/*
406 * Expand stuff in backwards quotes.
407 */
408
409STATIC void
410expbackq(shinstance *psh, union node *cmd, int quoted, int flag)
411{
412 struct backcmd in;
413 int i;
414 char buf[128];
415 char *p;
416 char *dest = psh->expdest;
417 struct ifsregion saveifs, *savelastp;
418 struct nodelist *saveargbackq;
419 char lastc;
420 int startloc = (int)(dest - stackblock(psh));
421 char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
422 int saveherefd;
423 int quotes = flag & (EXP_FULL | EXP_CASE);
424#ifdef SH_DEAL_WITH_CRLF
425 int pending_cr = 0;
426#endif
427
428 INTOFF;
429 saveifs = psh->ifsfirst;
430 savelastp = psh->ifslastp;
431 saveargbackq = psh->argbackq;
432 saveherefd = psh->herefd;
433 psh->herefd = -1;
434 p = grabstackstr(psh, dest);
435 evalbackcmd(psh, cmd, &in);
436 ungrabstackstr(psh, p, dest);
437 psh->ifsfirst = saveifs;
438 psh->ifslastp = savelastp;
439 psh->argbackq = saveargbackq;
440 psh->herefd = saveherefd;
441
442 p = in.buf;
443 lastc = '\0';
444 for (;;) {
445 if (--in.nleft < 0) {
446 if (in.fd < 0)
447 break;
448 while ((i = shfile_read(&psh->fdtab, in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
449 TRACE((psh, "expbackq: read returns %d\n", i));
450 if (i <= 0)
451 break;
452 p = buf;
453 in.nleft = i - 1;
454 }
455 lastc = *p++;
456#ifdef SH_DEAL_WITH_CRLF
457 if (pending_cr) {
458 pending_cr = 0;
459 if (lastc != '\n') {
460 if (quotes && syntax[(int)'\r'] == CCTL)
461 STPUTC(psh, CTLESC, dest);
462 STPUTC(psh, '\r', dest);
463 }
464 }
465 if (lastc == '\r')
466 pending_cr = '\r';
467 else
468#endif
469 if (lastc != '\0') {
470 if (quotes && syntax[(int)lastc] == CCTL)
471 STPUTC(psh, CTLESC, dest);
472 STPUTC(psh, lastc, dest);
473 }
474 }
475#ifdef SH_DEAL_WITH_CRLF
476 if (pending_cr) {
477 if (quotes && syntax[(int)'\r'] == CCTL)
478 STPUTC(psh, CTLESC, dest);
479 STPUTC(psh, '\r', dest);
480 }
481#endif
482
483 /* Eat all trailing newlines */
484 p = stackblock(psh) + startloc;
485 while (dest > p && dest[-1] == '\n')
486 STUNPUTC(psh, dest);
487
488 if (in.fd >= 0)
489 shfile_close(&psh->fdtab, in.fd);
490 if (in.buf)
491 ckfree(psh, in.buf);
492 if (in.jp)
493 psh->back_exitstatus = waitforjob(psh, in.jp);
494 if (quoted == 0)
495 recordregion(psh, startloc, (int)(dest - stackblock(psh)), 0);
496 TRACE((psh, "evalbackq: size=%d: \"%.*s\"\n",
497 (dest - stackblock(psh)) - startloc,
498 (dest - stackblock(psh)) - startloc,
499 stackblock(psh) + startloc));
500 psh->expdest = dest;
501 INTON;
502}
503
504
505
506STATIC int
507subevalvar(shinstance *psh, char *p, char *str, int strloc, int subtype, int startloc, int varflags)
508{
509 char *startp;
510 char *loc = NULL;
511 char *q;
512 int c = 0;
513 int saveherefd = psh->herefd;
514 struct nodelist *saveargbackq = psh->argbackq;
515 int amount;
516
517 psh->herefd = -1;
518 argstr(psh, p, 0);
519 STACKSTRNUL(psh, psh->expdest);
520 psh->herefd = saveherefd;
521 psh->argbackq = saveargbackq;
522 startp = stackblock(psh) + startloc;
523 if (str == NULL)
524 str = stackblock(psh) + strloc;
525
526 switch (subtype) {
527 case VSASSIGN:
528 setvar(psh, str, startp, 0);
529 amount = (int)(startp - psh->expdest);
530 STADJUST(psh, amount, psh->expdest);
531 varflags &= ~VSNUL;
532 if (c != 0)
533 *loc = c;
534 return 1;
535
536 case VSQUESTION:
537 if (*p != CTLENDVAR) {
538 outfmt(&psh->errout, "%s\n", startp);
539 error(psh, (char *)NULL);
540 }
541 error(psh, "%.*s: parameter %snot set", p - str - 1,
542 str, (varflags & VSNUL) ? "null or "
543 : nullstr);
544 /* NOTREACHED */
545
546 case VSTRIMLEFT:
547 for (loc = startp; loc < str; loc++) {
548 c = *loc;
549 *loc = '\0';
550 if (patmatch(psh, str, startp, varflags & VSQUOTE))
551 goto recordleft;
552 *loc = c;
553 if ((varflags & VSQUOTE) && *loc == CTLESC)
554 loc++;
555 }
556 return 0;
557
558 case VSTRIMLEFTMAX:
559 for (loc = str - 1; loc >= startp;) {
560 c = *loc;
561 *loc = '\0';
562 if (patmatch(psh, str, startp, varflags & VSQUOTE))
563 goto recordleft;
564 *loc = c;
565 loc--;
566 if ((varflags & VSQUOTE) && loc > startp &&
567 *(loc - 1) == CTLESC) {
568 for (q = startp; q < loc; q++)
569 if (*q == CTLESC)
570 q++;
571 if (q > loc)
572 loc--;
573 }
574 }
575 return 0;
576
577 case VSTRIMRIGHT:
578 for (loc = str - 1; loc >= startp;) {
579 if (patmatch(psh, str, loc, varflags & VSQUOTE))
580 goto recordright;
581 loc--;
582 if ((varflags & VSQUOTE) && loc > startp &&
583 *(loc - 1) == CTLESC) {
584 for (q = startp; q < loc; q++)
585 if (*q == CTLESC)
586 q++;
587 if (q > loc)
588 loc--;
589 }
590 }
591 return 0;
592
593 case VSTRIMRIGHTMAX:
594 for (loc = startp; loc < str - 1; loc++) {
595 if (patmatch(psh, str, loc, varflags & VSQUOTE))
596 goto recordright;
597 if ((varflags & VSQUOTE) && *loc == CTLESC)
598 loc++;
599 }
600 return 0;
601
602 default:
603 sh_abort(psh);
604 }
605
606recordleft:
607 *loc = c;
608 amount = (int)(((str - 1) - (loc - startp)) - psh->expdest);
609 STADJUST(psh, amount, psh->expdest);
610 while (loc != str - 1)
611 *startp++ = *loc++;
612 return 1;
613
614recordright:
615 amount = (int)(loc - psh->expdest);
616 STADJUST(psh, amount, psh->expdest);
617 STPUTC(psh, '\0', psh->expdest);
618 STADJUST(psh, -1, psh->expdest);
619 return 1;
620}
621
622
623/*
624 * Expand a variable, and return a pointer to the next character in the
625 * input string.
626 */
627
628STATIC char *
629evalvar(shinstance *psh, char *p, int flag)
630{
631 int subtype;
632 int varflags;
633 char *var;
634 char *val;
635 int patloc;
636 int c;
637 int set;
638 int special;
639 int startloc;
640 int varlen;
641 int apply_ifs;
642 int quotes = flag & (EXP_FULL | EXP_CASE);
643
644 varflags = (unsigned char)*p++;
645 subtype = varflags & VSTYPE;
646 var = p;
647 special = !is_name(*p);
648 p = strchr(p, '=') + 1;
649
650again: /* jump here after setting a variable with ${var=text} */
651 if (special) {
652 set = varisset(psh, var, varflags & VSNUL);
653 val = NULL;
654 } else {
655 val = lookupvar(psh, var);
656 if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
657 val = NULL;
658 set = 0;
659 } else
660 set = 1;
661 }
662
663 varlen = 0;
664 startloc = (int)(psh->expdest - stackblock(psh));
665
666 if (!set && uflag(psh)) {
667 switch (subtype) {
668 case VSNORMAL:
669 case VSTRIMLEFT:
670 case VSTRIMLEFTMAX:
671 case VSTRIMRIGHT:
672 case VSTRIMRIGHTMAX:
673 case VSLENGTH:
674 error(psh, "%.*s: parameter not set", p - var - 1, var);
675 /* NOTREACHED */
676 }
677 }
678
679 if (set && subtype != VSPLUS) {
680 /* insert the value of the variable */
681 if (special) {
682 varvalue(psh, var, varflags & VSQUOTE, subtype, flag);
683 if (subtype == VSLENGTH) {
684 varlen = (int)(psh->expdest - stackblock(psh) - startloc);
685 STADJUST(psh, -varlen, psh->expdest);
686 }
687 } else {
688 char const *syntax = (varflags & VSQUOTE) ? DQSYNTAX
689 : BASESYNTAX;
690
691 if (subtype == VSLENGTH) {
692 for (;*val; val++)
693 varlen++;
694 } else {
695 while (*val) {
696 if (quotes && syntax[(int)*val] == CCTL)
697 STPUTC(psh, CTLESC, psh->expdest);
698 STPUTC(psh, *val++, psh->expdest);
699 }
700
701 }
702 }
703 }
704
705
706 apply_ifs = ((varflags & VSQUOTE) == 0 ||
707 (*var == '@' && psh->shellparam.nparam != 1));
708
709 switch (subtype) {
710 case VSLENGTH:
711 psh->expdest = cvtnum(psh, varlen, psh->expdest);
712 break;
713
714 case VSNORMAL:
715 break;
716
717 case VSPLUS:
718 set = !set;
719 /* FALLTHROUGH */
720 case VSMINUS:
721 if (!set) {
722 argstr(psh, p, flag | (apply_ifs ? EXP_IFS_SPLIT : 0));
723 /*
724 * ${x-a b c} doesn't get split, but removing the
725 * 'apply_ifs = 0' apparantly breaks ${1+"$@"}..
726 * ${x-'a b' c} should generate 2 args.
727 */
728 /* We should have marked stuff already */
729 apply_ifs = 0;
730 }
731 break;
732
733 case VSTRIMLEFT:
734 case VSTRIMLEFTMAX:
735 case VSTRIMRIGHT:
736 case VSTRIMRIGHTMAX:
737 if (!set)
738 break;
739 /*
740 * Terminate the string and start recording the pattern
741 * right after it
742 */
743 STPUTC(psh, '\0', psh->expdest);
744 patloc = (int)(psh->expdest - stackblock(psh));
745 if (subevalvar(psh, p, NULL, patloc, subtype,
746 startloc, varflags) == 0) {
747 int amount = (int)(psh->expdest - stackblock(psh) - patloc) + 1;
748 STADJUST(psh, -amount, psh->expdest);
749 }
750 /* Remove any recorded regions beyond start of variable */
751 removerecordregions(psh, startloc);
752 apply_ifs = 1;
753 break;
754
755 case VSASSIGN:
756 case VSQUESTION:
757 if (set)
758 break;
759 if (subevalvar(psh, p, var, 0, subtype, startloc, varflags)) {
760 varflags &= ~VSNUL;
761 /*
762 * Remove any recorded regions beyond
763 * start of variable
764 */
765 removerecordregions(psh, startloc);
766 goto again;
767 }
768 apply_ifs = 0;
769 break;
770
771 default:
772 sh_abort(psh);
773 }
774
775 if (apply_ifs)
776 recordregion(psh, startloc, (int)(psh->expdest - stackblock(psh)),
777 varflags & VSQUOTE);
778
779 if (subtype != VSNORMAL) { /* skip to end of alternative */
780 int nesting = 1;
781 for (;;) {
782 if ((c = *p++) == CTLESC)
783 p++;
784 else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
785 if (set)
786 psh->argbackq = psh->argbackq->next;
787 } else if (c == CTLVAR) {
788 if ((*p++ & VSTYPE) != VSNORMAL)
789 nesting++;
790 } else if (c == CTLENDVAR) {
791 if (--nesting == 0)
792 break;
793 }
794 }
795 }
796 return p;
797}
798
799
800
801/*
802 * Test whether a specialized variable is set.
803 */
804
805STATIC int
806varisset(shinstance *psh, char *name, int nulok)
807{
808 if (*name == '!')
809 return psh->backgndpid != -1;
810 else if (*name == '@' || *name == '*') {
811 if (*psh->shellparam.p == NULL)
812 return 0;
813
814 if (nulok) {
815 char **av;
816
817 for (av = psh->shellparam.p; *av; av++)
818 if (**av != '\0')
819 return 1;
820 return 0;
821 }
822 } else if (is_digit(*name)) {
823 char *ap;
824 int num = atoi(name);
825
826 if (num > psh->shellparam.nparam)
827 return 0;
828
829 if (num == 0)
830 ap = psh->arg0;
831 else
832 ap = psh->shellparam.p[num - 1];
833
834 if (nulok && (ap == NULL || *ap == '\0'))
835 return 0;
836 }
837 return 1;
838}
839
840
841
842/*
843 * Add the value of a specialized variable to the stack string.
844 */
845
846STATIC void
847varvalue(shinstance *psh, char *name, int quoted, int subtype, int flag)
848{
849 int num;
850 char *p;
851 int i;
852 char sep;
853 char **ap;
854 char const *syntax;
855
856#define STRTODEST(p) \
857 do {\
858 if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) { \
859 syntax = quoted? DQSYNTAX : BASESYNTAX; \
860 while (*p) { \
861 if (syntax[(int)*p] == CCTL) \
862 STPUTC(psh, CTLESC, psh->expdest); \
863 STPUTC(psh, *p++, psh->expdest); \
864 } \
865 } else \
866 while (*p) \
867 STPUTC(psh, *p++, psh->expdest); \
868 } while (0)
869
870
871 switch (*name) {
872 case '$':
873#ifndef SH_FORKED_MODE
874 psh->expdest = cvtnum64(psh, psh->rootpid, psh->expdest);
875 break;
876#else
877 num = psh->rootpid;
878 goto numvar;
879#endif
880 case '?':
881 num = psh->exitstatus;
882 goto numvar;
883 case '#':
884 num = psh->shellparam.nparam;
885numvar:
886 psh->expdest = cvtnum(psh, num, psh->expdest);
887 break;
888 case '!':
889#ifndef SH_FORKED_MODE
890 psh->expdest = cvtnum64(psh, psh->backgndpid, psh->expdest);
891 break;
892#else
893 num = psh->backgndpid;
894 goto numvar;
895#endif
896 case '-':
897 for (i = 0; psh->optlist[i].name; i++) {
898 if (psh->optlist[i].val)
899 STPUTC(psh, psh->optlist[i].letter, psh->expdest);
900 }
901 break;
902 case '@':
903 if (flag & EXP_FULL && quoted) {
904 for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) {
905 STRTODEST(p);
906 if (*ap)
907 STPUTC(psh, '\0', psh->expdest);
908 }
909 break;
910 }
911 /* fall through */
912 case '*':
913 if (ifsset(psh) != 0)
914 sep = ifsval(psh)[0];
915 else
916 sep = ' ';
917 for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) {
918 STRTODEST(p);
919 if (*ap && sep)
920 STPUTC(psh, sep, psh->expdest);
921 }
922 break;
923 case '0':
924 p = psh->arg0;
925 STRTODEST(p);
926 break;
927 default:
928 if (is_digit(*name)) {
929 num = atoi(name);
930 if (num > 0 && num <= psh->shellparam.nparam) {
931 p = psh->shellparam.p[num - 1];
932 STRTODEST(p);
933 }
934 }
935 break;
936 }
937}
938
939
940
941/*
942 * Record the fact that we have to scan this region of the
943 * string for IFS characters.
944 */
945
946STATIC void
947recordregion(shinstance *psh, int start, int end, int inquotes)
948{
949 struct ifsregion *ifsp;
950
951 if (psh->ifslastp == NULL) {
952 ifsp = &psh->ifsfirst;
953 } else {
954 if (psh->ifslastp->endoff == start
955 && psh->ifslastp->inquotes == inquotes) {
956 /* extend previous area */
957 psh->ifslastp->endoff = end;
958 return;
959 }
960 ifsp = (struct ifsregion *)ckmalloc(psh, sizeof (struct ifsregion));
961 psh->ifslastp->next = ifsp;
962 }
963 psh->ifslastp = ifsp;
964 psh->ifslastp->next = NULL;
965 psh->ifslastp->begoff = start;
966 psh->ifslastp->endoff = end;
967 psh->ifslastp->inquotes = inquotes;
968}
969
970
971
972/*
973 * Break the argument string into pieces based upon IFS and add the
974 * strings to the argument list. The regions of the string to be
975 * searched for IFS characters have been stored by recordregion.
976 */
977STATIC void
978ifsbreakup(shinstance *psh, char *string, struct arglist *arglist)
979{
980 struct ifsregion *ifsp;
981 struct strlist *sp;
982 char *start;
983 char *p;
984 char *q;
985 const char *ifs;
986 const char *ifsspc;
987 int inquotes;
988
989 start = string;
990 ifsspc = NULL;
991 inquotes = 0;
992
993 if (psh->ifslastp == NULL) {
994 /* Return entire argument, IFS doesn't apply to any of it */
995 sp = (struct strlist *)stalloc(psh, sizeof *sp);
996 sp->text = start;
997 *arglist->lastp = sp;
998 arglist->lastp = &sp->next;
999 return;
1000 }
1001
1002 ifs = ifsset(psh) ? ifsval(psh) : " \t\n";
1003
1004 for (ifsp = &psh->ifsfirst; ifsp != NULL; ifsp = ifsp->next) {
1005 p = string + ifsp->begoff;
1006 inquotes = ifsp->inquotes;
1007 ifsspc = NULL;
1008 while (p < string + ifsp->endoff) {
1009 q = p;
1010 if (*p == CTLESC)
1011 p++;
1012 if (inquotes) {
1013 /* Only NULs (probably from "$@") end args */
1014 if (*p != 0) {
1015 p++;
1016 continue;
1017 }
1018 } else {
1019 if (!strchr(ifs, *p)) {
1020 p++;
1021 continue;
1022 }
1023 ifsspc = strchr(" \t\n", *p);
1024
1025 /* Ignore IFS whitespace at start */
1026 if (q == start && ifsspc != NULL) {
1027 p++;
1028 start = p;
1029 continue;
1030 }
1031 }
1032
1033 /* Save this argument... */
1034 *q = '\0';
1035 sp = (struct strlist *)stalloc(psh, sizeof *sp);
1036 sp->text = start;
1037 *arglist->lastp = sp;
1038 arglist->lastp = &sp->next;
1039 p++;
1040
1041 if (ifsspc != NULL) {
1042 /* Ignore further trailing IFS whitespace */
1043 for (; p < string + ifsp->endoff; p++) {
1044 q = p;
1045 if (*p == CTLESC)
1046 p++;
1047 if (strchr(ifs, *p) == NULL) {
1048 p = q;
1049 break;
1050 }
1051 if (strchr(" \t\n", *p) == NULL) {
1052 p++;
1053 break;
1054 }
1055 }
1056 }
1057 start = p;
1058 }
1059 }
1060
1061 /*
1062 * Save anything left as an argument.
1063 * Traditionally we have treated 'IFS=':'; set -- x$IFS' as
1064 * generating 2 arguments, the second of which is empty.
1065 * Some recent clarification of the Posix spec say that it
1066 * should only generate one....
1067 */
1068 if (*start /* || (!ifsspc && start > string) */) {
1069 sp = (struct strlist *)stalloc(psh, sizeof *sp);
1070 sp->text = start;
1071 *arglist->lastp = sp;
1072 arglist->lastp = &sp->next;
1073 }
1074}
1075
1076STATIC void
1077ifsfree(shinstance *psh)
1078{
1079 while (psh->ifsfirst.next != NULL) {
1080 struct ifsregion *ifsp;
1081 INTOFF;
1082 ifsp = psh->ifsfirst.next->next;
1083 ckfree(psh, psh->ifsfirst.next);
1084 psh->ifsfirst.next = ifsp;
1085 INTON;
1086 }
1087 psh->ifslastp = NULL;
1088 psh->ifsfirst.next = NULL;
1089}
1090
1091
1092
1093/*
1094 * Expand shell metacharacters. At this point, the only control characters
1095 * should be escapes. The results are stored in the list psh->exparg.
1096 */
1097
1098//char *expdir;
1099
1100
1101STATIC void
1102expandmeta(shinstance *psh, struct strlist *str, int flag)
1103{
1104 char *p;
1105 struct strlist **savelastp;
1106 struct strlist *sp;
1107 char c;
1108 /* TODO - EXP_REDIR */
1109
1110 while (str) {
1111 if (fflag(psh))
1112 goto nometa;
1113 p = str->text;
1114 for (;;) { /* fast check for meta chars */
1115 if ((c = *p++) == '\0')
1116 goto nometa;
1117 if (c == '*' || c == '?' || c == '[' || c == '!')
1118 break;
1119 }
1120 savelastp = psh->exparg.lastp;
1121 INTOFF;
1122 if (psh->expdir == NULL) {
1123 size_t i = strlen(str->text);
1124 psh->expdir = ckmalloc(psh, i < 2048 ? 2048 : i); /* XXX */
1125 }
1126
1127 expmeta(psh, psh->expdir, str->text);
1128 ckfree(psh, psh->expdir);
1129 psh->expdir = NULL;
1130 INTON;
1131 if (psh->exparg.lastp == savelastp) {
1132 /*
1133 * no matches
1134 */
1135nometa:
1136 *psh->exparg.lastp = str;
1137 rmescapes(psh, str->text);
1138 psh->exparg.lastp = &str->next;
1139 } else {
1140 *psh->exparg.lastp = NULL;
1141 *savelastp = sp = expsort(*savelastp);
1142 while (sp->next != NULL)
1143 sp = sp->next;
1144 psh->exparg.lastp = &sp->next;
1145 }
1146 str = str->next;
1147 }
1148}
1149
1150
1151/*
1152 * Do metacharacter (i.e. *, ?, [...]) expansion.
1153 */
1154
1155STATIC void
1156expmeta(shinstance *psh, char *enddir, char *name)
1157{
1158 char *p;
1159 const char *cp;
1160 char *q;
1161 char *start;
1162 char *endname;
1163 int metaflag;
1164 struct stat statb;
1165 shdir *dirp;
1166 shdirent *dp;
1167 int atend;
1168 int matchdot;
1169
1170 metaflag = 0;
1171 start = name;
1172 for (p = name ; ; p++) {
1173 if (*p == '*' || *p == '?')
1174 metaflag = 1;
1175 else if (*p == '[') {
1176 q = p + 1;
1177 if (*q == '!')
1178 q++;
1179 for (;;) {
1180 while (*q == CTLQUOTEMARK)
1181 q++;
1182 if (*q == CTLESC)
1183 q++;
1184 if (*q == '/' || *q == '\0')
1185 break;
1186 if (*++q == ']') {
1187 metaflag = 1;
1188 break;
1189 }
1190 }
1191 } else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) {
1192 metaflag = 1;
1193 } else if (*p == '\0')
1194 break;
1195 else if (*p == CTLQUOTEMARK)
1196 continue;
1197 else if (*p == CTLESC)
1198 p++;
1199 if (*p == '/') {
1200 if (metaflag)
1201 break;
1202 start = p + 1;
1203 }
1204 }
1205 if (metaflag == 0) { /* we've reached the end of the file name */
1206 if (enddir != psh->expdir)
1207 metaflag++;
1208 for (p = name ; ; p++) {
1209 if (*p == CTLQUOTEMARK)
1210 continue;
1211 if (*p == CTLESC)
1212 p++;
1213 *enddir++ = *p;
1214 if (*p == '\0')
1215 break;
1216 }
1217 if (metaflag == 0 || shfile_lstat(&psh->fdtab, psh->expdir, &statb) >= 0)
1218 addfname(psh, psh->expdir);
1219 TRACE2((psh, "expandarg: return #1 (metaflag=%d)\n", metaflag));
1220 return;
1221 }
1222 endname = p;
1223 if (start != name) {
1224 p = name;
1225 while (p < start) {
1226 while (*p == CTLQUOTEMARK)
1227 p++;
1228 if (*p == CTLESC)
1229 p++;
1230 *enddir++ = *p++;
1231 }
1232 }
1233 if (enddir == psh->expdir) {
1234 cp = ".";
1235 } else if (enddir == psh->expdir + 1 && *psh->expdir == '/') {
1236 cp = "/";
1237 } else {
1238 cp = psh->expdir;
1239 enddir[-1] = '\0';
1240 }
1241 if ((dirp = shfile_opendir(&psh->fdtab, cp)) == NULL) {
1242 TRACE2((psh, "expandarg: return #2 (shfile_opendir(,%s) failed)\n", cp));
1243 return;
1244 }
1245 if (enddir != psh->expdir)
1246 enddir[-1] = '/';
1247 if (*endname == 0) {
1248 atend = 1;
1249 } else {
1250 atend = 0;
1251 *endname++ = '\0';
1252 }
1253 matchdot = 0;
1254 p = start;
1255 while (*p == CTLQUOTEMARK)
1256 p++;
1257 if (*p == CTLESC)
1258 p++;
1259 if (*p == '.')
1260 matchdot++;
1261 while (! int_pending() && (dp = shfile_readdir(dirp)) != NULL) {
1262 if (dp->name[0] == '.' && ! matchdot)
1263 continue;
1264 if (patmatch(psh, start, dp->name, 0)) {
1265 if (atend) {
1266 scopy(dp->name, enddir);
1267 addfname(psh, psh->expdir);
1268 } else {
1269 for (p = enddir, cp = dp->name;
1270 (*p++ = *cp++) != '\0';)
1271 continue;
1272 p[-1] = '/';
1273 expmeta(psh, p, endname);
1274 }
1275 }
1276 }
1277 shfile_closedir(dirp);
1278 if (! atend)
1279 endname[-1] = '/';
1280}
1281
1282
1283/*
1284 * Add a file name to the list.
1285 */
1286
1287STATIC void
1288addfname(shinstance *psh, char *name)
1289{
1290 char *p;
1291 struct strlist *sp;
1292
1293 p = stalloc(psh, strlen(name) + 1);
1294 scopy(name, p);
1295 sp = (struct strlist *)stalloc(psh, sizeof *sp);
1296 sp->text = p;
1297 *psh->exparg.lastp = sp;
1298 psh->exparg.lastp = &sp->next;
1299}
1300
1301
1302/*
1303 * Sort the results of file name expansion. It calculates the number of
1304 * strings to sort and then calls msort (short for merge sort) to do the
1305 * work.
1306 */
1307
1308STATIC struct strlist *
1309expsort(struct strlist *str)
1310{
1311 int len;
1312 struct strlist *sp;
1313
1314 len = 0;
1315 for (sp = str ; sp ; sp = sp->next)
1316 len++;
1317 return msort(str, len);
1318}
1319
1320
1321STATIC struct strlist *
1322msort(struct strlist *list, int len)
1323{
1324 struct strlist *p, *q = NULL;
1325 struct strlist **lpp;
1326 int half;
1327 int n;
1328
1329 if (len <= 1)
1330 return list;
1331 half = len >> 1;
1332 p = list;
1333 for (n = half ; --n >= 0 ; ) {
1334 q = p;
1335 p = p->next;
1336 }
1337 q->next = NULL; /* terminate first half of list */
1338 q = msort(list, half); /* sort first half of list */
1339 p = msort(p, len - half); /* sort second half */
1340 lpp = &list;
1341 for (;;) {
1342 if (strcmp(p->text, q->text) < 0) {
1343 *lpp = p;
1344 lpp = &p->next;
1345 if ((p = *lpp) == NULL) {
1346 *lpp = q;
1347 break;
1348 }
1349 } else {
1350 *lpp = q;
1351 lpp = &q->next;
1352 if ((q = *lpp) == NULL) {
1353 *lpp = p;
1354 break;
1355 }
1356 }
1357 }
1358 return list;
1359}
1360
1361
1362
1363/*
1364 * Returns true if the pattern matches the string.
1365 */
1366
1367int
1368patmatch(shinstance *psh, char *pattern, char *string, int squoted)
1369{
1370#ifdef notdef
1371 if (pattern[0] == '!' && pattern[1] == '!')
1372 return 1 - pmatch(pattern + 2, string);
1373 else
1374#endif
1375 return pmatch(pattern, string, squoted);
1376}
1377
1378
1379STATIC int
1380pmatch(char *pattern, char *string, int squoted)
1381{
1382 char *p, *q;
1383 char c;
1384
1385 p = pattern;
1386 q = string;
1387 for (;;) {
1388 switch (c = *p++) {
1389 case '\0':
1390 goto breakloop;
1391 case CTLESC:
1392 if (squoted && *q == CTLESC)
1393 q++;
1394 if (*q++ != *p++)
1395 return 0;
1396 break;
1397 case CTLQUOTEMARK:
1398 continue;
1399 case '?':
1400 if (squoted && *q == CTLESC)
1401 q++;
1402 if (*q++ == '\0')
1403 return 0;
1404 break;
1405 case '*':
1406 c = *p;
1407 while (c == CTLQUOTEMARK || c == '*')
1408 c = *++p;
1409 if (c != CTLESC && c != CTLQUOTEMARK &&
1410 c != '?' && c != '*' && c != '[') {
1411 while (*q != c) {
1412 if (squoted && *q == CTLESC &&
1413 q[1] == c)
1414 break;
1415 if (*q == '\0')
1416 return 0;
1417 if (squoted && *q == CTLESC)
1418 q++;
1419 q++;
1420 }
1421 }
1422 do {
1423 if (pmatch(p, q, squoted))
1424 return 1;
1425 if (squoted && *q == CTLESC)
1426 q++;
1427 } while (*q++ != '\0');
1428 return 0;
1429 case '[': {
1430 char *endp;
1431 int invert, found;
1432 char chr;
1433
1434 endp = p;
1435 if (*endp == '!')
1436 endp++;
1437 for (;;) {
1438 while (*endp == CTLQUOTEMARK)
1439 endp++;
1440 if (*endp == '\0')
1441 goto dft; /* no matching ] */
1442 if (*endp == CTLESC)
1443 endp++;
1444 if (*++endp == ']')
1445 break;
1446 }
1447 invert = 0;
1448 if (*p == '!') {
1449 invert++;
1450 p++;
1451 }
1452 found = 0;
1453 chr = *q++;
1454 if (squoted && chr == CTLESC)
1455 chr = *q++;
1456 if (chr == '\0')
1457 return 0;
1458 c = *p++;
1459 do {
1460 if (c == CTLQUOTEMARK)
1461 continue;
1462 if (c == CTLESC)
1463 c = *p++;
1464 if (*p == '-' && p[1] != ']') {
1465 p++;
1466 while (*p == CTLQUOTEMARK)
1467 p++;
1468 if (*p == CTLESC)
1469 p++;
1470 if (chr >= c && chr <= *p)
1471 found = 1;
1472 p++;
1473 } else {
1474 if (chr == c)
1475 found = 1;
1476 }
1477 } while ((c = *p++) != ']');
1478 if (found == invert)
1479 return 0;
1480 break;
1481 }
1482dft: default:
1483 if (squoted && *q == CTLESC)
1484 q++;
1485 if (*q++ != c)
1486 return 0;
1487 break;
1488 }
1489 }
1490breakloop:
1491 if (*q != '\0')
1492 return 0;
1493 return 1;
1494}
1495
1496
1497
1498/*
1499 * Remove any CTLESC characters from a string.
1500 */
1501
1502void
1503rmescapes(shinstance *psh, char *str)
1504{
1505 char *p, *q;
1506
1507 p = str;
1508 while (*p != CTLESC && *p != CTLQUOTEMARK) {
1509 if (*p++ == '\0')
1510 return;
1511 }
1512 q = p;
1513 while (*p) {
1514 if (*p == CTLQUOTEMARK) {
1515 p++;
1516 continue;
1517 }
1518 if (*p == CTLESC)
1519 p++;
1520 *q++ = *p++;
1521 }
1522 *q = '\0';
1523}
1524
1525
1526
1527/*
1528 * See if a pattern matches in a case statement.
1529 */
1530
1531int
1532casematch(shinstance *psh, union node *pattern, char *val)
1533{
1534 struct stackmark smark;
1535 int result;
1536 char *p;
1537
1538 setstackmark(psh, &smark);
1539 psh->argbackq = pattern->narg.backquote;
1540 STARTSTACKSTR(psh, psh->expdest);
1541 psh->ifslastp = NULL;
1542 argstr(psh, pattern->narg.text, EXP_TILDE | EXP_CASE);
1543 STPUTC(psh, '\0', psh->expdest);
1544 p = grabstackstr(psh, psh->expdest);
1545 result = patmatch(psh, p, val, 0);
1546 popstackmark(psh, &smark);
1547 return result;
1548}
1549
1550/*
1551 * Our own itoa().
1552 */
1553
1554STATIC char *
1555cvtnum(shinstance *psh, int num, char *buf)
1556{
1557 char temp[32];
1558 int neg = num < 0;
1559 char *p = temp + 31;
1560
1561 temp[31] = '\0';
1562
1563 do {
1564 *--p = num % 10 + '0';
1565 } while ((num /= 10) != 0);
1566
1567 if (neg)
1568 *--p = '-';
1569
1570 while (*p)
1571 STPUTC(psh, *p++, buf);
1572 return buf;
1573}
1574
1575STATIC char *
1576cvtnum64(shinstance *psh, KI64 num, char *buf)
1577{
1578 char temp[32];
1579 int neg = num < 0;
1580 char *p = temp + 31;
1581
1582 temp[31] = '\0';
1583
1584 do {
1585 *--p = num % 10 + '0';
1586 } while ((num /= 10) != 0);
1587
1588 if (neg)
1589 *--p = '-';
1590
1591 while (*p)
1592 STPUTC(psh, *p++, buf);
1593 return buf;
1594}
1595
1596/*
1597 * Do most of the work for wordexp(3).
1598 */
1599
1600int
1601wordexpcmd(shinstance *psh, int argc, char **argv)
1602{
1603 size_t len;
1604 int i;
1605
1606 out1fmt(psh, "%d", argc - 1);
1607 out1c(psh, '\0');
1608 for (i = 1, len = 0; i < argc; i++)
1609 len += strlen(argv[i]);
1610 out1fmt(psh, "%zd", len);
1611 out1c(psh, '\0');
1612 for (i = 1; i < argc; i++) {
1613 out1str(psh, argv[i]);
1614 out1c(psh, '\0');
1615 }
1616 return (0);
1617}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette