expand.c revision 1.5 1 /*
2 * Copyright (c) 1983, 1993
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34 #ifndef lint
35 /* from: static char sccsid[] = "@(#)expand.c 8.1 (Berkeley) 6/9/93"; */
36 static char *rcsid = "$Id: expand.c,v 1.5 1994/03/07 05:05:28 cgd Exp $";
37 #endif /* not lint */
38
39 #include "defs.h"
40
41 #define GAVSIZ NCARGS / 6
42 #define LC '{'
43 #define RC '}'
44
45 static char shchars[] = "${[*?";
46
47 int which; /* bit mask of types to expand */
48 int eargc; /* expanded arg count */
49 char **eargv; /* expanded arg vectors */
50 char *path;
51 char *pathp;
52 char *lastpathp;
53 char *tilde; /* "~user" if not expanding tilde, else "" */
54 char *tpathp;
55 int nleft;
56
57 int expany; /* any expansions done? */
58 char *entp;
59 char **sortbase;
60
61 #define sort() qsort((char *)sortbase, &eargv[eargc] - sortbase, \
62 sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
63
64 static void Cat __P((char *, char *));
65 static void addpath __P((int));
66 static int amatch __P((char *, char *));
67 static int argcmp __P((const void *, const void *));
68 static int execbrc __P((char *, char *));
69 static void expsh __P((char *));
70 static void expstr __P((char *));
71 static int match __P((char *, char *));
72 static void matchdir __P((char *));
73 static int smatch __P((char *, char *));
74
75 /*
76 * Take a list of names and expand any macros, etc.
77 * wh = E_VARS if expanding variables.
78 * wh = E_SHELL if expanding shell characters.
79 * wh = E_TILDE if expanding `~'.
80 * or any of these or'ed together.
81 *
82 * Major portions of this were snarfed from csh/sh.glob.c.
83 */
84 struct namelist *
85 expand(list, wh)
86 struct namelist *list;
87 int wh;
88 {
89 register struct namelist *nl, *prev;
90 register int n;
91 char pathbuf[BUFSIZ];
92 char *argvbuf[GAVSIZ];
93
94 if (debug) {
95 printf("expand(%x, %d)\nlist = ", list, wh);
96 prnames(list);
97 }
98
99 if (wh == 0) {
100 register char *cp;
101
102 for (nl = list; nl != NULL; nl = nl->n_next)
103 for (cp = nl->n_name; *cp; cp++)
104 *cp = *cp & TRIM;
105 return(list);
106 }
107
108 which = wh;
109 path = tpathp = pathp = pathbuf;
110 *pathp = '\0';
111 lastpathp = &path[sizeof pathbuf - 2];
112 tilde = "";
113 eargc = 0;
114 eargv = sortbase = argvbuf;
115 *eargv = 0;
116 nleft = NCARGS - 4;
117 /*
118 * Walk the name list and expand names into eargv[];
119 */
120 for (nl = list; nl != NULL; nl = nl->n_next)
121 expstr(nl->n_name);
122 /*
123 * Take expanded list of names from eargv[] and build a new list.
124 */
125 list = prev = NULL;
126 for (n = 0; n < eargc; n++) {
127 nl = makenl(NULL);
128 nl->n_name = eargv[n];
129 if (prev == NULL)
130 list = prev = nl;
131 else {
132 prev->n_next = nl;
133 prev = nl;
134 }
135 }
136 if (debug) {
137 printf("expanded list = ");
138 prnames(list);
139 }
140 return(list);
141 }
142
143 static void
144 expstr(s)
145 char *s;
146 {
147 register char *cp, *cp1;
148 register struct namelist *tp;
149 char *tail;
150 char buf[BUFSIZ];
151 int savec, oeargc;
152 extern char homedir[];
153
154 if (s == NULL || *s == '\0')
155 return;
156
157 if ((which & E_VARS) && (cp = index(s, '$')) != NULL) {
158 *cp++ = '\0';
159 if (*cp == '\0') {
160 yyerror("no variable name after '$'");
161 return;
162 }
163 if (*cp == LC) {
164 cp++;
165 if ((tail = index(cp, RC)) == NULL) {
166 yyerror("unmatched '{'");
167 return;
168 }
169 *tail++ = savec = '\0';
170 if (*cp == '\0') {
171 yyerror("no variable name after '$'");
172 return;
173 }
174 } else {
175 tail = cp + 1;
176 savec = *tail;
177 *tail = '\0';
178 }
179 tp = lookup(cp, NULL, 0);
180 if (savec != '\0')
181 *tail = savec;
182 if (tp != NULL) {
183 for (; tp != NULL; tp = tp->n_next) {
184 sprintf(buf, "%s%s%s", s, tp->n_name, tail);
185 expstr(buf);
186 }
187 return;
188 }
189 sprintf(buf, "%s%s", s, tail);
190 expstr(buf);
191 return;
192 }
193 if ((which & ~E_VARS) == 0 || !strcmp(s, "{") || !strcmp(s, "{}")) {
194 Cat(s, "");
195 sort();
196 return;
197 }
198 if (*s == '~') {
199 cp = ++s;
200 if (*cp == '\0' || *cp == '/') {
201 tilde = "~";
202 cp1 = homedir;
203 } else {
204 tilde = cp1 = buf;
205 *cp1++ = '~';
206 do
207 *cp1++ = *cp++;
208 while (*cp && *cp != '/');
209 *cp1 = '\0';
210 if (pw == NULL || strcmp(pw->pw_name, buf+1) != 0) {
211 if ((pw = getpwnam(buf+1)) == NULL) {
212 strcat(buf, ": unknown user name");
213 yyerror(buf+1);
214 return;
215 }
216 }
217 cp1 = pw->pw_dir;
218 s = cp;
219 }
220 for (cp = path; *cp++ = *cp1++; )
221 ;
222 tpathp = pathp = cp - 1;
223 } else {
224 tpathp = pathp = path;
225 tilde = "";
226 }
227 *pathp = '\0';
228 if (!(which & E_SHELL)) {
229 if (which & E_TILDE)
230 Cat(path, s);
231 else
232 Cat(tilde, s);
233 sort();
234 return;
235 }
236 oeargc = eargc;
237 expany = 0;
238 expsh(s);
239 if (eargc == oeargc)
240 Cat(s, ""); /* "nonomatch" is set */
241 sort();
242 }
243
244 static int
245 argcmp(a1, a2)
246 const void *a1, *a2;
247 {
248
249 return (strcmp(*(char **)a1, *(char **)a2));
250 }
251
252 /*
253 * If there are any Shell meta characters in the name,
254 * expand into a list, after searching directory
255 */
256 static void
257 expsh(s)
258 char *s;
259 {
260 register char *cp;
261 register char *spathp, *oldcp;
262 struct stat stb;
263
264 spathp = pathp;
265 cp = s;
266 while (!any(*cp, shchars)) {
267 if (*cp == '\0') {
268 if (!expany || stat(path, &stb) >= 0) {
269 if (which & E_TILDE)
270 Cat(path, "");
271 else
272 Cat(tilde, tpathp);
273 }
274 goto endit;
275 }
276 addpath(*cp++);
277 }
278 oldcp = cp;
279 while (cp > s && *cp != '/')
280 cp--, pathp--;
281 if (*cp == '/')
282 cp++, pathp++;
283 *pathp = '\0';
284 if (*oldcp == '{') {
285 execbrc(cp, NULL);
286 return;
287 }
288 matchdir(cp);
289 endit:
290 pathp = spathp;
291 *pathp = '\0';
292 }
293
294 static void
295 matchdir(pattern)
296 char *pattern;
297 {
298 struct stat stb;
299 register struct direct *dp;
300 DIR *dirp;
301
302 dirp = opendir(path);
303 if (dirp == NULL) {
304 if (expany)
305 return;
306 goto patherr2;
307 }
308 if (fstat(dirp->dd_fd, &stb) < 0)
309 goto patherr1;
310 if (!ISDIR(stb.st_mode)) {
311 errno = ENOTDIR;
312 goto patherr1;
313 }
314 while ((dp = readdir(dirp)) != NULL)
315 if (match(dp->d_name, pattern)) {
316 if (which & E_TILDE)
317 Cat(path, dp->d_name);
318 else {
319 strcpy(pathp, dp->d_name);
320 Cat(tilde, tpathp);
321 *pathp = '\0';
322 }
323 }
324 closedir(dirp);
325 return;
326
327 patherr1:
328 closedir(dirp);
329 patherr2:
330 strcat(path, ": ");
331 strcat(path, strerror(errno));
332 yyerror(path);
333 }
334
335 static int
336 execbrc(p, s)
337 char *p, *s;
338 {
339 char restbuf[BUFSIZ + 2];
340 register char *pe, *pm, *pl;
341 int brclev = 0;
342 char *lm, savec, *spathp;
343
344 for (lm = restbuf; *p != '{'; *lm++ = *p++)
345 continue;
346 for (pe = ++p; *pe; pe++)
347 switch (*pe) {
348
349 case '{':
350 brclev++;
351 continue;
352
353 case '}':
354 if (brclev == 0)
355 goto pend;
356 brclev--;
357 continue;
358
359 case '[':
360 for (pe++; *pe && *pe != ']'; pe++)
361 continue;
362 if (!*pe)
363 yyerror("Missing ']'");
364 continue;
365 }
366 pend:
367 if (brclev || !*pe) {
368 yyerror("Missing '}'");
369 return (0);
370 }
371 for (pl = pm = p; pm <= pe; pm++)
372 switch (*pm & (QUOTE|TRIM)) {
373
374 case '{':
375 brclev++;
376 continue;
377
378 case '}':
379 if (brclev) {
380 brclev--;
381 continue;
382 }
383 goto doit;
384
385 case ',':
386 if (brclev)
387 continue;
388 doit:
389 savec = *pm;
390 *pm = 0;
391 strcpy(lm, pl);
392 strcat(restbuf, pe + 1);
393 *pm = savec;
394 if (s == 0) {
395 spathp = pathp;
396 expsh(restbuf);
397 pathp = spathp;
398 *pathp = 0;
399 } else if (amatch(s, restbuf))
400 return (1);
401 sort();
402 pl = pm + 1;
403 continue;
404
405 case '[':
406 for (pm++; *pm && *pm != ']'; pm++)
407 continue;
408 if (!*pm)
409 yyerror("Missing ']'");
410 continue;
411 }
412 return (0);
413 }
414
415 static int
416 match(s, p)
417 char *s, *p;
418 {
419 register int c;
420 register 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
434 amatch(s, p)
435 register char *s, *p;
436 {
437 register 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++) {
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 && 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 pathp = spathp;
516 *pathp = '\0';
517 return (0);
518 }
519 }
520 }
521
522 static int
523 smatch(s, p)
524 register char *s, *p;
525 {
526 register 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++) {
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(s1, s2)
583 register char *s1, *s2;
584 {
585 int len = strlen(s1) + strlen(s2) + 1;
586 register char *s;
587
588 nleft -= len;
589 if (nleft <= 0 || ++eargc >= GAVSIZ)
590 yyerror("Arguments too long");
591 eargv[eargc] = 0;
592 eargv[eargc - 1] = s = malloc(len);
593 if (s == NULL)
594 fatal("ran out of memory\n");
595 while (*s++ = *s1++ & TRIM)
596 ;
597 s--;
598 while (*s++ = *s2++ & TRIM)
599 ;
600 }
601
602 static void
603 addpath(c)
604 int c;
605 {
606
607 if (pathp >= lastpathp)
608 yyerror("Pathname too long");
609 else {
610 *pathp++ = c & TRIM;
611 *pathp = '\0';
612 }
613 }
614
615 /*
616 * Expand file names beginning with `~' into the
617 * user's home directory path name. Return a pointer in buf to the
618 * part corresponding to `file'.
619 */
620 char *
621 exptilde(buf, file)
622 char buf[];
623 register char *file;
624 {
625 register char *s1, *s2, *s3;
626 extern char homedir[];
627
628 if (*file != '~') {
629 strcpy(buf, file);
630 return(buf);
631 }
632 if (*++file == '\0') {
633 s2 = homedir;
634 s3 = NULL;
635 } else if (*file == '/') {
636 s2 = homedir;
637 s3 = file;
638 } else {
639 s3 = file;
640 while (*s3 && *s3 != '/')
641 s3++;
642 if (*s3 == '/')
643 *s3 = '\0';
644 else
645 s3 = NULL;
646 if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
647 if ((pw = getpwnam(file)) == NULL) {
648 error("%s: unknown user name\n", file);
649 if (s3 != NULL)
650 *s3 = '/';
651 return(NULL);
652 }
653 }
654 if (s3 != NULL)
655 *s3 = '/';
656 s2 = pw->pw_dir;
657 }
658 for (s1 = buf; *s1++ = *s2++; )
659 ;
660 s2 = --s1;
661 if (s3 != NULL) {
662 s2++;
663 while (*s1++ = *s3++)
664 ;
665 }
666 return(s2);
667 }
668