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