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