Commit | Line | Data |
---|---|---|
825f5713 C |
1 | /* glob.c: rc's (ugly) globber. This code is not elegant, but it works */ |
2 | ||
3 | #include <sys/types.h> | |
4 | #include <sys/stat.h> | |
5 | #include "rc.h" | |
6 | #include <dirent.h> | |
7 | ||
8 | static List *dmatch(char *, char *, char *); | |
9 | static List *doglob(char *, char *); | |
10 | static List *lglob(List *, char *, char *, size_t); | |
11 | static List *sort(List *); | |
12 | ||
13 | /* | |
14 | Matches a list of words s against a list of patterns p. Returns true iff | |
15 | a pattern in p matches a word in s. () matches (), but otherwise null | |
16 | patterns match nothing. | |
17 | */ | |
18 | ||
19 | extern bool lmatch(List *s, List *p) { | |
20 | List *q; | |
21 | int i; | |
22 | bool okay; | |
23 | if (s == NULL) { | |
24 | if (p == NULL) /* null matches null */ | |
25 | return TRUE; | |
26 | for (; p != NULL; p = p->n) { /* one or more stars match null */ | |
27 | if (*p->w != '\0') { /* the null string is a special case; it does *not* match () */ | |
28 | okay = TRUE; | |
29 | for (i = 0; p->w[i] != '\0'; i++) | |
30 | if (p->w[i] != '*' || p->m[i] != 1) { | |
31 | okay = FALSE; | |
32 | break; | |
33 | } | |
34 | if (okay) | |
35 | return TRUE; | |
36 | } | |
37 | } | |
38 | return FALSE; | |
39 | } | |
40 | for (; s != NULL; s = s->n) | |
41 | for (q = p; q != NULL; q = q->n) | |
42 | if (match(q->w, q->m, s->w)) | |
43 | return TRUE; | |
44 | return FALSE; | |
45 | } | |
46 | ||
47 | /* | |
48 | Globs a list; checks to see if each element in the list has a metacharacter. If it | |
49 | does, it is globbed, and the output is sorted. | |
50 | */ | |
51 | ||
52 | extern List *glob(List *s) { | |
53 | List *top, *r; | |
54 | bool meta; | |
55 | for (r = s, meta = FALSE; r != NULL; r = r->n) | |
56 | if (r->m != NULL) | |
57 | meta = TRUE; | |
58 | if (!meta) | |
59 | return s; /* don't copy lists with no metacharacters in them */ | |
60 | for (top = r = NULL; s != NULL; s = s->n) { | |
61 | if (s->m == NULL) { /* no metacharacters; just tack on to the return list */ | |
62 | if (top == NULL) | |
63 | top = r = nnew(List); | |
64 | else | |
65 | r = r->n = nnew(List); | |
66 | r->w = s->w; | |
67 | } else { | |
68 | if (top == NULL) | |
69 | top = r = sort(doglob(s->w, s->m)); | |
70 | else | |
71 | r->n = sort(doglob(s->w, s->m)); | |
72 | while (r->n != NULL) | |
73 | r = r->n; | |
74 | } | |
75 | } | |
76 | r->n = NULL; | |
77 | return top; | |
78 | } | |
79 | ||
80 | /* Matches a pattern p against the contents of directory d */ | |
81 | ||
82 | static List *dmatch(char *d, char *p, char *m) { | |
83 | bool matched = FALSE; | |
84 | List *top, *r; | |
85 | static DIR *dirp; | |
86 | static struct dirent *dp; | |
87 | static struct stat s; | |
88 | /* prototypes for XXXdir functions. comment out if necessary */ | |
89 | extern DIR *opendir(const char *); | |
90 | extern struct dirent *readdir(DIR *); | |
91 | /*extern int closedir(DIR *);*/ | |
92 | ||
93 | top = r = NULL; | |
94 | /* opendir succeeds on regular files on some systems, so the stat() call is necessary (sigh) */ | |
95 | if (stat(d, &s) < 0 || (s.st_mode & S_IFMT) != S_IFDIR || (dirp = opendir(d)) == NULL) | |
96 | return NULL; | |
97 | while ((dp = readdir(dirp)) != NULL) | |
98 | if ((*dp->d_name != '.' || *p == '.') && match(p, m, dp->d_name)) { /* match ^. explicitly */ | |
99 | matched = TRUE; | |
100 | if (top == NULL) | |
101 | top = r = nnew(List); | |
102 | else | |
103 | r = r->n = nnew(List); | |
104 | r->w = ncpy(dp->d_name); | |
105 | r->m = NULL; | |
106 | } | |
107 | closedir(dirp); | |
108 | if (!matched) | |
109 | return NULL; | |
110 | r->n = NULL; | |
111 | return top; | |
112 | } | |
113 | ||
114 | /* | |
115 | lglob() globs a pattern agains a list of directory roots. e.g., (/tmp /usr /bin) "*" | |
116 | will return a list with all the files in /tmp, /usr, and /bin. NULL on no match. | |
117 | slashcount indicates the number of slashes to stick between the directory and the | |
118 | matched name. e.g., for matching ////tmp/////foo* | |
119 | */ | |
120 | ||
121 | static List *lglob(List *s, char *p, char *m, size_t slashcount) { | |
122 | List *q, *r, *top, foo; | |
123 | static List slash; | |
124 | static size_t slashsize = 0; | |
125 | if (slashcount + 1 > slashsize) { | |
126 | slash.w = ealloc(slashcount + 1); | |
127 | slashsize = slashcount; | |
128 | } | |
129 | slash.w[slashcount] = '\0'; | |
130 | while (slashcount > 0) | |
131 | slash.w[--slashcount] = '/'; | |
132 | for (top = r = NULL; s != NULL; s = s->n) { | |
133 | q = dmatch(s->w, p, m); | |
134 | if (q != NULL) { | |
135 | foo.w = s->w; | |
136 | foo.m = NULL; | |
137 | foo.n = NULL; | |
138 | if (!(s->w[0] == '/' && s->w[1] == '\0')) /* need to separate */ | |
139 | q = concat(&slash, q); /* dir/name with slash */ | |
140 | q = concat(&foo, q); | |
141 | if (r == NULL) | |
142 | top = r = q; | |
143 | else | |
144 | r->n = q; | |
145 | while (r->n != NULL) | |
146 | r = r->n; | |
147 | } | |
148 | } | |
149 | return top; | |
150 | } | |
151 | ||
152 | /* | |
153 | Doglob globs a pathname in pattern form against a unix path. Returns the original | |
154 | pattern (cleaned of metacharacters) on failure, or the globbed string(s). | |
155 | */ | |
156 | ||
157 | static List *doglob(char *w, char *m) { | |
158 | static char *dir = NULL, *pattern = NULL, *metadir = NULL, *metapattern = NULL; | |
159 | static size_t dsize = 0; | |
160 | char *d, *p, *md, *mp; | |
161 | size_t psize; | |
162 | char *s = w; | |
163 | List firstdir; | |
164 | List *matched; | |
165 | if ((psize = strlen(w) + 1) > dsize || dir == NULL) { | |
166 | efree(dir); efree(pattern); efree(metadir); efree(metapattern); | |
167 | dir = ealloc(psize); | |
168 | pattern = ealloc(psize); | |
169 | metadir = ealloc(psize); | |
170 | metapattern = ealloc(psize); | |
171 | dsize = psize; | |
172 | } | |
173 | d = dir; | |
174 | p = pattern; | |
175 | md = metadir; | |
176 | mp = metapattern; | |
177 | if (*s == '/') | |
178 | while (*s == '/') | |
179 | *d++ = *s++, *md++ = *m++; | |
180 | else | |
181 | while (*s != '/' && *s != '\0') | |
182 | *d++ = *s++, *md++ = *m++; /* get first directory component */ | |
183 | *d = '\0'; | |
184 | /* | |
185 | Special case: no slashes in the pattern, i.e., open the current directory. | |
186 | Remember that w cannot consist of slashes alone (the other way *s could be | |
187 | zero) since doglob gets called iff there's a metacharacter to be matched | |
188 | */ | |
189 | if (*s == '\0') { | |
190 | matched = dmatch(".", dir, metadir); | |
191 | goto end; | |
192 | } | |
193 | if (*w == '/') { | |
194 | firstdir.w = dir; | |
195 | firstdir.m = metadir; | |
196 | firstdir.n = NULL; | |
197 | matched = &firstdir; | |
198 | } else { | |
199 | /* | |
200 | we must glob against current directory, | |
201 | since the first character is not a slash. | |
202 | */ | |
203 | matched = dmatch(".", dir, metadir); | |
204 | } | |
205 | do { | |
206 | size_t slashcount; | |
207 | SIGCHK; | |
208 | for (slashcount = 0; *s == '/'; s++, m++) | |
209 | slashcount++; /* skip slashes */ | |
210 | while (*s != '/' && *s != '\0') | |
211 | *p++ = *s++, *mp++ = *m++; /* get pattern */ | |
212 | *p = '\0'; | |
213 | matched = lglob(matched, pattern, metapattern, slashcount); | |
214 | p = pattern, mp = metapattern; | |
215 | } while (*s != '\0'); | |
216 | end: if (matched == NULL) { | |
217 | matched = nnew(List); | |
218 | matched->w = w; | |
219 | matched->m = NULL; | |
220 | matched->n = NULL; | |
221 | } | |
222 | return matched; | |
223 | } | |
224 | ||
225 | static List *sort(List *s) { | |
226 | size_t nel = listnel(s); | |
227 | if (nel > 1) { | |
228 | char **a; | |
229 | List *t; | |
230 | qsort(a = list2array(s, FALSE), nel, sizeof(char *), starstrcmp); | |
231 | for (t = s; t != NULL; t = t->n) | |
232 | t->w = *a++; | |
233 | } | |
234 | return s; | |
235 | } |