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