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