expand.c revision 223060
190792Sgshapiro/*-
290792Sgshapiro * Copyright (c) 1991, 1993
390792Sgshapiro *	The Regents of the University of California.  All rights reserved.
490792Sgshapiro * Copyright (c) 1997-2005
590792Sgshapiro *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
690792Sgshapiro *
790792Sgshapiro * This code is derived from software contributed to Berkeley by
890792Sgshapiro * Kenneth Almquist.
990792Sgshapiro *
1090792Sgshapiro * Redistribution and use in source and binary forms, with or without
1194334Sgshapiro * modification, are permitted provided that the following conditions
1290792Sgshapiro * are met:
1390792Sgshapiro * 1. Redistributions of source code must retain the above copyright
1490792Sgshapiro *    notice, this list of conditions and the following disclaimer.
1590792Sgshapiro * 2. Redistributions in binary form must reproduce the above copyright
1690792Sgshapiro *    notice, this list of conditions and the following disclaimer in the
1790792Sgshapiro *    documentation and/or other materials provided with the distribution.
1890792Sgshapiro * 4. Neither the name of the University nor the names of its contributors
1990792Sgshapiro *    may be used to endorse or promote products derived from this software
2090792Sgshapiro *    without specific prior written permission.
2190792Sgshapiro *
2290792Sgshapiro * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2390792Sgshapiro * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2490792Sgshapiro * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2590792Sgshapiro * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2690792Sgshapiro * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2794334Sgshapiro * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2894334Sgshapiro * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2994334Sgshapiro * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3090792Sgshapiro * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3190792Sgshapiro * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3290792Sgshapiro * SUCH DAMAGE.
3390792Sgshapiro */
3490792Sgshapiro
3590792Sgshapiro#ifndef lint
3690792Sgshapiro#if 0
3790792Sgshapirostatic char sccsid[] = "@(#)expand.c	8.5 (Berkeley) 5/15/95";
3890792Sgshapiro#endif
3990792Sgshapiro#endif /* not lint */
4090792Sgshapiro#include <sys/cdefs.h>
4190792Sgshapiro__FBSDID("$FreeBSD: head/bin/sh/expand.c 223060 2011-06-13 21:03:27Z jilles $");
4290792Sgshapiro
4390792Sgshapiro#include <sys/types.h>
4490792Sgshapiro#include <sys/time.h>
4590792Sgshapiro#include <sys/stat.h>
4690792Sgshapiro#include <dirent.h>
4790792Sgshapiro#include <errno.h>
4890792Sgshapiro#include <inttypes.h>
4990792Sgshapiro#include <limits.h>
5090792Sgshapiro#include <pwd.h>
5190792Sgshapiro#include <stdio.h>
5290792Sgshapiro#include <stdlib.h>
5390792Sgshapiro#include <string.h>
5490792Sgshapiro#include <unistd.h>
5594334Sgshapiro#include <wchar.h>
5690792Sgshapiro
5794334Sgshapiro/*
5894334Sgshapiro * Routines to expand arguments to commands.  We have to deal with
5994334Sgshapiro * backquotes, shell variables, and file metacharacters.
6094334Sgshapiro */
6194334Sgshapiro
6294334Sgshapiro#include "shell.h"
6390792Sgshapiro#include "main.h"
6490792Sgshapiro#include "nodes.h"
6590792Sgshapiro#include "eval.h"
6690792Sgshapiro#include "expand.h"
6790792Sgshapiro#include "syntax.h"
6890792Sgshapiro#include "parser.h"
6990792Sgshapiro#include "jobs.h"
7090792Sgshapiro#include "options.h"
7190792Sgshapiro#include "var.h"
7290792Sgshapiro#include "input.h"
7390792Sgshapiro#include "output.h"
7490792Sgshapiro#include "memalloc.h"
7590792Sgshapiro#include "error.h"
7690792Sgshapiro#include "mystring.h"
7790792Sgshapiro#include "arith.h"
7890792Sgshapiro#include "show.h"
7990792Sgshapiro#include "builtins.h"
8090792Sgshapiro
8190792Sgshapiro/*
8290792Sgshapiro * Structure specifying which parts of the string should be searched
8390792Sgshapiro * for IFS characters.
8494334Sgshapiro */
8594334Sgshapiro
8690792Sgshapirostruct ifsregion {
8790792Sgshapiro	struct ifsregion *next;	/* next region in list */
8890792Sgshapiro	int begoff;		/* offset of start of region */
8990792Sgshapiro	int endoff;		/* offset of end of region */
9090792Sgshapiro	int inquotes;		/* search for nul bytes only */
9190792Sgshapiro};
9290792Sgshapiro
9390792Sgshapiro
9490792Sgshapirostatic char *expdest;			/* output of current string */
9590792Sgshapirostatic struct nodelist *argbackq;	/* list of back quote expressions */
9690792Sgshapirostatic struct ifsregion ifsfirst;	/* first struct in list of ifs regions */
9790792Sgshapirostatic struct ifsregion *ifslastp;	/* last struct in list */
9890792Sgshapirostatic struct arglist exparg;		/* holds expanded arg list */
9990792Sgshapiro
10090792Sgshapirostatic void argstr(char *, int);
10190792Sgshapirostatic char *exptilde(char *, int);
10290792Sgshapirostatic void expbackq(union node *, int, int);
10390792Sgshapirostatic int subevalvar(char *, char *, int, int, int, int, int);
10490792Sgshapirostatic char *evalvar(char *, int);
10590792Sgshapirostatic int varisset(char *, int);
10690792Sgshapirostatic void varvalue(char *, int, int, int);
10790792Sgshapirostatic void recordregion(int, int, int);
10890792Sgshapirostatic void removerecordregions(int);
10990792Sgshapirostatic void ifsbreakup(char *, struct arglist *);
11090792Sgshapirostatic void expandmeta(struct strlist *, int);
11190792Sgshapirostatic void expmeta(char *, char *);
11290792Sgshapirostatic void addfname(char *);
11390792Sgshapirostatic struct strlist *expsort(struct strlist *);
11490792Sgshapirostatic struct strlist *msort(struct strlist *, int);
11590792Sgshapirostatic char *cvtnum(int, char *);
11690792Sgshapirostatic int collate_range_cmp(wchar_t, wchar_t);
11790792Sgshapiro
11890792Sgshapirostatic int
11990792Sgshapirocollate_range_cmp(wchar_t c1, wchar_t c2)
12090792Sgshapiro{
12190792Sgshapiro	static wchar_t s1[2], s2[2];
12290792Sgshapiro
12390792Sgshapiro	s1[0] = c1;
12490792Sgshapiro	s2[0] = c2;
12590792Sgshapiro	return (wcscoll(s1, s2));
12690792Sgshapiro}
12790792Sgshapiro
12890792Sgshapiro/*
12990792Sgshapiro * Expand shell variables and backquotes inside a here document.
13090792Sgshapiro *	union node *arg		the document
13190792Sgshapiro *	int fd;			where to write the expanded version
13290792Sgshapiro */
13390792Sgshapiro
13490792Sgshapirovoid
13590792Sgshapiroexpandhere(union node *arg, int fd)
13690792Sgshapiro{
13790792Sgshapiro	expandarg(arg, (struct arglist *)NULL, 0);
13890792Sgshapiro	xwrite(fd, stackblock(), expdest - stackblock());
13990792Sgshapiro}
14090792Sgshapiro
14190792Sgshapirostatic char *
14290792Sgshapirostputs_quotes(const char *data, const char *syntax, char *p)
14390792Sgshapiro{
14494334Sgshapiro	while (*data) {
14590792Sgshapiro		CHECKSTRSPACE(2, p);
14690792Sgshapiro		if (syntax[(int)*data] == CCTL)
14790792Sgshapiro			USTPUTC(CTLESC, p);
14894334Sgshapiro		USTPUTC(*data++, p);
14994334Sgshapiro	}
15094334Sgshapiro	return (p);
15194334Sgshapiro}
15294334Sgshapiro#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p)
15394334Sgshapiro
15490792Sgshapiro/*
15590792Sgshapiro * Perform expansions on an argument, placing the resulting list of arguments
15690792Sgshapiro * in arglist.  Parameter expansion, command substitution and arithmetic
15790792Sgshapiro * expansion are always performed; additional expansions can be requested
15890792Sgshapiro * via flag (EXP_*).
15990792Sgshapiro * The result is left in the stack string.
16090792Sgshapiro * When arglist is NULL, perform here document expansion.
16190792Sgshapiro *
16290792Sgshapiro * Caution: this function uses global state and is not reentrant.
16394334Sgshapiro * However, a new invocation after an interrupted invocation is safe
16490792Sgshapiro * and will reset the global state for the new call.
16590792Sgshapiro */
16690792Sgshapirovoid
16790792Sgshapiroexpandarg(union node *arg, struct arglist *arglist, int flag)
16890792Sgshapiro{
16990792Sgshapiro	struct strlist *sp;
17090792Sgshapiro	char *p;
17190792Sgshapiro
17290792Sgshapiro	argbackq = arg->narg.backquote;
17390792Sgshapiro	STARTSTACKSTR(expdest);
17490792Sgshapiro	ifsfirst.next = NULL;
17590792Sgshapiro	ifslastp = NULL;
17690792Sgshapiro	argstr(arg->narg.text, flag);
17790792Sgshapiro	if (arglist == NULL) {
17890792Sgshapiro		STACKSTRNUL(expdest);
17990792Sgshapiro		return;			/* here document expanded */
18090792Sgshapiro	}
18190792Sgshapiro	STPUTC('\0', expdest);
18290792Sgshapiro	p = grabstackstr(expdest);
18390792Sgshapiro	exparg.lastp = &exparg.list;
18490792Sgshapiro	/*
18590792Sgshapiro	 * TODO - EXP_REDIR
18690792Sgshapiro	 */
18790792Sgshapiro	if (flag & EXP_FULL) {
18890792Sgshapiro		ifsbreakup(p, &exparg);
18990792Sgshapiro		*exparg.lastp = NULL;
19090792Sgshapiro		exparg.lastp = &exparg.list;
19190792Sgshapiro		expandmeta(exparg.list, flag);
19290792Sgshapiro	} else {
19390792Sgshapiro		if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
19490792Sgshapiro			rmescapes(p);
19590792Sgshapiro		sp = (struct strlist *)stalloc(sizeof (struct strlist));
19690792Sgshapiro		sp->text = p;
19790792Sgshapiro		*exparg.lastp = sp;
19890792Sgshapiro		exparg.lastp = &sp->next;
19990792Sgshapiro	}
20090792Sgshapiro	while (ifsfirst.next != NULL) {
20190792Sgshapiro		struct ifsregion *ifsp;
20290792Sgshapiro		INTOFF;
20390792Sgshapiro		ifsp = ifsfirst.next->next;
20490792Sgshapiro		ckfree(ifsfirst.next);
20590792Sgshapiro		ifsfirst.next = ifsp;
20690792Sgshapiro		INTON;
20790792Sgshapiro	}
20890792Sgshapiro	*exparg.lastp = NULL;
20990792Sgshapiro	if (exparg.list) {
21090792Sgshapiro		*arglist->lastp = exparg.list;
21190792Sgshapiro		arglist->lastp = exparg.lastp;
21290792Sgshapiro	}
21390792Sgshapiro}
21490792Sgshapiro
21590792Sgshapiro
21690792Sgshapiro
21790792Sgshapiro/*
21890792Sgshapiro * Perform parameter expansion, command substitution and arithmetic
21990792Sgshapiro * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE.
22090792Sgshapiro * Processing ends at a CTLENDVAR character as well as '\0'.
22190792Sgshapiro * This is used to expand word in ${var+word} etc.
22290792Sgshapiro * If EXP_FULL, EXP_CASE or EXP_REDIR are set, keep and/or generate CTLESC
22390792Sgshapiro * characters to allow for further processing.
22490792Sgshapiro * If EXP_FULL is set, also preserve CTLQUOTEMARK characters.
22590792Sgshapiro */
22690792Sgshapirostatic void
22790792Sgshapiroargstr(char *p, int flag)
22890792Sgshapiro{
22990792Sgshapiro	char c;
23090792Sgshapiro	int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR);	/* do CTLESC */
23190792Sgshapiro	int firsteq = 1;
23290792Sgshapiro	int split_lit;
23390792Sgshapiro	int lit_quoted;
23490792Sgshapiro
23590792Sgshapiro	split_lit = flag & EXP_SPLIT_LIT;
23690792Sgshapiro	lit_quoted = flag & EXP_LIT_QUOTED;
23790792Sgshapiro	flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED);
23890792Sgshapiro	if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
23990792Sgshapiro		p = exptilde(p, flag);
24090792Sgshapiro	for (;;) {
24190792Sgshapiro		CHECKSTRSPACE(2, expdest);
24290792Sgshapiro		switch (c = *p++) {
24390792Sgshapiro		case '\0':
24490792Sgshapiro		case CTLENDVAR:
24590792Sgshapiro			goto breakloop;
24690792Sgshapiro		case CTLQUOTEMARK:
24790792Sgshapiro			lit_quoted = 1;
24890792Sgshapiro			/* "$@" syntax adherence hack */
24990792Sgshapiro			if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
25090792Sgshapiro				break;
25190792Sgshapiro			if ((flag & EXP_FULL) != 0)
25290792Sgshapiro				USTPUTC(c, expdest);
25390792Sgshapiro			break;
25490792Sgshapiro		case CTLQUOTEEND:
25590792Sgshapiro			lit_quoted = 0;
25690792Sgshapiro			break;
25790792Sgshapiro		case CTLESC:
25890792Sgshapiro			if (quotes)
25990792Sgshapiro				USTPUTC(c, expdest);
26090792Sgshapiro			c = *p++;
26190792Sgshapiro			USTPUTC(c, expdest);
26290792Sgshapiro			if (split_lit && !lit_quoted)
26390792Sgshapiro				recordregion(expdest - stackblock() -
26490792Sgshapiro				    (quotes ? 2 : 1),
26590792Sgshapiro				    expdest - stackblock(), 0);
26690792Sgshapiro			break;
26790792Sgshapiro		case CTLVAR:
26890792Sgshapiro			p = evalvar(p, flag);
26990792Sgshapiro			break;
27090792Sgshapiro		case CTLBACKQ:
27190792Sgshapiro		case CTLBACKQ|CTLQUOTE:
27290792Sgshapiro			expbackq(argbackq->n, c & CTLQUOTE, flag);
27390792Sgshapiro			argbackq = argbackq->next;
27490792Sgshapiro			break;
27590792Sgshapiro		case CTLENDARI:
27690792Sgshapiro			expari(flag);
27790792Sgshapiro			break;
27890792Sgshapiro		case ':':
27990792Sgshapiro		case '=':
28090792Sgshapiro			/*
28190792Sgshapiro			 * sort of a hack - expand tildes in variable
28290792Sgshapiro			 * assignments (after the first '=' and after ':'s).
28390792Sgshapiro			 */
28490792Sgshapiro			USTPUTC(c, expdest);
28590792Sgshapiro			if (split_lit && !lit_quoted)
28690792Sgshapiro				recordregion(expdest - stackblock() - 1,
28790792Sgshapiro				    expdest - stackblock(), 0);
28890792Sgshapiro			if (flag & EXP_VARTILDE && *p == '~' &&
28990792Sgshapiro			    (c != '=' || firsteq)) {
29090792Sgshapiro				if (c == '=')
29190792Sgshapiro					firsteq = 0;
29290792Sgshapiro				p = exptilde(p, flag);
29390792Sgshapiro			}
29490792Sgshapiro			break;
29590792Sgshapiro		default:
29690792Sgshapiro			USTPUTC(c, expdest);
29790792Sgshapiro			if (split_lit && !lit_quoted)
29890792Sgshapiro				recordregion(expdest - stackblock() - 1,
29990792Sgshapiro				    expdest - stackblock(), 0);
30090792Sgshapiro		}
30190792Sgshapiro	}
30290792Sgshapirobreakloop:;
30390792Sgshapiro}
30490792Sgshapiro
30590792Sgshapiro/*
30690792Sgshapiro * Perform tilde expansion, placing the result in the stack string and
30790792Sgshapiro * returning the next position in the input string to process.
30890792Sgshapiro */
30990792Sgshapirostatic char *
31090792Sgshapiroexptilde(char *p, int flag)
31190792Sgshapiro{
31290792Sgshapiro	char c, *startp = p;
31390792Sgshapiro	struct passwd *pw;
31494334Sgshapiro	char *home;
31594334Sgshapiro	int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR);
31690792Sgshapiro
31790792Sgshapiro	while ((c = *p) != '\0') {
31890792Sgshapiro		switch(c) {
31990792Sgshapiro		case CTLESC: /* This means CTL* are always considered quoted. */
32090792Sgshapiro		case CTLVAR:
32190792Sgshapiro		case CTLBACKQ:
32290792Sgshapiro		case CTLBACKQ | CTLQUOTE:
32390792Sgshapiro		case CTLARI:
32494334Sgshapiro		case CTLENDARI:
32594334Sgshapiro		case CTLQUOTEMARK:
32694334Sgshapiro			return (startp);
32794334Sgshapiro		case ':':
32894334Sgshapiro			if (flag & EXP_VARTILDE)
32994334Sgshapiro				goto done;
33094334Sgshapiro			break;
33194334Sgshapiro		case '/':
33294334Sgshapiro		case CTLENDVAR:
33394334Sgshapiro			goto done;
33494334Sgshapiro		}
33594334Sgshapiro		p++;
33694334Sgshapiro	}
33794334Sgshapirodone:
33894334Sgshapiro	*p = '\0';
33994334Sgshapiro	if (*(startp+1) == '\0') {
34094334Sgshapiro		if ((home = lookupvar("HOME")) == NULL)
34194334Sgshapiro			goto lose;
34294334Sgshapiro	} else {
34394334Sgshapiro		if ((pw = getpwnam(startp+1)) == NULL)
34494334Sgshapiro			goto lose;
34594334Sgshapiro		home = pw->pw_dir;
34694334Sgshapiro	}
34794334Sgshapiro	if (*home == '\0')
34894334Sgshapiro		goto lose;
34994334Sgshapiro	*p = c;
35094334Sgshapiro	if (quotes)
35194334Sgshapiro		STPUTS_QUOTES(home, SQSYNTAX, expdest);
35294334Sgshapiro	else
35394334Sgshapiro		STPUTS(home, expdest);
35494334Sgshapiro	return (p);
35594334Sgshapirolose:
35694334Sgshapiro	*p = c;
35794334Sgshapiro	return (startp);
35894334Sgshapiro}
35994334Sgshapiro
36094334Sgshapiro
36194334Sgshapirostatic void
36294334Sgshapiroremoverecordregions(int endoff)
36394334Sgshapiro{
36494334Sgshapiro	if (ifslastp == NULL)
36594334Sgshapiro		return;
36694334Sgshapiro
36794334Sgshapiro	if (ifsfirst.endoff > endoff) {
36894334Sgshapiro		while (ifsfirst.next != NULL) {
36994334Sgshapiro			struct ifsregion *ifsp;
37094334Sgshapiro			INTOFF;
37194334Sgshapiro			ifsp = ifsfirst.next->next;
37294334Sgshapiro			ckfree(ifsfirst.next);
37394334Sgshapiro			ifsfirst.next = ifsp;
37494334Sgshapiro			INTON;
37594334Sgshapiro		}
37694334Sgshapiro		if (ifsfirst.begoff > endoff)
37794334Sgshapiro			ifslastp = NULL;
37894334Sgshapiro		else {
37994334Sgshapiro			ifslastp = &ifsfirst;
38094334Sgshapiro			ifsfirst.endoff = endoff;
38194334Sgshapiro		}
38294334Sgshapiro		return;
38394334Sgshapiro	}
38490792Sgshapiro
38590792Sgshapiro	ifslastp = &ifsfirst;
38690792Sgshapiro	while (ifslastp->next && ifslastp->next->begoff < endoff)
38790792Sgshapiro		ifslastp=ifslastp->next;
38890792Sgshapiro	while (ifslastp->next != NULL) {
38990792Sgshapiro		struct ifsregion *ifsp;
39090792Sgshapiro		INTOFF;
39190792Sgshapiro		ifsp = ifslastp->next->next;
39290792Sgshapiro		ckfree(ifslastp->next);
39390792Sgshapiro		ifslastp->next = ifsp;
39490792Sgshapiro		INTON;
39590792Sgshapiro	}
39690792Sgshapiro	if (ifslastp->endoff > endoff)
39790792Sgshapiro		ifslastp->endoff = endoff;
39890792Sgshapiro}
39994334Sgshapiro
40090792Sgshapiro/*
40190792Sgshapiro * Expand arithmetic expression.  Backup to start of expression,
40290792Sgshapiro * evaluate, place result in (backed up) result, adjust string position.
40390792Sgshapiro */
40490792Sgshapirovoid
40590792Sgshapiroexpari(int flag)
40690792Sgshapiro{
40790792Sgshapiro	char *p, *q, *start;
40890792Sgshapiro	arith_t result;
40994334Sgshapiro	int begoff;
41094334Sgshapiro	int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR);
41190792Sgshapiro	int quoted;
41290792Sgshapiro
41390792Sgshapiro	/*
41490792Sgshapiro	 * This routine is slightly over-complicated for
41590792Sgshapiro	 * efficiency.  First we make sure there is
41694334Sgshapiro	 * enough space for the result, which may be bigger
41794334Sgshapiro	 * than the expression.  Next we
41894334Sgshapiro	 * scan backwards looking for the start of arithmetic.  If the
41994334Sgshapiro	 * next previous character is a CTLESC character, then we
42094334Sgshapiro	 * have to rescan starting from the beginning since CTLESC
42194334Sgshapiro	 * characters have to be processed left to right.
42294334Sgshapiro	 */
42394334Sgshapiro	CHECKSTRSPACE(DIGITS(result) - 2, expdest);
42494334Sgshapiro	USTPUTC('\0', expdest);
42590792Sgshapiro	start = stackblock();
42694334Sgshapiro	p = expdest - 2;
42794334Sgshapiro	while (p >= start && *p != CTLARI)
42894334Sgshapiro		--p;
42994334Sgshapiro	if (p < start || *p != CTLARI)
43094334Sgshapiro		error("missing CTLARI (shouldn't happen)");
43194334Sgshapiro	if (p > start && *(p - 1) == CTLESC)
43294334Sgshapiro		for (p = start; *p != CTLARI; p++)
43390792Sgshapiro			if (*p == CTLESC)
43494334Sgshapiro				p++;
43594334Sgshapiro
43694334Sgshapiro	if (p[1] == '"')
43794334Sgshapiro		quoted=1;
43894334Sgshapiro	else
43994334Sgshapiro		quoted=0;
44094334Sgshapiro	begoff = p - start;
44194334Sgshapiro	removerecordregions(begoff);
44294334Sgshapiro	if (quotes)
44394334Sgshapiro		rmescapes(p+2);
44494334Sgshapiro	q = grabstackstr(expdest);
44594334Sgshapiro	result = arith(p+2);
44690792Sgshapiro	ungrabstackstr(q, expdest);
44794334Sgshapiro	fmtstr(p, DIGITS(result), ARITH_FORMAT_STR, result);
44894334Sgshapiro	while (*p++)
44990792Sgshapiro		;
45094334Sgshapiro	if (quoted == 0)
45194334Sgshapiro		recordregion(begoff, p - 1 - start, 0);
45294334Sgshapiro	result = expdest - p + 1;
45394334Sgshapiro	STADJUST(-result, expdest);
45494334Sgshapiro}
45594334Sgshapiro
45694334Sgshapiro
45794334Sgshapiro/*
45894334Sgshapiro * Perform command substitution.
45990792Sgshapiro */
46090792Sgshapirostatic void
46194334Sgshapiroexpbackq(union node *cmd, int quoted, int flag)
46294334Sgshapiro{
46394334Sgshapiro	struct backcmd in;
46494334Sgshapiro	int i;
46594334Sgshapiro	char buf[128];
46694334Sgshapiro	char *p;
46794334Sgshapiro	char *dest = expdest;
46894334Sgshapiro	struct ifsregion saveifs, *savelastp;
46994334Sgshapiro	struct nodelist *saveargbackq;
47090792Sgshapiro	char lastc;
47194334Sgshapiro	int startloc = dest - stackblock();
47294334Sgshapiro	char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
47394334Sgshapiro	int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR);
47494334Sgshapiro	int nnl;
47594334Sgshapiro
47694334Sgshapiro	INTOFF;
47794334Sgshapiro	saveifs = ifsfirst;
47894334Sgshapiro	savelastp = ifslastp;
47994334Sgshapiro	saveargbackq = argbackq;
48094334Sgshapiro	p = grabstackstr(dest);
48194334Sgshapiro	evalbackcmd(cmd, &in);
48294334Sgshapiro	ungrabstackstr(p, dest);
48394334Sgshapiro	ifsfirst = saveifs;
48494334Sgshapiro	ifslastp = savelastp;
48594334Sgshapiro	argbackq = saveargbackq;
48694334Sgshapiro
48794334Sgshapiro	p = in.buf;
48894334Sgshapiro	lastc = '\0';
48994334Sgshapiro	nnl = 0;
49094334Sgshapiro	/* Don't copy trailing newlines */
49194334Sgshapiro	for (;;) {
49294334Sgshapiro		if (--in.nleft < 0) {
49394334Sgshapiro			if (in.fd < 0)
49494334Sgshapiro				break;
49594334Sgshapiro			while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
49694334Sgshapiro			TRACE(("expbackq: read returns %d\n", i));
49794334Sgshapiro			if (i <= 0)
49894334Sgshapiro				break;
49994334Sgshapiro			p = buf;
50094334Sgshapiro			in.nleft = i - 1;
50194334Sgshapiro		}
50294334Sgshapiro		lastc = *p++;
50394334Sgshapiro		if (lastc != '\0') {
50494334Sgshapiro			if (lastc == '\n') {
50594334Sgshapiro				nnl++;
50694334Sgshapiro			} else {
50794334Sgshapiro				CHECKSTRSPACE(nnl + 2, dest);
50894334Sgshapiro				while (nnl > 0) {
50994334Sgshapiro					nnl--;
51094334Sgshapiro					USTPUTC('\n', dest);
51194334Sgshapiro				}
51294334Sgshapiro				if (quotes && syntax[(int)lastc] == CCTL)
51394334Sgshapiro					USTPUTC(CTLESC, dest);
51494334Sgshapiro				USTPUTC(lastc, dest);
51594334Sgshapiro			}
51694334Sgshapiro		}
51794334Sgshapiro	}
51890792Sgshapiro
51990792Sgshapiro	if (in.fd >= 0)
52090792Sgshapiro		close(in.fd);
52194334Sgshapiro	if (in.buf)
52294334Sgshapiro		ckfree(in.buf);
52390792Sgshapiro	if (in.jp)
52490792Sgshapiro		exitstatus = waitforjob(in.jp, (int *)NULL);
52590792Sgshapiro	if (quoted == 0)
52694334Sgshapiro		recordregion(startloc, dest - stackblock(), 0);
52790792Sgshapiro	TRACE(("expbackq: size=%td: \"%.*s\"\n",
52890792Sgshapiro		((dest - stackblock()) - startloc),
52994334Sgshapiro		(int)((dest - stackblock()) - startloc),
53094334Sgshapiro		stackblock() + startloc));
53190792Sgshapiro	expdest = dest;
53290792Sgshapiro	INTON;
53390792Sgshapiro}
53490792Sgshapiro
53590792Sgshapiro
53690792Sgshapiro
53790792Sgshapirostatic int
53890792Sgshapirosubevalvar(char *p, char *str, int strloc, int subtype, int startloc,
53990792Sgshapiro  int varflags, int quotes)
54094334Sgshapiro{
54190792Sgshapiro	char *startp;
54290792Sgshapiro	char *loc = NULL;
54390792Sgshapiro	char *q;
54490792Sgshapiro	int c = 0;
54590792Sgshapiro	struct nodelist *saveargbackq = argbackq;
54690792Sgshapiro	int amount;
54790792Sgshapiro
54890792Sgshapiro	argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
54990792Sgshapiro	    subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ?
55090792Sgshapiro	    EXP_CASE : 0) | EXP_TILDE);
55190792Sgshapiro	STACKSTRNUL(expdest);
55290792Sgshapiro	argbackq = saveargbackq;
55390792Sgshapiro	startp = stackblock() + startloc;
55490792Sgshapiro	if (str == NULL)
55594334Sgshapiro	    str = stackblock() + strloc;
55690792Sgshapiro
55790792Sgshapiro	switch (subtype) {
55890792Sgshapiro	case VSASSIGN:
55990792Sgshapiro		setvar(str, startp, 0);
56090792Sgshapiro		amount = startp - expdest;
56190792Sgshapiro		STADJUST(amount, expdest);
56290792Sgshapiro		varflags &= ~VSNUL;
56390792Sgshapiro		return 1;
56490792Sgshapiro
56590792Sgshapiro	case VSQUESTION:
56690792Sgshapiro		if (*p != CTLENDVAR) {
56790792Sgshapiro			outfmt(out2, "%s\n", startp);
56890792Sgshapiro			error((char *)NULL);
56990792Sgshapiro		}
57090792Sgshapiro		error("%.*s: parameter %snot set", (int)(p - str - 1),
57190792Sgshapiro		      str, (varflags & VSNUL) ? "null or "
57290792Sgshapiro					      : nullstr);
57390792Sgshapiro		return 0;
57490792Sgshapiro
57590792Sgshapiro	case VSTRIMLEFT:
57690792Sgshapiro		for (loc = startp; loc < str; loc++) {
57790792Sgshapiro			c = *loc;
57890792Sgshapiro			*loc = '\0';
57990792Sgshapiro			if (patmatch(str, startp, quotes)) {
58090792Sgshapiro				*loc = c;
58190792Sgshapiro				goto recordleft;
58290792Sgshapiro			}
58394334Sgshapiro			*loc = c;
58490792Sgshapiro			if (quotes && *loc == CTLESC)
58590792Sgshapiro				loc++;
58690792Sgshapiro		}
58790792Sgshapiro		return 0;
58894334Sgshapiro
58994334Sgshapiro	case VSTRIMLEFTMAX:
59094334Sgshapiro		for (loc = str - 1; loc >= startp;) {
59194334Sgshapiro			c = *loc;
59294334Sgshapiro			*loc = '\0';
59390792Sgshapiro			if (patmatch(str, startp, quotes)) {
59490792Sgshapiro				*loc = c;
59594334Sgshapiro				goto recordleft;
59690792Sgshapiro			}
59790792Sgshapiro			*loc = c;
59894334Sgshapiro			loc--;
59994334Sgshapiro			if (quotes && loc > startp && *(loc - 1) == CTLESC) {
60094334Sgshapiro				for (q = startp; q < loc; q++)
60190792Sgshapiro					if (*q == CTLESC)
60290792Sgshapiro						q++;
60390792Sgshapiro				if (q > loc)
60490792Sgshapiro					loc--;
60590792Sgshapiro			}
60690792Sgshapiro		}
60790792Sgshapiro		return 0;
60890792Sgshapiro
60990792Sgshapiro	case VSTRIMRIGHT:
61090792Sgshapiro		for (loc = str - 1; loc >= startp;) {
61190792Sgshapiro			if (patmatch(str, loc, quotes)) {
61290792Sgshapiro				amount = loc - expdest;
61390792Sgshapiro				STADJUST(amount, expdest);
61490792Sgshapiro				return 1;
61590792Sgshapiro			}
61690792Sgshapiro			loc--;
61790792Sgshapiro			if (quotes && loc > startp && *(loc - 1) == CTLESC) {
61890792Sgshapiro				for (q = startp; q < loc; q++)
61990792Sgshapiro					if (*q == CTLESC)
62090792Sgshapiro						q++;
62190792Sgshapiro				if (q > loc)
62290792Sgshapiro					loc--;
62390792Sgshapiro			}
62490792Sgshapiro		}
62594334Sgshapiro		return 0;
62690792Sgshapiro
62794334Sgshapiro	case VSTRIMRIGHTMAX:
62890792Sgshapiro		for (loc = startp; loc < str - 1; loc++) {
62990792Sgshapiro			if (patmatch(str, loc, quotes)) {
63090792Sgshapiro				amount = loc - expdest;
63190792Sgshapiro				STADJUST(amount, expdest);
63290792Sgshapiro				return 1;
63390792Sgshapiro			}
63494334Sgshapiro			if (quotes && *loc == CTLESC)
63590792Sgshapiro				loc++;
63690792Sgshapiro		}
63790792Sgshapiro		return 0;
63894334Sgshapiro
63994334Sgshapiro
64094334Sgshapiro	default:
64190792Sgshapiro		abort();
64294334Sgshapiro	}
64394334Sgshapiro
64494334Sgshapirorecordleft:
64594334Sgshapiro	amount = ((str - 1) - (loc - startp)) - expdest;
64694334Sgshapiro	STADJUST(amount, expdest);
64794334Sgshapiro	while (loc != str - 1)
64894334Sgshapiro		*startp++ = *loc++;
64990792Sgshapiro	return 1;
65090792Sgshapiro}
65194334Sgshapiro
65290792Sgshapiro
65390792Sgshapiro/*
65490792Sgshapiro * Expand a variable, and return a pointer to the next character in the
65590792Sgshapiro * input string.
65694334Sgshapiro */
65794334Sgshapiro
65894334Sgshapirostatic char *
65994334Sgshapiroevalvar(char *p, int flag)
66094334Sgshapiro{
66194334Sgshapiro	int subtype;
66294334Sgshapiro	int varflags;
66394334Sgshapiro	char *var;
66494334Sgshapiro	char *val;
66594334Sgshapiro	int patloc;
66694334Sgshapiro	int c;
66794334Sgshapiro	int set;
66894334Sgshapiro	int special;
66994334Sgshapiro	int startloc;
67094334Sgshapiro	int varlen;
67194334Sgshapiro	int varlenb;
67294334Sgshapiro	int easy;
67390792Sgshapiro	int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR);
67490792Sgshapiro
67590792Sgshapiro	varflags = (unsigned char)*p++;
67690792Sgshapiro	subtype = varflags & VSTYPE;
67790792Sgshapiro	var = p;
67890792Sgshapiro	special = 0;
67990792Sgshapiro	if (! is_name(*p))
68090792Sgshapiro		special = 1;
68190792Sgshapiro	p = strchr(p, '=') + 1;
68290792Sgshapiroagain: /* jump here after setting a variable with ${var=text} */
68390792Sgshapiro	if (varflags & VSLINENO) {
68490792Sgshapiro		set = 1;
68590792Sgshapiro		special = 0;
68690792Sgshapiro		val = var;
68790792Sgshapiro		p[-1] = '\0';	/* temporarily overwrite '=' to have \0
68890792Sgshapiro				   terminated string */
68990792Sgshapiro	} else if (special) {
69094334Sgshapiro		set = varisset(var, varflags & VSNUL);
69190792Sgshapiro		val = NULL;
69290792Sgshapiro	} else {
69390792Sgshapiro		val = bltinlookup(var, 1);
69490792Sgshapiro		if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
69590792Sgshapiro			val = NULL;
69690792Sgshapiro			set = 0;
69790792Sgshapiro		} else
69890792Sgshapiro			set = 1;
69990792Sgshapiro	}
70090792Sgshapiro	varlen = 0;
70190792Sgshapiro	startloc = expdest - stackblock();
70290792Sgshapiro	if (!set && uflag && *var != '@' && *var != '*') {
70390792Sgshapiro		switch (subtype) {
70490792Sgshapiro		case VSNORMAL:
70590792Sgshapiro		case VSTRIMLEFT:
70690792Sgshapiro		case VSTRIMLEFTMAX:
70790792Sgshapiro		case VSTRIMRIGHT:
70890792Sgshapiro		case VSTRIMRIGHTMAX:
70990792Sgshapiro		case VSLENGTH:
71090792Sgshapiro			error("%.*s: parameter not set", (int)(p - var - 1),
71190792Sgshapiro			    var);
71290792Sgshapiro		}
71390792Sgshapiro	}
71490792Sgshapiro	if (set && subtype != VSPLUS) {
71590792Sgshapiro		/* insert the value of the variable */
71690792Sgshapiro		if (special) {
71790792Sgshapiro			varvalue(var, varflags & VSQUOTE, subtype, flag);
71890792Sgshapiro			if (subtype == VSLENGTH) {
71990792Sgshapiro				varlenb = expdest - stackblock() - startloc;
72090792Sgshapiro				varlen = varlenb;
72190792Sgshapiro				if (localeisutf8) {
72290792Sgshapiro					val = stackblock() + startloc;
72390792Sgshapiro					for (;val != expdest; val++)
72490792Sgshapiro						if ((*val & 0xC0) == 0x80)
72590792Sgshapiro							varlen--;
72694334Sgshapiro				}
72790792Sgshapiro				STADJUST(-varlenb, expdest);
72894334Sgshapiro			}
72994334Sgshapiro		} else {
73094334Sgshapiro			char const *syntax = (varflags & VSQUOTE) ? DQSYNTAX
73194334Sgshapiro								  : BASESYNTAX;
73294334Sgshapiro
73394334Sgshapiro			if (subtype == VSLENGTH) {
73494334Sgshapiro				for (;*val; val++)
73594334Sgshapiro					if (!localeisutf8 ||
73694334Sgshapiro					    (*val & 0xC0) != 0x80)
73794334Sgshapiro						varlen++;
73894334Sgshapiro			}
73994334Sgshapiro			else {
74094334Sgshapiro				if (quotes)
74194334Sgshapiro					STPUTS_QUOTES(val, syntax, expdest);
74294334Sgshapiro				else
74394334Sgshapiro					STPUTS(val, expdest);
74490792Sgshapiro
74590792Sgshapiro			}
74690792Sgshapiro		}
74790792Sgshapiro	}
74890792Sgshapiro
74990792Sgshapiro	if (subtype == VSPLUS)
75090792Sgshapiro		set = ! set;
75190792Sgshapiro
75290792Sgshapiro	easy = ((varflags & VSQUOTE) == 0 ||
75390792Sgshapiro		(*var == '@' && shellparam.nparam != 1));
75490792Sgshapiro
75590792Sgshapiro
75690792Sgshapiro	switch (subtype) {
75790792Sgshapiro	case VSLENGTH:
75890792Sgshapiro		expdest = cvtnum(varlen, expdest);
75990792Sgshapiro		goto record;
76090792Sgshapiro
76190792Sgshapiro	case VSNORMAL:
76290792Sgshapiro		if (!easy)
76390792Sgshapiro			break;
76490792Sgshapirorecord:
76590792Sgshapiro		recordregion(startloc, expdest - stackblock(),
76690792Sgshapiro		    varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' &&
76790792Sgshapiro		    (*var == '@' || *var == '*')));
76890792Sgshapiro		break;
76990792Sgshapiro
77090792Sgshapiro	case VSPLUS:
77190792Sgshapiro	case VSMINUS:
77290792Sgshapiro		if (!set) {
77390792Sgshapiro			argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) |
77490792Sgshapiro			    (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0));
77590792Sgshapiro			break;
77690792Sgshapiro		}
77790792Sgshapiro		if (easy)
77890792Sgshapiro			goto record;
77990792Sgshapiro		break;
78090792Sgshapiro
78190792Sgshapiro	case VSTRIMLEFT:
78290792Sgshapiro	case VSTRIMLEFTMAX:
78390792Sgshapiro	case VSTRIMRIGHT:
78490792Sgshapiro	case VSTRIMRIGHTMAX:
78590792Sgshapiro		if (!set)
78694334Sgshapiro			break;
78794334Sgshapiro		/*
78894334Sgshapiro		 * Terminate the string and start recording the pattern
78994334Sgshapiro		 * right after it
79094334Sgshapiro		 */
79194334Sgshapiro		STPUTC('\0', expdest);
79294334Sgshapiro		patloc = expdest - stackblock();
79394334Sgshapiro		if (subevalvar(p, NULL, patloc, subtype,
79494334Sgshapiro		    startloc, varflags, quotes) == 0) {
79594334Sgshapiro			int amount = (expdest - stackblock() - patloc) + 1;
79690792Sgshapiro			STADJUST(-amount, expdest);
79790792Sgshapiro		}
79890792Sgshapiro		/* Remove any recorded regions beyond start of variable */
79990792Sgshapiro		removerecordregions(startloc);
80090792Sgshapiro		goto record;
80190792Sgshapiro
80294334Sgshapiro	case VSASSIGN:
80390792Sgshapiro	case VSQUESTION:
80490792Sgshapiro		if (!set) {
80590792Sgshapiro			if (subevalvar(p, var, 0, subtype, startloc, varflags,
80690792Sgshapiro			    quotes)) {
80790792Sgshapiro				varflags &= ~VSNUL;
80890792Sgshapiro				/*
80990792Sgshapiro				 * Remove any recorded regions beyond
81090792Sgshapiro				 * start of variable
81194334Sgshapiro				 */
81294334Sgshapiro				removerecordregions(startloc);
81394334Sgshapiro				goto again;
81494334Sgshapiro			}
81594334Sgshapiro			break;
81694334Sgshapiro		}
81790792Sgshapiro		if (easy)
81890792Sgshapiro			goto record;
81994334Sgshapiro		break;
82094334Sgshapiro
82194334Sgshapiro	case VSERROR:
82294334Sgshapiro		c = p - var - 1;
82394334Sgshapiro		error("${%.*s%s}: Bad substitution", c, var,
82494334Sgshapiro		    (c > 0 && *p != CTLENDVAR) ? "..." : "");
82594334Sgshapiro
82694334Sgshapiro	default:
82794334Sgshapiro		abort();
82894334Sgshapiro	}
82990792Sgshapiro	p[-1] = '=';	/* recover overwritten '=' */
83090792Sgshapiro
83190792Sgshapiro	if (subtype != VSNORMAL) {	/* skip to end of alternative */
83294334Sgshapiro		int nesting = 1;
83394334Sgshapiro		for (;;) {
83494334Sgshapiro			if ((c = *p++) == CTLESC)
83590792Sgshapiro				p++;
83694334Sgshapiro			else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
83794334Sgshapiro				if (set)
83890792Sgshapiro					argbackq = argbackq->next;
83994334Sgshapiro			} else if (c == CTLVAR) {
84094334Sgshapiro				if ((*p++ & VSTYPE) != VSNORMAL)
84190792Sgshapiro					nesting++;
84294334Sgshapiro			} else if (c == CTLENDVAR) {
84390792Sgshapiro				if (--nesting == 0)
84490792Sgshapiro					break;
84590792Sgshapiro			}
84690792Sgshapiro		}
84790792Sgshapiro	}
84894334Sgshapiro	return p;
84990792Sgshapiro}
85090792Sgshapiro
85190792Sgshapiro
85290792Sgshapiro
85390792Sgshapiro/*
85494334Sgshapiro * Test whether a specialized variable is set.
85594334Sgshapiro */
85694334Sgshapiro
85794334Sgshapirostatic int
85894334Sgshapirovarisset(char *name, int nulok)
85994334Sgshapiro{
86094334Sgshapiro
86194334Sgshapiro	if (*name == '!')
86290792Sgshapiro		return backgndpidset();
86394334Sgshapiro	else if (*name == '@' || *name == '*') {
86494334Sgshapiro		if (*shellparam.p == NULL)
86594334Sgshapiro			return 0;
86694334Sgshapiro
86790792Sgshapiro		if (nulok) {
86894334Sgshapiro			char **av;
86994334Sgshapiro
87094334Sgshapiro			for (av = shellparam.p; *av; av++)
87194334Sgshapiro				if (**av != '\0')
87294334Sgshapiro					return 1;
87394334Sgshapiro			return 0;
87494334Sgshapiro		}
87594334Sgshapiro	} else if (is_digit(*name)) {
87694334Sgshapiro		char *ap;
87794334Sgshapiro		int num = atoi(name);
87894334Sgshapiro
87994334Sgshapiro		if (num > shellparam.nparam)
88094334Sgshapiro			return 0;
88194334Sgshapiro
88294334Sgshapiro		if (num == 0)
88394334Sgshapiro			ap = arg0;
88490792Sgshapiro		else
88594334Sgshapiro			ap = shellparam.p[num - 1];
88694334Sgshapiro
88794334Sgshapiro		if (nulok && (ap == NULL || *ap == '\0'))
88894334Sgshapiro			return 0;
88994334Sgshapiro	}
89090792Sgshapiro	return 1;
89194334Sgshapiro}
89294334Sgshapiro
89394334Sgshapirostatic void
89494334Sgshapirostrtodest(const char *p, int flag, int subtype, int quoted)
89594334Sgshapiro{
89694334Sgshapiro	if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH)
89794334Sgshapiro		STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest);
89894334Sgshapiro	else
89994334Sgshapiro		STPUTS(p, expdest);
90094334Sgshapiro}
90194334Sgshapiro
90294334Sgshapiro/*
90394334Sgshapiro * Add the value of a specialized variable to the stack string.
90494334Sgshapiro */
90594334Sgshapiro
90694334Sgshapirostatic void
90794334Sgshapirovarvalue(char *name, int quoted, int subtype, int flag)
90894334Sgshapiro{
90994334Sgshapiro	int num;
91094334Sgshapiro	char *p;
91194334Sgshapiro	int i;
91290792Sgshapiro	char sep;
91390792Sgshapiro	char **ap;
91490792Sgshapiro
91590792Sgshapiro	switch (*name) {
91690792Sgshapiro	case '$':
91790792Sgshapiro		num = rootpid;
91890792Sgshapiro		goto numvar;
91990792Sgshapiro	case '?':
92090792Sgshapiro		num = oexitstatus;
92190792Sgshapiro		goto numvar;
92290792Sgshapiro	case '#':
92390792Sgshapiro		num = shellparam.nparam;
92490792Sgshapiro		goto numvar;
92590792Sgshapiro	case '!':
92690792Sgshapiro		num = backgndpidval();
92790792Sgshapironumvar:
92890792Sgshapiro		expdest = cvtnum(num, expdest);
92990792Sgshapiro		break;
93090792Sgshapiro	case '-':
93190792Sgshapiro		for (i = 0 ; i < NOPTS ; i++) {
93290792Sgshapiro			if (optlist[i].val)
93390792Sgshapiro				STPUTC(optlist[i].letter, expdest);
93494334Sgshapiro		}
93590792Sgshapiro		break;
93690792Sgshapiro	case '@':
93790792Sgshapiro		if (flag & EXP_FULL && quoted) {
93890792Sgshapiro			for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
93994334Sgshapiro				strtodest(p, flag, subtype, quoted);
94094334Sgshapiro				if (*ap)
94194334Sgshapiro					STPUTC('\0', expdest);
94290792Sgshapiro			}
94394334Sgshapiro			break;
94490792Sgshapiro		}
94590792Sgshapiro		/* FALLTHROUGH */
94690792Sgshapiro	case '*':
94790792Sgshapiro		if (ifsset())
94890792Sgshapiro			sep = ifsval()[0];
94990792Sgshapiro		else
95090792Sgshapiro			sep = ' ';
95190792Sgshapiro		for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
95294334Sgshapiro			strtodest(p, flag, subtype, quoted);
95390792Sgshapiro			if (!*ap)
95490792Sgshapiro				break;
95590792Sgshapiro			if (sep || (flag & EXP_FULL && !quoted && **ap != '\0'))
95690792Sgshapiro				STPUTC(sep, expdest);
95790792Sgshapiro		}
95890792Sgshapiro		break;
95990792Sgshapiro	case '0':
96090792Sgshapiro		p = arg0;
96190792Sgshapiro		strtodest(p, flag, subtype, quoted);
96290792Sgshapiro		break;
96390792Sgshapiro	default:
96490792Sgshapiro		if (is_digit(*name)) {
96590792Sgshapiro			num = atoi(name);
96690792Sgshapiro			if (num > 0 && num <= shellparam.nparam) {
96790792Sgshapiro				p = shellparam.p[num - 1];
96890792Sgshapiro				strtodest(p, flag, subtype, quoted);
96990792Sgshapiro			}
97090792Sgshapiro		}
97190792Sgshapiro		break;
97290792Sgshapiro	}
97390792Sgshapiro}
97490792Sgshapiro
97590792Sgshapiro
97694334Sgshapiro
97794334Sgshapiro/*
97894334Sgshapiro * Record the fact that we have to scan this region of the
97994334Sgshapiro * string for IFS characters.
98094334Sgshapiro */
98194334Sgshapiro
98290792Sgshapirostatic void
98390792Sgshapirorecordregion(int start, int end, int inquotes)
98490792Sgshapiro{
98590792Sgshapiro	struct ifsregion *ifsp;
98690792Sgshapiro
98794334Sgshapiro	if (ifslastp == NULL) {
98890792Sgshapiro		ifsp = &ifsfirst;
98990792Sgshapiro	} else {
99090792Sgshapiro		if (ifslastp->endoff == start
99190792Sgshapiro		    && ifslastp->inquotes == inquotes) {
99290792Sgshapiro			/* extend previous area */
99390792Sgshapiro			ifslastp->endoff = end;
99490792Sgshapiro			return;
99590792Sgshapiro		}
99690792Sgshapiro		ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
99790792Sgshapiro		ifslastp->next = ifsp;
99890792Sgshapiro	}
99990792Sgshapiro	ifslastp = ifsp;
100094334Sgshapiro	ifslastp->next = NULL;
100190792Sgshapiro	ifslastp->begoff = start;
100290792Sgshapiro	ifslastp->endoff = end;
100390792Sgshapiro	ifslastp->inquotes = inquotes;
100490792Sgshapiro}
100590792Sgshapiro
100690792Sgshapiro
100790792Sgshapiro
100890792Sgshapiro/*
100990792Sgshapiro * Break the argument string into pieces based upon IFS and add the
101090792Sgshapiro * strings to the argument list.  The regions of the string to be
101194334Sgshapiro * searched for IFS characters have been stored by recordregion.
101290792Sgshapiro * CTLESC characters are preserved but have little effect in this pass
101394334Sgshapiro * other than escaping CTL* characters.  In particular, they do not escape
101490792Sgshapiro * IFS characters: that should be done with the ifsregion mechanism.
101590792Sgshapiro * CTLQUOTEMARK characters are used to preserve empty quoted strings.
101690792Sgshapiro * This pass treats them as a regular character, making the string non-empty.
101794334Sgshapiro * Later, they are removed along with the other CTL* characters.
101894334Sgshapiro */
101994334Sgshapirostatic void
102094334Sgshapiroifsbreakup(char *string, struct arglist *arglist)
102190792Sgshapiro{
102290792Sgshapiro	struct ifsregion *ifsp;
102390792Sgshapiro	struct strlist *sp;
102490792Sgshapiro	char *start;
102594334Sgshapiro	char *p;
102694334Sgshapiro	char *q;
102790792Sgshapiro	const char *ifs;
102890792Sgshapiro	const char *ifsspc;
102990792Sgshapiro	int had_param_ch = 0;
103090792Sgshapiro
103190792Sgshapiro	start = string;
103290792Sgshapiro
103394334Sgshapiro	if (ifslastp == NULL) {
103494334Sgshapiro		/* Return entire argument, IFS doesn't apply to any of it */
103590792Sgshapiro		sp = (struct strlist *)stalloc(sizeof *sp);
103690792Sgshapiro		sp->text = start;
103794334Sgshapiro		*arglist->lastp = sp;
103890792Sgshapiro		arglist->lastp = &sp->next;
103990792Sgshapiro		return;
104090792Sgshapiro	}
104190792Sgshapiro
104290792Sgshapiro	ifs = ifsset() ? ifsval() : " \t\n";
104390792Sgshapiro
104494334Sgshapiro	for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) {
104594334Sgshapiro		p = string + ifsp->begoff;
104690792Sgshapiro		while (p < string + ifsp->endoff) {
104790792Sgshapiro			q = p;
104894334Sgshapiro			if (*p == CTLESC)
104990792Sgshapiro				p++;
105090792Sgshapiro			if (ifsp->inquotes) {
105190792Sgshapiro				/* Only NULs (should be from "$@") end args */
105290792Sgshapiro				had_param_ch = 1;
105390792Sgshapiro				if (*p != 0) {
105494334Sgshapiro					p++;
105590792Sgshapiro					continue;
105690792Sgshapiro				}
105790792Sgshapiro				ifsspc = NULL;
105890792Sgshapiro			} else {
105990792Sgshapiro				if (!strchr(ifs, *p)) {
106090792Sgshapiro					had_param_ch = 1;
106190792Sgshapiro					p++;
106290792Sgshapiro					continue;
106390792Sgshapiro				}
106490792Sgshapiro				ifsspc = strchr(" \t\n", *p);
106590792Sgshapiro
106690792Sgshapiro				/* Ignore IFS whitespace at start */
106790792Sgshapiro				if (q == start && ifsspc != NULL) {
106890792Sgshapiro					p++;
106990792Sgshapiro					start = p;
107090792Sgshapiro					continue;
107190792Sgshapiro				}
107290792Sgshapiro				had_param_ch = 0;
107390792Sgshapiro			}
107490792Sgshapiro
107594334Sgshapiro			/* Save this argument... */
107694334Sgshapiro			*q = '\0';
107794334Sgshapiro			sp = (struct strlist *)stalloc(sizeof *sp);
107894334Sgshapiro			sp->text = start;
107994334Sgshapiro			*arglist->lastp = sp;
108094334Sgshapiro			arglist->lastp = &sp->next;
108190792Sgshapiro			p++;
108290792Sgshapiro
108390792Sgshapiro			if (ifsspc != NULL) {
108490792Sgshapiro				/* Ignore further trailing IFS whitespace */
108590792Sgshapiro				for (; p < string + ifsp->endoff; p++) {
108690792Sgshapiro					q = p;
108790792Sgshapiro					if (*p == CTLESC)
108894334Sgshapiro						p++;
108994334Sgshapiro					if (strchr(ifs, *p) == NULL) {
109094334Sgshapiro						p = q;
109190792Sgshapiro						break;
109290792Sgshapiro					}
109390792Sgshapiro					if (strchr(" \t\n", *p) == NULL) {
109490792Sgshapiro						p++;
109590792Sgshapiro						break;
109690792Sgshapiro					}
109790792Sgshapiro				}
109890792Sgshapiro			}
109994334Sgshapiro			start = p;
110094334Sgshapiro		}
110194334Sgshapiro	}
110294334Sgshapiro
110390792Sgshapiro	/*
110490792Sgshapiro	 * Save anything left as an argument.
110590792Sgshapiro	 * Traditionally we have treated 'IFS=':'; set -- x$IFS' as
110690792Sgshapiro	 * generating 2 arguments, the second of which is empty.
110790792Sgshapiro	 * Some recent clarification of the Posix spec say that it
110890792Sgshapiro	 * should only generate one....
110990792Sgshapiro	 */
111090792Sgshapiro	if (had_param_ch || *start != 0) {
111190792Sgshapiro		sp = (struct strlist *)stalloc(sizeof *sp);
111290792Sgshapiro		sp->text = start;
111390792Sgshapiro		*arglist->lastp = sp;
111490792Sgshapiro		arglist->lastp = &sp->next;
111590792Sgshapiro	}
111690792Sgshapiro}
111790792Sgshapiro
111890792Sgshapiro
111990792Sgshapirostatic char expdir[PATH_MAX];
112090792Sgshapiro#define expdir_end (expdir + sizeof(expdir))
112190792Sgshapiro
112290792Sgshapiro/*
112390792Sgshapiro * Perform pathname generation and remove control characters.
112490792Sgshapiro * At this point, the only control characters should be CTLESC and CTLQUOTEMARK.
112590792Sgshapiro * The results are stored in the list exparg.
112690792Sgshapiro */
112790792Sgshapirostatic void
112890792Sgshapiroexpandmeta(struct strlist *str, int flag __unused)
112990792Sgshapiro{
113090792Sgshapiro	char *p;
113190792Sgshapiro	struct strlist **savelastp;
113290792Sgshapiro	struct strlist *sp;
113390792Sgshapiro	char c;
113490792Sgshapiro	/* TODO - EXP_REDIR */
113590792Sgshapiro
113690792Sgshapiro	while (str) {
113790792Sgshapiro		if (fflag)
113890792Sgshapiro			goto nometa;
113990792Sgshapiro		p = str->text;
114090792Sgshapiro		for (;;) {			/* fast check for meta chars */
114190792Sgshapiro			if ((c = *p++) == '\0')
114290792Sgshapiro				goto nometa;
114390792Sgshapiro			if (c == '*' || c == '?' || c == '[')
114490792Sgshapiro				break;
114590792Sgshapiro		}
114690792Sgshapiro		savelastp = exparg.lastp;
114790792Sgshapiro		INTOFF;
114890792Sgshapiro		expmeta(expdir, str->text);
114990792Sgshapiro		INTON;
115090792Sgshapiro		if (exparg.lastp == savelastp) {
115194334Sgshapiro			/*
115294334Sgshapiro			 * no matches
115394334Sgshapiro			 */
115494334Sgshapironometa:
115594334Sgshapiro			*exparg.lastp = str;
115694334Sgshapiro			rmescapes(str->text);
115794334Sgshapiro			exparg.lastp = &str->next;
115890792Sgshapiro		} else {
115990792Sgshapiro			*exparg.lastp = NULL;
116090792Sgshapiro			*savelastp = sp = expsort(*savelastp);
116190792Sgshapiro			while (sp->next != NULL)
116290792Sgshapiro				sp = sp->next;
116390792Sgshapiro			exparg.lastp = &sp->next;
116490792Sgshapiro		}
116590792Sgshapiro		str = str->next;
116690792Sgshapiro	}
116790792Sgshapiro}
116890792Sgshapiro
116990792Sgshapiro
117090792Sgshapiro/*
117190792Sgshapiro * Do metacharacter (i.e. *, ?, [...]) expansion.
117290792Sgshapiro */
117390792Sgshapiro
117490792Sgshapirostatic void
117590792Sgshapiroexpmeta(char *enddir, char *name)
117690792Sgshapiro{
117790792Sgshapiro	char *p;
117890792Sgshapiro	char *q;
117990792Sgshapiro	char *start;
118090792Sgshapiro	char *endname;
118190792Sgshapiro	int metaflag;
118290792Sgshapiro	struct stat statb;
118390792Sgshapiro	DIR *dirp;
118490792Sgshapiro	struct dirent *dp;
118590792Sgshapiro	int atend;
118690792Sgshapiro	int matchdot;
118790792Sgshapiro	int esc;
118890792Sgshapiro
118990792Sgshapiro	metaflag = 0;
119090792Sgshapiro	start = name;
119190792Sgshapiro	for (p = name; esc = 0, *p; p += esc + 1) {
119290792Sgshapiro		if (*p == '*' || *p == '?')
119390792Sgshapiro			metaflag = 1;
119490792Sgshapiro		else if (*p == '[') {
119590792Sgshapiro			q = p + 1;
119690792Sgshapiro			if (*q == '!' || *q == '^')
119790792Sgshapiro				q++;
119890792Sgshapiro			for (;;) {
119990792Sgshapiro				while (*q == CTLQUOTEMARK)
120090792Sgshapiro					q++;
120190792Sgshapiro				if (*q == CTLESC)
120290792Sgshapiro					q++;
120390792Sgshapiro				if (*q == '/' || *q == '\0')
120490792Sgshapiro					break;
120590792Sgshapiro				if (*++q == ']') {
120690792Sgshapiro					metaflag = 1;
120790792Sgshapiro					break;
120890792Sgshapiro				}
120990792Sgshapiro			}
1210		} else if (*p == '\0')
1211			break;
1212		else if (*p == CTLQUOTEMARK)
1213			continue;
1214		else {
1215			if (*p == CTLESC)
1216				esc++;
1217			if (p[esc] == '/') {
1218				if (metaflag)
1219					break;
1220				start = p + esc + 1;
1221			}
1222		}
1223	}
1224	if (metaflag == 0) {	/* we've reached the end of the file name */
1225		if (enddir != expdir)
1226			metaflag++;
1227		for (p = name ; ; p++) {
1228			if (*p == CTLQUOTEMARK)
1229				continue;
1230			if (*p == CTLESC)
1231				p++;
1232			*enddir++ = *p;
1233			if (*p == '\0')
1234				break;
1235			if (enddir == expdir_end)
1236				return;
1237		}
1238		if (metaflag == 0 || lstat(expdir, &statb) >= 0)
1239			addfname(expdir);
1240		return;
1241	}
1242	endname = p;
1243	if (start != name) {
1244		p = name;
1245		while (p < start) {
1246			while (*p == CTLQUOTEMARK)
1247				p++;
1248			if (*p == CTLESC)
1249				p++;
1250			*enddir++ = *p++;
1251			if (enddir == expdir_end)
1252				return;
1253		}
1254	}
1255	if (enddir == expdir) {
1256		p = ".";
1257	} else if (enddir == expdir + 1 && *expdir == '/') {
1258		p = "/";
1259	} else {
1260		p = expdir;
1261		enddir[-1] = '\0';
1262	}
1263	if ((dirp = opendir(p)) == NULL)
1264		return;
1265	if (enddir != expdir)
1266		enddir[-1] = '/';
1267	if (*endname == 0) {
1268		atend = 1;
1269	} else {
1270		atend = 0;
1271		*endname = '\0';
1272		endname += esc + 1;
1273	}
1274	matchdot = 0;
1275	p = start;
1276	while (*p == CTLQUOTEMARK)
1277		p++;
1278	if (*p == CTLESC)
1279		p++;
1280	if (*p == '.')
1281		matchdot++;
1282	while (! int_pending() && (dp = readdir(dirp)) != NULL) {
1283		if (dp->d_name[0] == '.' && ! matchdot)
1284			continue;
1285		if (patmatch(start, dp->d_name, 0)) {
1286			if (enddir + dp->d_namlen + 1 > expdir_end)
1287				continue;
1288			memcpy(enddir, dp->d_name, dp->d_namlen + 1);
1289			if (atend)
1290				addfname(expdir);
1291			else {
1292				if (enddir + dp->d_namlen + 2 > expdir_end)
1293					continue;
1294				enddir[dp->d_namlen] = '/';
1295				enddir[dp->d_namlen + 1] = '\0';
1296				expmeta(enddir + dp->d_namlen + 1, endname);
1297			}
1298		}
1299	}
1300	closedir(dirp);
1301	if (! atend)
1302		endname[-esc - 1] = esc ? CTLESC : '/';
1303}
1304
1305
1306/*
1307 * Add a file name to the list.
1308 */
1309
1310static void
1311addfname(char *name)
1312{
1313	char *p;
1314	struct strlist *sp;
1315
1316	p = stalloc(strlen(name) + 1);
1317	scopy(name, p);
1318	sp = (struct strlist *)stalloc(sizeof *sp);
1319	sp->text = p;
1320	*exparg.lastp = sp;
1321	exparg.lastp = &sp->next;
1322}
1323
1324
1325/*
1326 * Sort the results of file name expansion.  It calculates the number of
1327 * strings to sort and then calls msort (short for merge sort) to do the
1328 * work.
1329 */
1330
1331static struct strlist *
1332expsort(struct strlist *str)
1333{
1334	int len;
1335	struct strlist *sp;
1336
1337	len = 0;
1338	for (sp = str ; sp ; sp = sp->next)
1339		len++;
1340	return msort(str, len);
1341}
1342
1343
1344static struct strlist *
1345msort(struct strlist *list, int len)
1346{
1347	struct strlist *p, *q = NULL;
1348	struct strlist **lpp;
1349	int half;
1350	int n;
1351
1352	if (len <= 1)
1353		return list;
1354	half = len >> 1;
1355	p = list;
1356	for (n = half ; --n >= 0 ; ) {
1357		q = p;
1358		p = p->next;
1359	}
1360	q->next = NULL;			/* terminate first half of list */
1361	q = msort(list, half);		/* sort first half of list */
1362	p = msort(p, len - half);		/* sort second half */
1363	lpp = &list;
1364	for (;;) {
1365		if (strcmp(p->text, q->text) < 0) {
1366			*lpp = p;
1367			lpp = &p->next;
1368			if ((p = *lpp) == NULL) {
1369				*lpp = q;
1370				break;
1371			}
1372		} else {
1373			*lpp = q;
1374			lpp = &q->next;
1375			if ((q = *lpp) == NULL) {
1376				*lpp = p;
1377				break;
1378			}
1379		}
1380	}
1381	return list;
1382}
1383
1384
1385
1386static wchar_t
1387get_wc(const char **p)
1388{
1389	wchar_t c;
1390	int chrlen;
1391
1392	chrlen = mbtowc(&c, *p, 4);
1393	if (chrlen == 0)
1394		return 0;
1395	else if (chrlen == -1)
1396		c = 0;
1397	else
1398		*p += chrlen;
1399	return c;
1400}
1401
1402
1403/*
1404 * Returns true if the pattern matches the string.
1405 */
1406
1407int
1408patmatch(const char *pattern, const char *string, int squoted)
1409{
1410	const char *p, *q;
1411	char c;
1412	wchar_t wc, wc2;
1413
1414	p = pattern;
1415	q = string;
1416	for (;;) {
1417		switch (c = *p++) {
1418		case '\0':
1419			goto breakloop;
1420		case CTLESC:
1421			if (squoted && *q == CTLESC)
1422				q++;
1423			if (*q++ != *p++)
1424				return 0;
1425			break;
1426		case CTLQUOTEMARK:
1427			continue;
1428		case '?':
1429			if (squoted && *q == CTLESC)
1430				q++;
1431			if (localeisutf8)
1432				wc = get_wc(&q);
1433			else
1434				wc = (unsigned char)*q++;
1435			if (wc == '\0')
1436				return 0;
1437			break;
1438		case '*':
1439			c = *p;
1440			while (c == CTLQUOTEMARK || c == '*')
1441				c = *++p;
1442			if (c != CTLESC &&  c != CTLQUOTEMARK &&
1443			    c != '?' && c != '*' && c != '[') {
1444				while (*q != c) {
1445					if (squoted && *q == CTLESC &&
1446					    q[1] == c)
1447						break;
1448					if (*q == '\0')
1449						return 0;
1450					if (squoted && *q == CTLESC)
1451						q++;
1452					q++;
1453				}
1454			}
1455			do {
1456				if (patmatch(p, q, squoted))
1457					return 1;
1458				if (squoted && *q == CTLESC)
1459					q++;
1460			} while (*q++ != '\0');
1461			return 0;
1462		case '[': {
1463			const char *endp;
1464			int invert, found;
1465			wchar_t chr;
1466
1467			endp = p;
1468			if (*endp == '!' || *endp == '^')
1469				endp++;
1470			for (;;) {
1471				while (*endp == CTLQUOTEMARK)
1472					endp++;
1473				if (*endp == '\0')
1474					goto dft;		/* no matching ] */
1475				if (*endp == CTLESC)
1476					endp++;
1477				if (*++endp == ']')
1478					break;
1479			}
1480			invert = 0;
1481			if (*p == '!' || *p == '^') {
1482				invert++;
1483				p++;
1484			}
1485			found = 0;
1486			if (squoted && *q == CTLESC)
1487				q++;
1488			if (localeisutf8)
1489				chr = get_wc(&q);
1490			else
1491				chr = (unsigned char)*q++;
1492			if (chr == '\0')
1493				return 0;
1494			c = *p++;
1495			do {
1496				if (c == CTLQUOTEMARK)
1497					continue;
1498				if (c == CTLESC)
1499					c = *p++;
1500				if (localeisutf8 && c & 0x80) {
1501					p--;
1502					wc = get_wc(&p);
1503					if (wc == 0) /* bad utf-8 */
1504						return 0;
1505				} else
1506					wc = (unsigned char)c;
1507				if (*p == '-' && p[1] != ']') {
1508					p++;
1509					while (*p == CTLQUOTEMARK)
1510						p++;
1511					if (*p == CTLESC)
1512						p++;
1513					if (localeisutf8) {
1514						wc2 = get_wc(&p);
1515						if (wc2 == 0) /* bad utf-8 */
1516							return 0;
1517					} else
1518						wc2 = (unsigned char)*p++;
1519					if (   collate_range_cmp(chr, wc) >= 0
1520					    && collate_range_cmp(chr, wc2) <= 0
1521					   )
1522						found = 1;
1523				} else {
1524					if (chr == wc)
1525						found = 1;
1526				}
1527			} while ((c = *p++) != ']');
1528			if (found == invert)
1529				return 0;
1530			break;
1531		}
1532dft:	        default:
1533			if (squoted && *q == CTLESC)
1534				q++;
1535			if (*q++ != c)
1536				return 0;
1537			break;
1538		}
1539	}
1540breakloop:
1541	if (*q != '\0')
1542		return 0;
1543	return 1;
1544}
1545
1546
1547
1548/*
1549 * Remove any CTLESC and CTLQUOTEMARK characters from a string.
1550 */
1551
1552void
1553rmescapes(char *str)
1554{
1555	char *p, *q;
1556
1557	p = str;
1558	while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) {
1559		if (*p++ == '\0')
1560			return;
1561	}
1562	q = p;
1563	while (*p) {
1564		if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) {
1565			p++;
1566			continue;
1567		}
1568		if (*p == CTLESC)
1569			p++;
1570		*q++ = *p++;
1571	}
1572	*q = '\0';
1573}
1574
1575
1576
1577/*
1578 * See if a pattern matches in a case statement.
1579 */
1580
1581int
1582casematch(union node *pattern, const char *val)
1583{
1584	struct stackmark smark;
1585	int result;
1586	char *p;
1587
1588	setstackmark(&smark);
1589	argbackq = pattern->narg.backquote;
1590	STARTSTACKSTR(expdest);
1591	ifslastp = NULL;
1592	argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
1593	STPUTC('\0', expdest);
1594	p = grabstackstr(expdest);
1595	result = patmatch(p, val, 0);
1596	popstackmark(&smark);
1597	return result;
1598}
1599
1600/*
1601 * Our own itoa().
1602 */
1603
1604static char *
1605cvtnum(int num, char *buf)
1606{
1607	char temp[32];
1608	int neg = num < 0;
1609	char *p = temp + 31;
1610
1611	temp[31] = '\0';
1612
1613	do {
1614		*--p = num % 10 + '0';
1615	} while ((num /= 10) != 0);
1616
1617	if (neg)
1618		*--p = '-';
1619
1620	STPUTS(p, buf);
1621	return buf;
1622}
1623
1624/*
1625 * Do most of the work for wordexp(3).
1626 */
1627
1628int
1629wordexpcmd(int argc, char **argv)
1630{
1631	size_t len;
1632	int i;
1633
1634	out1fmt("%08x", argc - 1);
1635	for (i = 1, len = 0; i < argc; i++)
1636		len += strlen(argv[i]);
1637	out1fmt("%08x", (int)len);
1638	for (i = 1; i < argc; i++)
1639		outbin(argv[i], strlen(argv[i]) + 1, out1);
1640        return (0);
1641}
1642