1 #include <dirent.h>
2 #include <errno.h>
3 #include <fnmatch.h>
4 #include <glob.h>
5 #include <limits.h>
6 #include <stddef.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <sys/stat.h>
10 
11 struct match {
12     struct match* next;
13     char name[1];
14 };
15 
is_literal(const char * p,int useesc)16 static int is_literal(const char* p, int useesc) {
17     int bracket = 0;
18     for (; *p; p++) {
19         switch (*p) {
20         case '\\':
21             if (!useesc)
22                 break;
23         case '?':
24         case '*':
25             return 0;
26         case '[':
27             bracket = 1;
28             break;
29         case ']':
30             if (bracket)
31                 return 0;
32             break;
33         }
34     }
35     return 1;
36 }
37 
append(struct match ** tail,const char * name,size_t len,int mark)38 static int append(struct match** tail, const char* name, size_t len, int mark) {
39     struct match* new = malloc(sizeof(struct match) + len + 1);
40     if (!new)
41         return -1;
42     (*tail)->next = new;
43     new->next = NULL;
44     strcpy(new->name, name);
45     if (mark)
46         strcat(new->name, "/");
47     *tail = new;
48     return 0;
49 }
50 
match_in_dir(const char * d,const char * p,int flags,int (* errfunc)(const char * path,int err),struct match ** tail)51 static int match_in_dir(const char* d, const char* p, int flags,
52                         int (*errfunc)(const char* path, int err), struct match** tail) {
53     DIR* dir;
54     struct dirent* de;
55     char pat[strlen(p) + 1];
56     char* p2;
57     size_t l = strlen(d);
58     int literal;
59     int fnm_flags =
60         ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0) | ((!(flags & GLOB_PERIOD)) ? FNM_PERIOD : 0);
61     int error;
62 
63     if ((p2 = strchr(p, '/'))) {
64         strcpy(pat, p);
65         pat[p2 - p] = 0;
66         for (; *p2 == '/'; p2++)
67             ;
68         p = pat;
69     }
70     literal = is_literal(p, !(flags & GLOB_NOESCAPE));
71     if (*d == '/' && !*(d + 1))
72         l = 0;
73 
74     /* rely on opendir failing for nondirectory objects */
75     dir = opendir(*d ? d : ".");
76     error = errno;
77     if (!dir) {
78         /* this is not an error -- we let opendir call stat for us */
79         if (error == ENOTDIR)
80             return 0;
81         if (error == EACCES && !*p) {
82             struct stat st;
83             if (!stat(d, &st) && S_ISDIR(st.st_mode)) {
84                 if (append(tail, d, l, l))
85                     return GLOB_NOSPACE;
86                 return 0;
87             }
88         }
89         if (errfunc(d, error) || (flags & GLOB_ERR))
90             return GLOB_ABORTED;
91         return 0;
92     }
93     if (!*p) {
94         error = append(tail, d, l, l) ? GLOB_NOSPACE : 0;
95         closedir(dir);
96         return error;
97     }
98     while ((errno = 0, de = readdir(dir)) ? 1 : (error = errno, 0)) {
99         char namebuf[l + de->d_reclen + 2], *name = namebuf;
100         if (!literal && fnmatch(p, de->d_name, fnm_flags))
101             continue;
102         if (literal && strcmp(p, de->d_name))
103             continue;
104         if (p2 && de->d_type && !S_ISDIR(de->d_type << 12) && !S_ISLNK(de->d_type << 12))
105             continue;
106         if (*d) {
107             memcpy(name, d, l);
108             name[l] = '/';
109             strcpy(name + l + 1, de->d_name);
110         } else {
111             name = de->d_name;
112         }
113         if (p2) {
114             if ((error = match_in_dir(name, p2, flags, errfunc, tail))) {
115                 closedir(dir);
116                 return error;
117             }
118         } else {
119             int mark = 0;
120             if (flags & GLOB_MARK) {
121                 if (de->d_type && !S_ISLNK(de->d_type << 12))
122                     mark = S_ISDIR(de->d_type << 12);
123                 else {
124                     struct stat st;
125                     stat(name, &st);
126                     mark = S_ISDIR(st.st_mode);
127                 }
128             }
129             if (append(tail, name, l + de->d_reclen + 1, mark)) {
130                 closedir(dir);
131                 return GLOB_NOSPACE;
132             }
133         }
134     }
135     closedir(dir);
136     if (error && (errfunc(d, error) || (flags & GLOB_ERR)))
137         return GLOB_ABORTED;
138     return 0;
139 }
140 
ignore_err(const char * path,int err)141 static int ignore_err(const char* path, int err) {
142     return 0;
143 }
144 
freelist(struct match * head)145 static void freelist(struct match* head) {
146     struct match *match, *next;
147     for (match = head->next; match; match = next) {
148         next = match->next;
149         free(match);
150     }
151 }
152 
sort(const void * a,const void * b)153 static int sort(const void* a, const void* b) {
154     return strcmp(*(const char**)a, *(const char**)b);
155 }
156 
glob(const char * restrict pat,int flags,int (* errfunc)(const char * path,int err),glob_t * restrict g)157 int glob(const char* restrict pat, int flags, int (*errfunc)(const char* path, int err),
158          glob_t* restrict g) {
159     const char *p = pat, *d;
160     struct match head = {.next = NULL}, *tail = &head;
161     size_t cnt, i;
162     size_t offs = (flags & GLOB_DOOFFS) ? g->gl_offs : 0;
163     int error = 0;
164 
165     if (*p == '/') {
166         for (; *p == '/'; p++)
167             ;
168         d = "/";
169     } else {
170         d = "";
171     }
172 
173     if (strlen(p) > PATH_MAX)
174         return GLOB_NOSPACE;
175 
176     if (!errfunc)
177         errfunc = ignore_err;
178 
179     if (!(flags & GLOB_APPEND)) {
180         g->gl_offs = offs;
181         g->gl_pathc = 0;
182         g->gl_pathv = NULL;
183     }
184 
185     if (*p)
186         error = match_in_dir(d, p, flags, errfunc, &tail);
187     if (error == GLOB_NOSPACE) {
188         freelist(&head);
189         return error;
190     }
191 
192     for (cnt = 0, tail = head.next; tail; tail = tail->next, cnt++)
193         ;
194     if (!cnt) {
195         if (flags & GLOB_NOCHECK) {
196             tail = &head;
197             if (append(&tail, pat, strlen(pat), 0))
198                 return GLOB_NOSPACE;
199             cnt++;
200         } else
201             return GLOB_NOMATCH;
202     }
203 
204     if (flags & GLOB_APPEND) {
205         char** pathv = realloc(g->gl_pathv, (offs + g->gl_pathc + cnt + 1) * sizeof(char*));
206         if (!pathv) {
207             freelist(&head);
208             return GLOB_NOSPACE;
209         }
210         g->gl_pathv = pathv;
211         offs += g->gl_pathc;
212     } else {
213         g->gl_pathv = malloc((offs + cnt + 1) * sizeof(char*));
214         if (!g->gl_pathv) {
215             freelist(&head);
216             return GLOB_NOSPACE;
217         }
218         for (i = 0; i < offs; i++)
219             g->gl_pathv[i] = NULL;
220     }
221     for (i = 0, tail = head.next; i < cnt; tail = tail->next, i++)
222         g->gl_pathv[offs + i] = tail->name;
223     g->gl_pathv[offs + i] = NULL;
224     g->gl_pathc += cnt;
225 
226     if (!(flags & GLOB_NOSORT))
227         qsort(g->gl_pathv + offs, cnt, sizeof(char*), sort);
228 
229     return error;
230 }
231 
globfree(glob_t * g)232 void globfree(glob_t* g) {
233     size_t i;
234     for (i = 0; i < g->gl_pathc; i++)
235         free(g->gl_pathv[g->gl_offs + i] - offsetof(struct match, name));
236     free(g->gl_pathv);
237     g->gl_pathc = 0;
238     g->gl_pathv = NULL;
239 }
240