xref: /netbsd-src/usr.bin/rdist/expand.c (revision 23c8222edbfb0f0932d88a8351d3a0cf817dfb9e)
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