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