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