Home | History | Annotate | Line # | Download | only in rdist
expand.c revision 1.16
      1 /*	$NetBSD: expand.c,v 1.16 2003/08/07 11:15:35 agc Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1983, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #ifndef lint
     34 #if 0
     35 static char sccsid[] = "@(#)expand.c	8.1 (Berkeley) 6/9/93";
     36 #else
     37 __RCSID("$NetBSD: expand.c,v 1.16 2003/08/07 11:15:35 agc Exp $");
     38 #endif
     39 #endif /* not lint */
     40 
     41 #include <sys/types.h>
     42 
     43 #include <errno.h>
     44 #include <pwd.h>
     45 
     46 #include "defs.h"
     47 
     48 #define	GAVSIZ	NCARGS / 6
     49 #define LC '{'
     50 #define RC '}'
     51 
     52 static char	shchars[] = "${[*?";
     53 
     54 int	which;		/* bit mask of types to expand */
     55 int	eargc;		/* expanded arg count */
     56 char	**eargv;	/* expanded arg vectors */
     57 char	*path;
     58 char	*pathp;
     59 char	*lastpathp;
     60 char	*tilde;		/* "~user" if not expanding tilde, else "" */
     61 char	*tpathp;
     62 int	nleft;
     63 
     64 int	expany;		/* any expansions done? */
     65 char	*entp;
     66 char	**sortbase;
     67 
     68 #define sort()	qsort((char *)sortbase, &eargv[eargc] - sortbase, \
     69 		      sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
     70 
     71 static void	Cat(char *, char *);
     72 static void	addpath(int);
     73 static int	amatch(char *, char *);
     74 static int	argcmp(const void *, const void *);
     75 static int	execbrc(char *, char *);
     76 static void	expsh(char *);
     77 static void	expstr(char *);
     78 static int	match(char *, char *);
     79 static void	matchdir(char *);
     80 static int	smatch(char *, char *);
     81 
     82 /*
     83  * Take a list of names and expand any macros, etc.
     84  * wh = E_VARS if expanding variables.
     85  * wh = E_SHELL if expanding shell characters.
     86  * wh = E_TILDE if expanding `~'.
     87  * or any of these or'ed together.
     88  *
     89  * Major portions of this were snarfed from csh/sh.glob.c.
     90  */
     91 struct namelist *
     92 expand(struct namelist *list, int wh)
     93 {
     94 	struct namelist *nl, *prev;
     95 	int n;
     96 	char pathbuf[BUFSIZ];
     97 	char *argvbuf[GAVSIZ];
     98 
     99 	if (debug) {
    100 		printf("expand(%lx, %d)\nlist = ", (long)list, wh);
    101 		prnames(list);
    102 	}
    103 
    104 	if (wh == 0) {
    105 		char *cp;
    106 
    107 		for (nl = list; nl != NULL; nl = nl->n_next)
    108 			for (cp = nl->n_name; *cp; cp++)
    109 				*cp = *cp & TRIM;
    110 		return(list);
    111 	}
    112 
    113 	which = wh;
    114 	path = tpathp = pathp = pathbuf;
    115 	*pathp = '\0';
    116 	lastpathp = &path[sizeof pathbuf - 2];
    117 	tilde = "";
    118 	eargc = 0;
    119 	eargv = sortbase = argvbuf;
    120 	*eargv = 0;
    121 	nleft = NCARGS - 4;
    122 	/*
    123 	 * Walk the name list and expand names into eargv[];
    124 	 */
    125 	for (nl = list; nl != NULL; nl = nl->n_next)
    126 		expstr(nl->n_name);
    127 	/*
    128 	 * Take expanded list of names from eargv[] and build a new list.
    129 	 */
    130 	list = prev = NULL;
    131 	for (n = 0; n < eargc; n++) {
    132 		nl = makenl(NULL);
    133 		nl->n_name = eargv[n];
    134 		if (prev == NULL)
    135 			list = prev = nl;
    136 		else {
    137 			prev->n_next = nl;
    138 			prev = nl;
    139 		}
    140 	}
    141 	if (debug) {
    142 		printf("expanded list = ");
    143 		prnames(list);
    144 	}
    145 	return(list);
    146 }
    147 
    148 static void
    149 expstr(char *s)
    150 {
    151 	char *cp, *cp1;
    152 	struct namelist *tp;
    153 	char *tail;
    154 	char buf[BUFSIZ];
    155 	int savec, oeargc;
    156 	extern char homedir[];
    157 
    158 	if (s == NULL || *s == '\0')
    159 		return;
    160 
    161 	if ((which & E_VARS) && (cp = strchr(s, '$')) != NULL) {
    162 		*cp++ = '\0';
    163 		if (*cp == '\0') {
    164 			yyerror("no variable name after '$'");
    165 			return;
    166 		}
    167 		if (*cp == LC) {
    168 			cp++;
    169 			if ((tail = strchr(cp, RC)) == NULL) {
    170 				yyerror("unmatched '{'");
    171 				return;
    172 			}
    173 			*tail++ = savec = '\0';
    174 			if (*cp == '\0') {
    175 				yyerror("no variable name after '$'");
    176 				return;
    177 			}
    178 		} else {
    179 			tail = cp + 1;
    180 			savec = *tail;
    181 			*tail = '\0';
    182 		}
    183 		tp = lookup(cp, 0, 0);
    184 		if (savec != '\0')
    185 			*tail = savec;
    186 		if (tp != NULL) {
    187 			for (; tp != NULL; tp = tp->n_next) {
    188 				snprintf(buf, sizeof(buf), "%s%s%s", s,
    189 				    tp->n_name, tail);
    190 				expstr(buf);
    191 			}
    192 			return;
    193 		}
    194 		snprintf(buf, sizeof(buf), "%s%s", s, tail);
    195 		expstr(buf);
    196 		return;
    197 	}
    198 	if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) {
    199 		Cat(s, "");
    200 		sort();
    201 		return;
    202 	}
    203 	if (*s == '~') {
    204 		cp = ++s;
    205 		if (*cp == '\0' || *cp == '/') {
    206 			tilde = "~";
    207 			cp1 = homedir;
    208 		} else {
    209 			tilde = cp1 = buf;
    210 			*cp1++ = '~';
    211 			do
    212 				*cp1++ = *cp++;
    213 			while (*cp && *cp != '/');
    214 			*cp1 = '\0';
    215 			if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) {
    216 				if ((pw = getpwnam(buf+1)) == NULL) {
    217 					strlcat(buf, ": unknown user name",
    218 					    sizeof(buf));
    219 					yyerror(buf+1);
    220 					return;
    221 				}
    222 			}
    223 			cp1 = pw->pw_dir;
    224 			s = cp;
    225 		}
    226 		for (cp = path; (*cp++ = *cp1++) != 0; )
    227 			;
    228 		tpathp = pathp = cp - 1;
    229 	} else {
    230 		tpathp = pathp = path;
    231 		tilde = "";
    232 	}
    233 	*pathp = '\0';
    234 	if (!(which & E_SHELL)) {
    235 		if (which & E_TILDE)
    236 			Cat(path, s);
    237 		else
    238 			Cat(tilde, s);
    239 		sort();
    240 		return;
    241 	}
    242 	oeargc = eargc;
    243 	expany = 0;
    244 	expsh(s);
    245 	if (eargc == oeargc)
    246 		Cat(s, "");		/* "nonomatch" is set */
    247 	sort();
    248 }
    249 
    250 static int
    251 argcmp(const void *a1, const void *a2)
    252 {
    253 
    254 	return (strcmp(*(char **)a1, *(char **)a2));
    255 }
    256 
    257 /*
    258  * If there are any Shell meta characters in the name,
    259  * expand into a list, after searching directory
    260  */
    261 static void
    262 expsh(char *s)
    263 {
    264 	char *cp;
    265 	char *spathp, *oldcp;
    266 	struct stat stb;
    267 
    268 	spathp = pathp;
    269 	cp = s;
    270 	while (!any(*cp, shchars)) {
    271 		if (*cp == '\0') {
    272 			if (!expany || stat(path, &stb) >= 0) {
    273 				if (which & E_TILDE)
    274 					Cat(path, "");
    275 				else
    276 					Cat(tilde, tpathp);
    277 			}
    278 			goto endit;
    279 		}
    280 		addpath(*cp++);
    281 	}
    282 	oldcp = cp;
    283 	while (cp > s && *cp != '/')
    284 		cp--, pathp--;
    285 	if (*cp == '/')
    286 		cp++, pathp++;
    287 	*pathp = '\0';
    288 	if (*oldcp == '{') {
    289 		execbrc(cp, NULL);
    290 		return;
    291 	}
    292 	matchdir(cp);
    293 endit:
    294 	pathp = spathp;
    295 	*pathp = '\0';
    296 }
    297 
    298 static void
    299 matchdir(char *pattern)
    300 {
    301 	struct stat stb;
    302 	struct dirent *dp;
    303 	DIR *dirp;
    304 
    305 	dirp = opendir(path);
    306 	if (dirp == NULL) {
    307 		if (expany)
    308 			return;
    309 		goto patherr2;
    310 	}
    311 	if (fstat(dirp->dd_fd, &stb) < 0)
    312 		goto patherr1;
    313 	if (!S_ISDIR(stb.st_mode)) {
    314 		errno = ENOTDIR;
    315 		goto patherr1;
    316 	}
    317 	while ((dp = readdir(dirp)) != NULL)
    318 		if (match(dp->d_name, pattern)) {
    319 			if (which & E_TILDE)
    320 				Cat(path, dp->d_name);
    321 			else {
    322 				strcpy(pathp, dp->d_name);
    323 				Cat(tilde, tpathp);
    324 				*pathp = '\0';
    325 			}
    326 		}
    327 	closedir(dirp);
    328 	return;
    329 
    330 patherr1:
    331 	closedir(dirp);
    332 patherr2:
    333 	strcat(path, ": ");
    334 	strcat(path, strerror(errno));
    335 	yyerror(path);
    336 }
    337 
    338 static int
    339 execbrc(char *p, char *s)
    340 {
    341 	char restbuf[BUFSIZ + 2];
    342 	char *pe, *pm, *pl;
    343 	int brclev = 0;
    344 	char *lm, savec, *spathp;
    345 
    346 	for (lm = restbuf; *p != '{'; *lm++ = *p++)
    347 		continue;
    348 	for (pe = ++p; *pe; pe++)
    349 		switch (*pe) {
    350 
    351 		case '{':
    352 			brclev++;
    353 			continue;
    354 
    355 		case '}':
    356 			if (brclev == 0)
    357 				goto pend;
    358 			brclev--;
    359 			continue;
    360 
    361 		case '[':
    362 			for (pe++; *pe && *pe != ']'; pe++)
    363 				continue;
    364 			if (!*pe)
    365 				yyerror("Missing ']'");
    366 			continue;
    367 		}
    368 pend:
    369 	if (brclev || !*pe) {
    370 		yyerror("Missing '}'");
    371 		return (0);
    372 	}
    373 	for (pl = pm = p; pm <= pe; pm++)
    374 		switch (*pm & (QUOTE|TRIM)) {
    375 
    376 		case '{':
    377 			brclev++;
    378 			continue;
    379 
    380 		case '}':
    381 			if (brclev) {
    382 				brclev--;
    383 				continue;
    384 			}
    385 			goto doit;
    386 
    387 		case ',':
    388 			if (brclev)
    389 				continue;
    390 doit:
    391 			savec = *pm;
    392 			*pm = 0;
    393 			strlcpy(lm, pl, sizeof(restbuf) - (lm - restbuf));
    394 			strlcat(restbuf, pe + 1, sizeof(restbuf));
    395 			*pm = savec;
    396 			if (s == 0) {
    397 				spathp = pathp;
    398 				expsh(restbuf);
    399 				pathp = spathp;
    400 				*pathp = 0;
    401 			} else if (amatch(s, restbuf))
    402 				return (1);
    403 			sort();
    404 			pl = pm + 1;
    405 			continue;
    406 
    407 		case '[':
    408 			for (pm++; *pm && *pm != ']'; pm++)
    409 				continue;
    410 			if (!*pm)
    411 				yyerror("Missing ']'");
    412 			continue;
    413 		}
    414 	return (0);
    415 }
    416 
    417 static int
    418 match(char *s, char *p)
    419 {
    420 	int c;
    421 	char *sentp;
    422 	char sexpany = expany;
    423 
    424 	if (*s == '.' && *p != '.')
    425 		return (0);
    426 	sentp = entp;
    427 	entp = s;
    428 	c = amatch(s, p);
    429 	entp = sentp;
    430 	expany = sexpany;
    431 	return (c);
    432 }
    433 
    434 static int
    435 amatch(char *s, char *p)
    436 {
    437 	int scc;
    438 	int ok, lc;
    439 	char *spathp;
    440 	struct stat stb;
    441 	int c, cc;
    442 
    443 	expany = 1;
    444 	for (;;) {
    445 		scc = *s++ & TRIM;
    446 		switch (c = *p++) {
    447 
    448 		case '{':
    449 			return (execbrc(p - 1, s - 1));
    450 
    451 		case '[':
    452 			ok = 0;
    453 			lc = 077777;
    454 			while ((cc = *p++) != 0) {
    455 				if (cc == ']') {
    456 					if (ok)
    457 						break;
    458 					return (0);
    459 				}
    460 				if (cc == '-') {
    461 					if (lc <= scc && scc <= *p++)
    462 						ok++;
    463 				} else
    464 					if (scc == (lc = cc))
    465 						ok++;
    466 			}
    467 			if (cc == 0) {
    468 				yyerror("Missing ']'");
    469 				return (0);
    470 			}
    471 			continue;
    472 
    473 		case '*':
    474 			if (!*p)
    475 				return (1);
    476 			if (*p == '/') {
    477 				p++;
    478 				goto slash;
    479 			}
    480 			for (s--; *s; s++)
    481 				if (amatch(s, p))
    482 					return (1);
    483 			return (0);
    484 
    485 		case '\0':
    486 			return (scc == '\0');
    487 
    488 		default:
    489 			if ((c & TRIM) != scc)
    490 				return (0);
    491 			continue;
    492 
    493 		case '?':
    494 			if (scc == '\0')
    495 				return (0);
    496 			continue;
    497 
    498 		case '/':
    499 			if (scc)
    500 				return (0);
    501 slash:
    502 			s = entp;
    503 			spathp = pathp;
    504 			while (*s)
    505 				addpath(*s++);
    506 			addpath('/');
    507 			if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) {
    508 				if (*p == '\0') {
    509 					if (which & E_TILDE)
    510 						Cat(path, "");
    511 					else
    512 						Cat(tilde, tpathp);
    513 				} else
    514 					expsh(p);
    515 			}
    516 			pathp = spathp;
    517 			*pathp = '\0';
    518 			return (0);
    519 		}
    520 	}
    521 }
    522 
    523 static int
    524 smatch(char *s, char *p)
    525 {
    526 	int scc;
    527 	int ok, lc;
    528 	int c, cc;
    529 
    530 	for (;;) {
    531 		scc = *s++ & TRIM;
    532 		switch (c = *p++) {
    533 
    534 		case '[':
    535 			ok = 0;
    536 			lc = 077777;
    537 			while ((cc = *p++) != 0) {
    538 				if (cc == ']') {
    539 					if (ok)
    540 						break;
    541 					return (0);
    542 				}
    543 				if (cc == '-') {
    544 					if (lc <= scc && scc <= *p++)
    545 						ok++;
    546 				} else
    547 					if (scc == (lc = cc))
    548 						ok++;
    549 			}
    550 			if (cc == 0) {
    551 				yyerror("Missing ']'");
    552 				return (0);
    553 			}
    554 			continue;
    555 
    556 		case '*':
    557 			if (!*p)
    558 				return (1);
    559 			for (s--; *s; s++)
    560 				if (smatch(s, p))
    561 					return (1);
    562 			return (0);
    563 
    564 		case '\0':
    565 			return (scc == '\0');
    566 
    567 		default:
    568 			if ((c & TRIM) != scc)
    569 				return (0);
    570 			continue;
    571 
    572 		case '?':
    573 			if (scc == 0)
    574 				return (0);
    575 			continue;
    576 
    577 		}
    578 	}
    579 }
    580 
    581 static void
    582 Cat(char *s1, char *s2)
    583 {
    584 	int len = strlen(s1) + strlen(s2) + 1;
    585 	char *s;
    586 
    587 	nleft -= len;
    588 	if (nleft <= 0 || ++eargc >= GAVSIZ)
    589 		yyerror("Arguments too long");
    590 	eargv[eargc] = 0;
    591 	eargv[eargc - 1] = s = malloc(len);
    592 	if (s == NULL)
    593 		fatal("ran out of memory\n");
    594 	while ((*s++ = *s1++ & TRIM) != 0)
    595 		;
    596 	s--;
    597 	while ((*s++ = *s2++ & TRIM) != 0)
    598 		;
    599 }
    600 
    601 static void
    602 addpath(int c)
    603 {
    604 
    605 	if (pathp >= lastpathp)
    606 		yyerror("Pathname too long");
    607 	else {
    608 		*pathp++ = c & TRIM;
    609 		*pathp = '\0';
    610 	}
    611 }
    612 
    613 /*
    614  * Expand file names beginning with `~' into the
    615  * user's home directory path name. Return a pointer in buf to the
    616  * part corresponding to `file'.
    617  */
    618 char *
    619 exptilde(char *buf, char *file)
    620 {
    621 	char *s1, *s2, *s3;
    622 	extern char homedir[];
    623 
    624 	if (*file != '~') {
    625 		strcpy(buf, file);
    626 		return(buf);
    627 	}
    628 	if (*++file == '\0') {
    629 		s2 = homedir;
    630 		s3 = NULL;
    631 	} else if (*file == '/') {
    632 		s2 = homedir;
    633 		s3 = file;
    634 	} else {
    635 		s3 = file;
    636 		while (*s3 && *s3 != '/')
    637 			s3++;
    638 		if (*s3 == '/')
    639 			*s3 = '\0';
    640 		else
    641 			s3 = NULL;
    642 		if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
    643 			if ((pw = getpwnam(file)) == NULL) {
    644 				error("%s: unknown user name\n", file);
    645 				if (s3 != NULL)
    646 					*s3 = '/';
    647 				return(NULL);
    648 			}
    649 		}
    650 		if (s3 != NULL)
    651 			*s3 = '/';
    652 		s2 = pw->pw_dir;
    653 	}
    654 	for (s1 = buf; (*s1++ = *s2++) != 0; )
    655 		;
    656 	s2 = --s1;
    657 	if (s3 != NULL) {
    658 		s2++;
    659 		while ((*s1++ = *s3++) != 0)
    660 			;
    661 	}
    662 	return(s2);
    663 }
    664