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