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