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