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