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