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