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  *
14  * Copyright (c) 1998, by Sun Microsystems, Inc.
15  * All rights reserved.
16  */
17 #pragma ident	"%Z%%M%	%I%	%E% SMI"
18 
19 #include "defs.h"
20 #include <string.h>
21 
22 #define	GAVSIZ	NCARGS / 6
23 #define	LC '{'
24 #define	RC '}'
25 
26 static char	shchars[] = "${[*?";
27 
28 int	which;		/* bit mask of types to expand */
29 int	eargc;		/* expanded arg count */
30 char	**eargv;	/* expanded arg vectors */
31 char	*path;
32 char	*pathp;
33 char	*lastpathp;
34 char	*tilde;		/* "~user" if not expanding tilde, else "" */
35 char	*tpathp;
36 int	nleft;
37 
38 int	expany;		/* any expansions done? */
39 char	*entp;
40 char	**sortbase;
41 
42 char	*index();
43 int	argcmp();
44 
45 #define	sort()	qsort((char *)sortbase, &eargv[eargc] - sortbase, \
46 		sizeof (*sortbase), argcmp), sortbase = &eargv[eargc]
47 
48 #define	MIN(a, b)	((a) < (b) ? (a) : (b))
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[LINESIZE];
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[LINESIZE];
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 				(void) snprintf(buf, sizeof (buf), "%s%s%s", s,
159 				    tp->n_name, tail);
160 				expstr(buf);
161 			}
162 			return;
163 		}
164 		(void) snprintf(buf, sizeof (buf), "%s%s", s, tail);
165 		expstr(buf);
166 		return;
167 	}
168 	if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) {
169 		Cat(s, "");
170 		sort();
171 		return;
172 	}
173 	if (*s == '~') {
174 		cp = ++s;
175 		if (*cp == '\0' || *cp == '/') {
176 			tilde = "~";
177 			cp1 = homedir;
178 		} else {
179 			tilde = cp1 = buf;
180 			*cp1++ = '~';
181 			do {
182 				if (cp1 >= &buf[sizeof (buf)]) {
183 					yyerror("User name too long");
184 					return;
185 				}
186 				*cp1++ = *cp++;
187 			} while (*cp && *cp != '/');
188 			*cp1 = '\0';
189 			if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) {
190 				if ((pw = getpwnam(buf+1)) == NULL) {
191 					static char unknown_user[] =
192 					    ": unknown user name";
193 
194 					cp1 = MIN(cp1,
195 					    &buf[sizeof (buf)] -
196 					    sizeof (unknown_user));
197 					strcpy(cp1, unknown_user);
198 					yyerror(buf+1);
199 					return;
200 				}
201 			}
202 			cp1 = pw->pw_dir;
203 			s = cp;
204 		}
205 		for (cp = path; cp <= lastpathp + 1 && (*cp++ = *cp1++); )
206 			;
207 		tpathp = pathp = cp - 1;
208 	} else {
209 		tpathp = pathp = path;
210 		tilde = "";
211 	}
212 	*pathp = '\0';
213 	if (!(which & E_SHELL)) {
214 		if (which & E_TILDE)
215 			Cat(path, s);
216 		else
217 			Cat(tilde, s);
218 		sort();
219 		return;
220 	}
221 	oeargc = eargc;
222 	expany = 0;
223 	expsh(s);
224 	if (eargc == oeargc)
225 		Cat(s, "");		/* "nonomatch" is set */
226 	sort();
227 }
228 
229 static
230 argcmp(a1, a2)
231 	char **a1, **a2;
232 {
233 
234 	return (strcmp(*a1, *a2));
235 }
236 
237 /*
238  * If there are any Shell meta characters in the name,
239  * expand into a list, after searching directory
240  */
241 expsh(s)
242 	char *s;
243 {
244 	register char *cp;
245 	register char *spathp, *oldcp;
246 	struct stat stb;
247 
248 	spathp = pathp;
249 	cp = s;
250 	while (!any(*cp, shchars)) {
251 		if (*cp == '\0') {
252 			if (!expany || stat(path, &stb) >= 0) {
253 				if (which & E_TILDE)
254 					Cat(path, "");
255 				else
256 					Cat(tilde, tpathp);
257 			}
258 			goto endit;
259 		}
260 		addpath(*cp++);
261 	}
262 	oldcp = cp;
263 	while (cp > s && *cp != '/')
264 		cp--, pathp--;
265 	if (*cp == '/')
266 		cp++, pathp++;
267 	*pathp = '\0';
268 	if (*oldcp == '{') {
269 		execbrc(cp, NULL);
270 		return;
271 	}
272 	matchdir(cp);
273 endit:
274 	pathp = spathp;
275 	*pathp = '\0';
276 }
277 
278 matchdir(pattern)
279 	char *pattern;
280 {
281 	struct stat stb;
282 	register struct dirent *dp;
283 	DIR *dirp;
284 
285 	dirp = opendir(path);
286 	if (dirp == NULL) {
287 		if (expany)
288 			return;
289 		goto patherr2;
290 	}
291 	if (fstat(dirp->dd_fd, &stb) < 0)
292 		goto patherr1;
293 	if (!ISDIR(stb.st_mode)) {
294 		errno = ENOTDIR;
295 		goto patherr1;
296 	}
297 	while ((dp = readdir(dirp)) != NULL)
298 		if (match(dp->d_name, pattern)) {
299 			if (which & E_TILDE)
300 				Cat(path, dp->d_name);
301 			else {
302 				if (pathp + strlen(dp->d_name) - 1 >
303 				    lastpathp) {
304 					errno = ENAMETOOLONG;
305 					goto patherr1;
306 				}
307 				strcpy(pathp, dp->d_name);
308 				Cat(tilde, tpathp);
309 				*pathp = '\0';
310 			}
311 		}
312 	closedir(dirp);
313 	return;
314 
315 patherr1:
316 	closedir(dirp);
317 patherr2:
318 	{
319 		char *strerr = strerror(errno);
320 
321 		if (path + strlen(path) + strlen(strerr) + 1 > lastpathp)
322 			strcpy(lastpathp - strlen(strerr) - 1, ": ");
323 		else
324 			strcat(path, ": ");
325 		strcat(path, strerr);
326 	}
327 	yyerror(path);
328 }
329 
330 execbrc(p, s)
331 	char *p, *s;
332 {
333 	char restbuf[LINESIZE + 2];
334 	register char *pe, *pm, *pl;
335 	int brclev = 0;
336 	char *lm, savec, *spathp;
337 
338 	for (lm = restbuf; *p != '{'; *lm++ = *p++) {
339 		if (lm >= &restbuf[sizeof (restbuf)]) {
340 			yyerror("Pathname too long");
341 			return (0);
342 		}
343 	}
344 	for (pe = ++p; *pe; pe++)
345 		switch (*pe) {
346 
347 		case '{':
348 			brclev++;
349 			continue;
350 
351 		case '}':
352 			if (brclev == 0)
353 				goto pend;
354 			brclev--;
355 			continue;
356 
357 		case '[':
358 			for (pe++; *pe && *pe != ']'; pe++)
359 				continue;
360 			if (!*pe)
361 				yyerror("Missing ']'");
362 			continue;
363 		}
364 pend:
365 	if (brclev || !*pe) {
366 		yyerror("Missing '}'");
367 		return (0);
368 	}
369 	for (pl = pm = p; pm <= pe; pm++)
370 		switch (*pm & (QUOTE|TRIM)) {
371 
372 		case '{':
373 			brclev++;
374 			continue;
375 
376 		case '}':
377 			if (brclev) {
378 				brclev--;
379 				continue;
380 			}
381 			goto doit;
382 
383 		case ',':
384 			if (brclev)
385 				continue;
386 doit:
387 			savec = *pm;
388 			*pm = 0;
389 			if (lm + strlen(pl) + strlen(pe + 1) >=
390 			    &restbuf[sizeof (restbuf)]) {
391 				yyerror("Pathname too long");
392 				return (0);
393 			}
394 			strcpy(lm, pl);
395 			strcat(restbuf, pe + 1);
396 			*pm = savec;
397 			if (s == 0) {
398 				spathp = pathp;
399 				expsh(restbuf);
400 				pathp = spathp;
401 				*pathp = 0;
402 			} else if (amatch(s, restbuf))
403 				return (1);
404 			sort();
405 			pl = pm + 1;
406 			continue;
407 
408 		case '[':
409 			for (pm++; *pm && *pm != ']'; pm++)
410 				continue;
411 			if (!*pm)
412 				yyerror("Missing ']'");
413 			continue;
414 		}
415 	return (0);
416 }
417 
418 match(s, p)
419 	char *s, *p;
420 {
421 	register int c;
422 	register char *sentp;
423 	char sexpany = expany;
424 
425 	if (*s == '.' && *p != '.')
426 		return (0);
427 	sentp = entp;
428 	entp = s;
429 	c = amatch(s, p);
430 	entp = sentp;
431 	expany = sexpany;
432 	return (c);
433 }
434 
435 amatch(s, p)
436 	register char *s, *p;
437 {
438 	register int scc;
439 	int ok, lc;
440 	char *spathp;
441 	struct stat stb;
442 	int c, cc;
443 
444 	expany = 1;
445 	for (;;) {
446 		scc = *s++ & TRIM;
447 		switch (c = *p++) {
448 
449 		case '{':
450 			return (execbrc(p - 1, s - 1));
451 
452 		case '[':
453 			ok = 0;
454 			lc = 077777;
455 			while (cc = *p++) {
456 				if (cc == ']') {
457 					if (ok)
458 						break;
459 					return (0);
460 				}
461 				if (cc == '-') {
462 					if (lc <= scc && scc <= *p++)
463 						ok++;
464 				} else
465 					if (scc == (lc = cc))
466 						ok++;
467 			}
468 			if (cc == 0) {
469 				yyerror("Missing ']'");
470 				return (0);
471 			}
472 			continue;
473 
474 		case '*':
475 			if (!*p)
476 				return (1);
477 			if (*p == '/') {
478 				p++;
479 				goto slash;
480 			}
481 			for (s--; *s; s++)
482 				if (amatch(s, p))
483 					return (1);
484 			return (0);
485 
486 		case '\0':
487 			return (scc == '\0');
488 
489 		default:
490 			if ((c & TRIM) != scc)
491 				return (0);
492 			continue;
493 
494 		case '?':
495 			if (scc == '\0')
496 				return (0);
497 			continue;
498 
499 		case '/':
500 			if (scc)
501 				return (0);
502 slash:
503 			s = entp;
504 			spathp = pathp;
505 			while (*s)
506 				addpath(*s++);
507 			addpath('/');
508 			if (stat(path, &stb) == 0 && ISDIR(stb.st_mode))
509 				if (*p == '\0') {
510 					if (which & E_TILDE)
511 						Cat(path, "");
512 					else
513 						Cat(tilde, tpathp);
514 				} else
515 					expsh(p);
516 			pathp = spathp;
517 			*pathp = '\0';
518 			return (0);
519 		}
520 	}
521 }
522 
523 smatch(s, p)
524 	register char *s, *p;
525 {
526 	register int scc;
527 	int ok, lc;
528 	int c, cc;
529 
530 	for (;;) {
531 		scc = *s++ & TRIM;
532 		switch (c = *p++) {
533 
534 		case '[':
535 			ok = 0;
536 			lc = 077777;
537 			while (cc = *p++) {
538 				if (cc == ']') {
539 					if (ok)
540 						break;
541 					return (0);
542 				}
543 				if (cc == '-') {
544 					if (lc <= scc && scc <= *p++)
545 						ok++;
546 				} else
547 					if (scc == (lc = cc))
548 						ok++;
549 			}
550 			if (cc == 0) {
551 				yyerror("Missing ']'");
552 				return (0);
553 			}
554 			continue;
555 
556 		case '*':
557 			if (!*p)
558 				return (1);
559 			for (s--; *s; s++)
560 				if (smatch(s, p))
561 					return (1);
562 			return (0);
563 
564 		case '\0':
565 			return (scc == '\0');
566 
567 		default:
568 			if ((c & TRIM) != scc)
569 				return (0);
570 			continue;
571 
572 		case '?':
573 			if (scc == 0)
574 				return (0);
575 			continue;
576 
577 		}
578 	}
579 }
580 
581 Cat(s1, s2)
582 	register char *s1, *s2;
583 {
584 	int len = strlen(s1) + strlen(s2) + 1;
585 	register char *s;
586 
587 	nleft -= len;
588 	if (nleft <= 0 || ++eargc >= GAVSIZ)
589 		fatal("Arguments too long\n");
590 	eargv[eargc] = 0;
591 	eargv[eargc - 1] = s = (char *)malloc(len);
592 	if (s == NULL)
593 		fatal("ran out of memory\n");
594 	while (*s++ = *s1++ & TRIM)
595 		;
596 	s--;
597 	while (*s++ = *s2++ & TRIM)
598 		;
599 }
600 
601 addpath(c)
602 	char c;
603 {
604 
605 	if (pathp > lastpathp)
606 		yyerror("Pathname too long");
607 	else {
608 		*pathp++ = c & TRIM;
609 		*pathp = '\0';
610 	}
611 }
612 
613 /*
614  * Expand file names beginning with `~' into the
615  * user's home directory path name. Return a pointer in buf to the
616  * part corresponding to `file'.
617  */
618 char *
619 exptilde(buf, len, file)
620 	char buf[];
621 	unsigned int len;
622 	register char *file;
623 {
624 	register char *s1, *s2, *s3;
625 	extern char homedir[];
626 
627 	if (*file != '~') {
628 		if (strlen(file) + 1 > len) {
629 			error("pathname too long: %s\n", file);
630 			return (NULL);
631 		}
632 		strcpy(buf, file);
633 		return (buf);
634 	}
635 	if (*++file == '\0') {
636 		s2 = homedir;
637 		s3 = NULL;
638 	} else if (*file == '/') {
639 		s2 = homedir;
640 		s3 = file;
641 	} else {
642 		s3 = file;
643 		while (*s3 && *s3 != '/')
644 			s3++;
645 		if (*s3 == '/')
646 			*s3 = '\0';
647 		else
648 			s3 = NULL;
649 		if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
650 			if ((pw = getpwnam(file)) == NULL) {
651 				error("%s: unknown user name\n", file);
652 				if (s3 != NULL)
653 					*s3 = '/';
654 				return (NULL);
655 			}
656 		}
657 		if (s3 != NULL)
658 			*s3 = '/';
659 		s2 = pw->pw_dir;
660 	}
661 	for (s1 = buf; s1 < &buf[len] && (*s1++ = *s2++); )
662 		;
663 	s2 = --s1;
664 	if (s3 != NULL) {
665 		s2++;
666 		while (s1 < &buf[len] && (*s1++ = *s3++))
667 			;
668 	}
669 	if (s1 == &buf[len]) {
670 		error("pathname too long: %s\n", file - 1);
671 		return (NULL);
672 	}
673 	return (s2);
674 }
675