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