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