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