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