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