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