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