593bd86b958653aad7c048915ae3fa1bf9c57cb0
[dragonfly.git] / usr.bin / rdist / expand.c
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  * @(#)expand.c 8.1 (Berkeley) 6/9/93
34  * $FreeBSD: src/usr.bin/rdist/expand.c,v 1.8 1999/08/28 01:05:06 peter Exp $
35  * $DragonFly: src/usr.bin/rdist/expand.c,v 1.2 2003/06/17 04:29:30 dillon Exp $
36  */
37
38 #include "defs.h"
39
40 #define GAVSIZ  NCARGS / 6
41 #define LC '{'
42 #define RC '}'
43
44 static char     shchars[] = "${[*?";
45
46 int     which;          /* bit mask of types to expand */
47 int     eargc;          /* expanded arg count */
48 char    **eargv;        /* expanded arg vectors */
49 char    *path;
50 char    *pathp;
51 char    *lastpathp;
52 char    *tilde;         /* "~user" if not expanding tilde, else "" */
53 char    *tpathp;
54 int     nleft;
55
56 int     expany;         /* any expansions done? */
57 char    *entp;
58 char    **sortbase;
59
60 #define sort()  qsort((char *)sortbase, &eargv[eargc] - sortbase, \
61                       sizeof(*sortbase), argcmp), sortbase = &eargv[eargc]
62
63 static void     Cat __P((char *, char *));
64 static void     addpath __P((int));
65 static int      amatch __P((char *, char *));
66 static int      argcmp __P((const void *, const void *));
67 static int      execbrc __P((char *, char *));
68 static void     expsh __P((char *));
69 static void     expstr __P((char *));
70 static int      match __P((char *, char *));
71 static void     matchdir __P((char *));
72 static int      smatch __P((char *, char *));
73
74 /*
75  * Take a list of names and expand any macros, etc.
76  * wh = E_VARS if expanding variables.
77  * wh = E_SHELL if expanding shell characters.
78  * wh = E_TILDE if expanding `~'.
79  * or any of these or'ed together.
80  *
81  * Major portions of this were snarfed from csh/sh.glob.c.
82  */
83 struct namelist *
84 expand(list, wh)
85         struct namelist *list;
86         int wh;
87 {
88         register struct namelist *nl, *prev;
89         register int n;
90         char pathbuf[BUFSIZ];
91         char *argvbuf[GAVSIZ];
92
93         if (debug) {
94                 printf("expand(%p, %d)\nlist = ", list, wh);
95                 prnames(list);
96         }
97
98         if (wh == 0) {
99                 register char *cp;
100
101                 for (nl = list; nl != NULL; nl = nl->n_next)
102                         for (cp = nl->n_name; *cp; cp++)
103                                 *cp = *cp & TRIM;
104                 return(list);
105         }
106
107         which = wh;
108         path = tpathp = pathp = pathbuf;
109         *pathp = '\0';
110         lastpathp = &path[sizeof pathbuf - 2];
111         tilde = "";
112         eargc = 0;
113         eargv = sortbase = argvbuf;
114         *eargv = 0;
115         nleft = NCARGS - 4;
116         /*
117          * Walk the name list and expand names into eargv[];
118          */
119         for (nl = list; nl != NULL; nl = nl->n_next)
120                 expstr(nl->n_name);
121         /*
122          * Take expanded list of names from eargv[] and build a new list.
123          */
124         list = prev = NULL;
125         for (n = 0; n < eargc; n++) {
126                 nl = makenl(NULL);
127                 nl->n_name = eargv[n];
128                 if (prev == NULL)
129                         list = prev = nl;
130                 else {
131                         prev->n_next = nl;
132                         prev = nl;
133                 }
134         }
135         if (debug) {
136                 printf("expanded list = ");
137                 prnames(list);
138         }
139         return(list);
140 }
141
142 static void
143 expstr(s)
144         char *s;
145 {
146         register char *cp, *cp1;
147         register struct namelist *tp;
148         char *tail;
149         char buf[BUFSIZ];
150         int savec, oeargc;
151         extern char homedir[];
152
153         if (s == NULL || *s == '\0')
154                 return;
155
156         if ((which & E_VARS) && (cp = index(s, '$')) != NULL) {
157                 *cp++ = '\0';
158                 if (*cp == '\0') {
159                         yyerror("no variable name after '$'");
160                         return;
161                 }
162                 if (*cp == LC) {
163                         cp++;
164                         if ((tail = index(cp, RC)) == NULL) {
165                                 yyerror("unmatched '{'");
166                                 return;
167                         }
168                         *tail++ = savec = '\0';
169                         if (*cp == '\0') {
170                                 yyerror("no variable name after '$'");
171                                 return;
172                         }
173                 } else {
174                         tail = cp + 1;
175                         savec = *tail;
176                         *tail = '\0';
177                 }
178                 tp = lookup(cp, NULL, 0);
179                 if (savec != '\0')
180                         *tail = savec;
181                 if (tp != NULL) {
182                         for (; tp != NULL; tp = tp->n_next) {
183                                 snprintf(buf, sizeof(buf), 
184                                     "%s%s%s", s, tp->n_name, tail);
185                                 expstr(buf);
186                         }
187                         return;
188                 }
189                 snprintf(buf, sizeof(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 dirent *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                         }
516                         pathp = spathp;
517                         *pathp = '\0';
518                         return (0);
519                 }
520         }
521 }
522
523 static int
524 smatch(s, p)
525         register char *s, *p;
526 {
527         register int scc;
528         int ok, lc;
529         int c, cc;
530
531         for (;;) {
532                 scc = *s++ & TRIM;
533                 switch (c = *p++) {
534
535                 case '[':
536                         ok = 0;
537                         lc = 077777;
538                         while ((cc = *p++)) {
539                                 if (cc == ']') {
540                                         if (ok)
541                                                 break;
542                                         return (0);
543                                 }
544                                 if (cc == '-') {
545                                         if (lc <= scc && scc <= *p++)
546                                                 ok++;
547                                 } else
548                                         if (scc == (lc = cc))
549                                                 ok++;
550                         }
551                         if (cc == 0) {
552                                 yyerror("Missing ']'");
553                                 return (0);
554                         }
555                         continue;
556
557                 case '*':
558                         if (!*p)
559                                 return (1);
560                         for (s--; *s; s++)
561                                 if (smatch(s, p))
562                                         return (1);
563                         return (0);
564
565                 case '\0':
566                         return (scc == '\0');
567
568                 default:
569                         if ((c & TRIM) != scc)
570                                 return (0);
571                         continue;
572
573                 case '?':
574                         if (scc == 0)
575                                 return (0);
576                         continue;
577
578                 }
579         }
580 }
581
582 static void
583 Cat(s1, s2)
584         register char *s1, *s2;
585 {
586         int len = strlen(s1) + strlen(s2) + 1;
587         register char *s;
588
589         nleft -= len;
590         if (nleft <= 0 || ++eargc >= GAVSIZ)
591                 yyerror("Arguments too long");
592         eargv[eargc] = 0;
593         eargv[eargc - 1] = s = malloc(len);
594         if (s == NULL)
595                 fatal("ran out of memory\n");
596         while ((*s++ = *s1++ & TRIM))
597                 ;
598         s--;
599         while ((*s++ = *s2++ & TRIM))
600                 ;
601 }
602
603 static void
604 addpath(c)
605         int c;
606 {
607
608         if (pathp >= lastpathp)
609                 yyerror("Pathname too long");
610         else {
611                 *pathp++ = c & TRIM;
612                 *pathp = '\0';
613         }
614 }
615
616 /*
617  * Expand file names beginning with `~' into the
618  * user's home directory path name. Return a pointer in buf to the
619  * part corresponding to `file'.
620  */
621 char *
622 exptilde(buf, file, maxlen)
623         char buf[];
624         register char *file;
625         int maxlen;
626 {
627         register char *s1, *s2, *s3;
628         extern char homedir[];
629
630         if (strlen(file) >= maxlen)
631            return(NULL);
632         if (*file != '~') {
633                 strcpy(buf, file);
634                 return(buf);
635         }
636         if (*++file == '\0') {
637                 s2 = homedir;
638                 s3 = NULL;
639         } else if (*file == '/') {
640                 s2 = homedir;
641                 s3 = file;
642         } else {
643                 s3 = file;
644                 while (*s3 && *s3 != '/')
645                         s3++;
646                 if (*s3 == '/')
647                         *s3 = '\0';
648                 else
649                         s3 = NULL;
650                 if (pw == NULL || strcmp(pw->pw_name, file) != 0) {
651                         if ((pw = getpwnam(file)) == NULL) {
652                                 error("%s: unknown user name\n", file);
653                                 if (s3 != NULL)
654                                         *s3 = '/';
655                                 return(NULL);
656                         }
657                 }
658                 if (s3 != NULL)
659                         *s3 = '/';
660                 s2 = pw->pw_dir;
661         }
662         for (s1 = buf; (*s1++ = *s2++) && s1 < buf+maxlen; )
663                 ;
664         s2 = --s1;
665         if (s3 != NULL && s1 < buf+maxlen) {
666                 s2++;
667                 while ((*s1++ = *s3++) && s1 < buf+maxlen)
668                         ;
669         }
670         if (s1 == buf+maxlen)
671                 return(NULL);
672         return(s2);
673 }