xref: /netbsd-src/usr.bin/rdist/expand.c (revision 2bd8f802e402fd318180dfc11d6dd13f109f058b)
1 /*	$NetBSD: expand.c,v 1.19 2023/08/03 08:03:19 mrg 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.19 2023/08/03 08:03:19 mrg 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 	sortbase = NULL;
145 	eargv = NULL;
146 	path = tpathp = pathp = NULL;
147 	lastpathp = NULL;
148 	return(list);
149 }
150 
151 static void
expstr(char * s)152 expstr(char *s)
153 {
154 	char *cp, *cp1;
155 	struct namelist *tp;
156 	char *tail;
157 	char expbuf[BUFSIZ];
158 	int savec, oeargc;
159 	extern char homedir[];
160 
161 	if (s == NULL || *s == '\0')
162 		return;
163 
164 	if ((which & E_VARS) && (cp = strchr(s, '$')) != NULL) {
165 		*cp++ = '\0';
166 		if (*cp == '\0') {
167 			yyerror("no variable name after '$'");
168 			return;
169 		}
170 		if (*cp == LC) {
171 			cp++;
172 			if ((tail = strchr(cp, RC)) == NULL) {
173 				yyerror("unmatched '{'");
174 				return;
175 			}
176 			*tail++ = savec = '\0';
177 			if (*cp == '\0') {
178 				yyerror("no variable name after '$'");
179 				return;
180 			}
181 		} else {
182 			tail = cp + 1;
183 			savec = *tail;
184 			*tail = '\0';
185 		}
186 		tp = lookup(cp, 0, 0);
187 		if (savec != '\0')
188 			*tail = savec;
189 		if (tp != NULL) {
190 			for (; tp != NULL; tp = tp->n_next) {
191 				snprintf(expbuf, sizeof(expbuf), "%s%s%s", s,
192 				    tp->n_name, tail);
193 				expstr(expbuf);
194 			}
195 			return;
196 		}
197 		snprintf(expbuf, sizeof(expbuf), "%s%s", s, tail);
198 		expstr(expbuf);
199 		return;
200 	}
201 	if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) {
202 		Cat(s, "");
203 		sort();
204 		return;
205 	}
206 	if (*s == '~') {
207 		cp = ++s;
208 		if (*cp == '\0' || *cp == '/') {
209 			tilde = "~";
210 			cp1 = homedir;
211 		} else {
212 			tilde = cp1 = expbuf;
213 			*cp1++ = '~';
214 			do
215 				*cp1++ = *cp++;
216 			while (*cp && *cp != '/');
217 			*cp1 = '\0';
218 			if (pw == NULL || strcmp(pw->pw_name, expbuf+1) != 0) {
219 				if ((pw = getpwnam(expbuf+1)) == NULL) {
220 					strlcat(expbuf, ": unknown user name",
221 					    sizeof(expbuf));
222 					yyerror(expbuf+1);
223 					return;
224 				}
225 			}
226 			cp1 = pw->pw_dir;
227 			s = cp;
228 		}
229 		for (cp = path; (*cp++ = *cp1++) != 0; )
230 			;
231 		tpathp = pathp = cp - 1;
232 	} else {
233 		tpathp = pathp = path;
234 		tilde = "";
235 	}
236 	*pathp = '\0';
237 	if (!(which & E_SHELL)) {
238 		if (which & E_TILDE)
239 			Cat(path, s);
240 		else
241 			Cat(tilde, s);
242 		sort();
243 		return;
244 	}
245 	oeargc = eargc;
246 	expany = 0;
247 	expsh(s);
248 	if (eargc == oeargc)
249 		Cat(s, "");		/* "nonomatch" is set */
250 	sort();
251 }
252 
253 static int
argcmp(const void * a1,const void * a2)254 argcmp(const void *a1, const void *a2)
255 {
256 
257 	return (strcmp(*(const char * const *)a1, *(const char * const *)a2));
258 }
259 
260 /*
261  * If there are any Shell meta characters in the name,
262  * expand into a list, after searching directory
263  */
264 static void
expsh(char * s)265 expsh(char *s)
266 {
267 	char *cp;
268 	char *spathp, *oldcp;
269 	struct stat stb;
270 
271 	spathp = pathp;
272 	cp = s;
273 	while (!any(*cp, shchars)) {
274 		if (*cp == '\0') {
275 			if (!expany || stat(path, &stb) >= 0) {
276 				if (which & E_TILDE)
277 					Cat(path, "");
278 				else
279 					Cat(tilde, tpathp);
280 			}
281 			goto endit;
282 		}
283 		addpath(*cp++);
284 	}
285 	oldcp = cp;
286 	while (cp > s && *cp != '/')
287 		cp--, pathp--;
288 	if (*cp == '/')
289 		cp++, pathp++;
290 	*pathp = '\0';
291 	if (*oldcp == '{') {
292 		execbrc(cp, NULL);
293 		return;
294 	}
295 	matchdir(cp);
296 endit:
297 	pathp = spathp;
298 	*pathp = '\0';
299 }
300 
301 static void
matchdir(char * pattern)302 matchdir(char *pattern)
303 {
304 	struct stat stb;
305 	struct dirent *dp;
306 	DIR *dirp;
307 
308 	dirp = opendir(path);
309 	if (dirp == NULL) {
310 		if (expany)
311 			return;
312 		goto patherr2;
313 	}
314 	if (fstat(dirp->dd_fd, &stb) < 0)
315 		goto patherr1;
316 	if (!S_ISDIR(stb.st_mode)) {
317 		errno = ENOTDIR;
318 		goto patherr1;
319 	}
320 	while ((dp = readdir(dirp)) != NULL)
321 		if (match(dp->d_name, pattern)) {
322 			if (which & E_TILDE)
323 				Cat(path, dp->d_name);
324 			else {
325 				strcpy(pathp, dp->d_name);
326 				Cat(tilde, tpathp);
327 				*pathp = '\0';
328 			}
329 		}
330 	closedir(dirp);
331 	return;
332 
333 patherr1:
334 	closedir(dirp);
335 patherr2:
336 	strcat(path, ": ");
337 	strcat(path, strerror(errno));
338 	yyerror(path);
339 }
340 
341 static int
execbrc(char * p,char * s)342 execbrc(char *p, char *s)
343 {
344 	char restbuf[BUFSIZ + 2];
345 	char *pe, *pm, *pl;
346 	int brclev = 0;
347 	char *lm, savec, *spathp;
348 
349 	for (lm = restbuf; *p != '{'; *lm++ = *p++)
350 		continue;
351 	for (pe = ++p; *pe; pe++)
352 		switch (*pe) {
353 
354 		case '{':
355 			brclev++;
356 			continue;
357 
358 		case '}':
359 			if (brclev == 0)
360 				goto pend;
361 			brclev--;
362 			continue;
363 
364 		case '[':
365 			for (pe++; *pe && *pe != ']'; pe++)
366 				continue;
367 			if (!*pe)
368 				yyerror("Missing ']'");
369 			continue;
370 		}
371 pend:
372 	if (brclev || !*pe) {
373 		yyerror("Missing '}'");
374 		return (0);
375 	}
376 	for (pl = pm = p; pm <= pe; pm++)
377 		switch (*pm & (QUOTE|TRIM)) {
378 
379 		case '{':
380 			brclev++;
381 			continue;
382 
383 		case '}':
384 			if (brclev) {
385 				brclev--;
386 				continue;
387 			}
388 			goto doit;
389 
390 		case ',':
391 			if (brclev)
392 				continue;
393 doit:
394 			savec = *pm;
395 			*pm = 0;
396 			strlcpy(lm, pl, sizeof(restbuf) - (lm - restbuf));
397 			strlcat(restbuf, pe + 1, sizeof(restbuf));
398 			*pm = savec;
399 			if (s == 0) {
400 				spathp = pathp;
401 				expsh(restbuf);
402 				pathp = spathp;
403 				*pathp = 0;
404 			} else if (amatch(s, restbuf))
405 				return (1);
406 			sort();
407 			pl = pm + 1;
408 			continue;
409 
410 		case '[':
411 			for (pm++; *pm && *pm != ']'; pm++)
412 				continue;
413 			if (!*pm)
414 				yyerror("Missing ']'");
415 			continue;
416 		}
417 	return (0);
418 }
419 
420 static int
match(char * s,char * p)421 match(char *s, char *p)
422 {
423 	int c;
424 	char *sentp;
425 	char sexpany = expany;
426 
427 	if (*s == '.' && *p != '.')
428 		return (0);
429 	sentp = entp;
430 	entp = s;
431 	c = amatch(s, p);
432 	entp = sentp;
433 	expany = sexpany;
434 	return (c);
435 }
436 
437 static int
amatch(char * s,char * p)438 amatch(char *s, char *p)
439 {
440 	int scc;
441 	int ok, lc;
442 	char *spathp;
443 	struct stat stb;
444 	int c, cc;
445 
446 	expany = 1;
447 	for (;;) {
448 		scc = *s++ & TRIM;
449 		switch (c = *p++) {
450 
451 		case '{':
452 			return (execbrc(p - 1, s - 1));
453 
454 		case '[':
455 			ok = 0;
456 			lc = 077777;
457 			while ((cc = *p++) != 0) {
458 				if (cc == ']') {
459 					if (ok)
460 						break;
461 					return (0);
462 				}
463 				if (cc == '-') {
464 					if (lc <= scc && scc <= *p++)
465 						ok++;
466 				} else
467 					if (scc == (lc = cc))
468 						ok++;
469 			}
470 			if (cc == 0) {
471 				yyerror("Missing ']'");
472 				return (0);
473 			}
474 			continue;
475 
476 		case '*':
477 			if (!*p)
478 				return (1);
479 			if (*p == '/') {
480 				p++;
481 				goto slash;
482 			}
483 			for (s--; *s; s++)
484 				if (amatch(s, p))
485 					return (1);
486 			return (0);
487 
488 		case '\0':
489 			return (scc == '\0');
490 
491 		default:
492 			if ((c & TRIM) != scc)
493 				return (0);
494 			continue;
495 
496 		case '?':
497 			if (scc == '\0')
498 				return (0);
499 			continue;
500 
501 		case '/':
502 			if (scc)
503 				return (0);
504 slash:
505 			s = entp;
506 			spathp = pathp;
507 			while (*s)
508 				addpath(*s++);
509 			addpath('/');
510 			if (stat(path, &stb) == 0 && S_ISDIR(stb.st_mode)) {
511 				if (*p == '\0') {
512 					if (which & E_TILDE)
513 						Cat(path, "");
514 					else
515 						Cat(tilde, tpathp);
516 				} else
517 					expsh(p);
518 			}
519 			pathp = spathp;
520 			*pathp = '\0';
521 			return (0);
522 		}
523 	}
524 }
525 
526 static void
Cat(const char * s1,const char * s2)527 Cat(const char *s1, const char *s2)
528 {
529 	int len = strlen(s1) + strlen(s2) + 1;
530 	char *s;
531 
532 	nleft -= len;
533 	if (nleft <= 0 || ++eargc >= GAVSIZ)
534 		yyerror("Arguments too long");
535 	eargv[eargc] = 0;
536 	eargv[eargc - 1] = s = malloc(len);
537 	if (s == NULL)
538 		fatal("ran out of memory\n");
539 	while ((*s++ = *s1++ & TRIM) != 0)
540 		;
541 	s--;
542 	while ((*s++ = *s2++ & TRIM) != 0)
543 		;
544 }
545 
546 static void
addpath(int c)547 addpath(int c)
548 {
549 
550 	if (pathp >= lastpathp)
551 		yyerror("Pathname too long");
552 	else {
553 		*pathp++ = c & TRIM;
554 		*pathp = '\0';
555 	}
556 }
557 
558 /*
559  * Expand file names beginning with `~' into the
560  * user's home directory path name. Return a pointer in buf to the
561  * part corresponding to `file'.
562  */
563 char *
exptilde(char * expbuf,char * file)564 exptilde(char *expbuf, char *file)
565 {
566 	char *s1, *s2, *s3;
567 	extern char homedir[];
568 
569 	if (*file != '~') {
570 		strcpy(expbuf, file);
571 		return(expbuf);
572 	}
573 	if (*++file == '\0') {
574 		s2 = homedir;
575 		s3 = NULL;
576 	} else if (*file == '/') {
577 		s2 = homedir;
578 		s3 = file;
579 	} else {
580 		s3 = file;
581 		while (*s3 && *s3 != '/')
582 			s3++;
583 		if (*s3 == '/')
584 			*s3 = '\0';
585 		else
586 			s3 = NULL;
587 		if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
588 			if ((pw = getpwnam(file)) == NULL) {
589 				error("%s: unknown user name\n", file);
590 				if (s3 != NULL)
591 					*s3 = '/';
592 				return(NULL);
593 			}
594 		}
595 		if (s3 != NULL)
596 			*s3 = '/';
597 		s2 = pw->pw_dir;
598 	}
599 	for (s1 = expbuf; (*s1++ = *s2++) != 0; )
600 		;
601 	s2 = --s1;
602 	if (s3 != NULL) {
603 		s2++;
604 		while ((*s1++ = *s3++) != 0)
605 			;
606 	}
607 	return(s2);
608 }
609