1 /*-
2 * Copyright (c) 2014
3 * Thorsten ``mirabilos'' Glaser <t.glaser@tarent.de>
4 *
5 * Provided that these terms and disclaimer and all copyright notices
6 * are retained or reproduced in an accompanying document, permission
7 * is granted to deal in this work without restriction, including un-
8 * limited rights to use, publicly perform, distribute, sell, modify,
9 * merge, give away, or sublicence.
10 *
11 * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to
12 * the utmost extent permitted by applicable law, neither express nor
13 * implied; without malicious intent or gross negligence. In no event
14 * may a licensor, author or contributor be held liable for indirect,
15 * direct, other damage, loss, or other issues arising in any way out
16 * of dealing in the work, even if advised of the possibility of such
17 * damage or existence of a defect, except proven that it results out
18 * of said person's immediate fault when using the work as intended.
19 */
20
21 #include <errno.h>
22 #include <glob.h>
23 #include <limits.h>
24 #include <paths.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include "globit.h"
31
32 /*
33 * globit_best - return the best prefix for a pattern, or the sole match
34 *
35 * Arguments:
36 * [in] const char *pattern
37 * [out] char **result
38 * [in] void (*callback)(const void *, const char **, unsigned) -- function to run in case there are multiple results (with count) and user specified parameter (first argument)
39 * [int] const void* cback_user_parm -- first parameter to callback
40 * Return value:
41 * (int) number of matches
42 * + if -1: an error occurred; *result is the error string (or NULL)
43 * + if 0: *result is unchanged
44 * + if 1: *result contains the sole match
45 * + else: *result contains the longest common prefix
46 * In all cases where it is set, the caller has to free(*result) later.
47 */
48
49 /* helper functions */
50
51 #ifndef _PATH_DEFPATH
52 #define _PATH_DEFPATH "/bin:/usr/bin:/sbin:/usr/sbin"
53 #endif
54
55 static char *
globit_escape(const char * istr)56 globit_escape(const char *istr)
57 {
58 char c, *cp, *ostr;
59 size_t i;
60
61 if (!(cp = ostr = (char*) calloc((i = strlen(istr)) + 1, 2)))
62 return (nullptr);
63
64 /* strictly spoken, newline should be single-quoted instead */
65 while (i--) {
66 if (strchr("\t\n \"#$&'()*:;<=>?[\\`{|}", (c = *istr++)))
67 *cp++ = '\\';
68 *cp++ = c;
69 }
70 *cp = '\0';
71
72 return (ostr);
73 }
74
75 static int
globit_pfxlen(char ** words,int nwords)76 globit_pfxlen(char **words, int nwords)
77 {
78 int j, prefix_len;
79 char *p;
80 int i;
81
82 prefix_len = strlen(words[0]);
83 for (i = 1; i < nwords; i++)
84 for (j = 0, p = words[i]; j < prefix_len; j++)
85 if (p[j] != words[0][j]) {
86 prefix_len = j;
87 break;
88 }
89 return (prefix_len);
90 }
91
my_glob_pattern_p(const char * pat,int quote)92 static bool my_glob_pattern_p(const char *pat, int quote)
93 {
94 for (const char *s = pat; *s; ++s) {
95 switch (*s) {
96 case '?':
97 case '*':
98 case '[':
99 case ']':
100 return true;
101 case '\\':
102 if (quote && s[1]) ++s;
103 break;
104 default: break;
105 }
106 }
107 return false;
108 }
109
110 /* main function */
111
112 int
globit_best(const char * pattern_,char ** result,void (* callback)(const void *,const char * const *,unsigned cnt),const void * cback_user_parm)113 globit_best(const char *pattern_, char **result,
114 void(*callback)(const void *, const char * const *, unsigned cnt), const void* cback_user_parm)
115 {
116 char c, *cp, **results = nullptr;
117 size_t z, nresults = 0;
118 int i, j = 0;
119 int glob_flags = GLOB_MARK |
120 #ifdef GLOB_NO_DOTDIRS
121 GLOB_NO_DOTDIRS |
122 #endif
123 GLOB_NOSORT;
124 int is_absolute = 0;
125 char *pattern, *pf = nullptr, *pp = nullptr, *pathpfx = nullptr, *pathstr = nullptr;
126 size_t pathlen = 0, joinlen;
127 glob_t glob_block;
128 const char *errstr = nullptr;
129
130 /* initialise pattern, with '*' appended unless it already has magic */
131 if (!(pattern = (char*) malloc((z = strlen(pattern_)) + 2))) {
132 *result = strdup("not enough memory");
133 return (-1);
134 }
135 memcpy(pattern, pattern_, z);
136
137 if (my_glob_pattern_p(pattern_, 1) == false)
138 pattern[z++] = '*';
139 pattern[z] = '\0';
140 cp = pattern;
141
142 /* check if pattern is absolute */
143 if (*pattern == '/') {
144 /* yes, pathname */
145 is_absolute = 1;
146 } else if (*pattern == '~') {
147 /* yes, tilde */
148 is_absolute = 2;
149 #ifdef GLOB_TILDE
150 glob_flags |= GLOB_TILDE;
151 #endif
152 /* any slash in the pattern? */
153 while (*cp && *cp != '/')
154 ++cp;
155 if (!*cp) {
156 /* no, so don't append '*' */
157 memcpy(pattern, pattern_, z + 1);
158 }
159 cp = pattern;
160 /* TODO: keep tilde unexpanded in return value (hard) */
161 } else {
162 /* no, get $PATH */
163 cp = getenv("PATH");
164 pp = pathstr = strdup(cp && *cp ? cp : _PATH_DEFPATH);
165 glob_repeatedly:
166 /* get one path element */
167 cp = pp;
168 while ((c = *cp) && c != ':')
169 ++cp;
170 *cp = '\0';
171 free(pathpfx);
172 pathpfx = strdup(cp == pp ? "." : pp);
173 *cp = c;
174 /* advance path prefix */
175 pp = cp;
176 if (c)
177 ++pp;
178 /* construct pattern for this round */
179 pathlen = strlen(pathpfx) + 1;
180 if (!(cp = globit_escape(pathpfx)))
181 goto oom;
182 free(pathpfx);
183 pathpfx = cp;
184 joinlen = strlen(pathpfx) + strlen(pattern) + 2;
185 cp = (char *) malloc(joinlen);
186 if (!cp) {
187 errstr = "could not compose path";
188 goto errout;
189 }
190 snprintf(cp, joinlen, "%s/%s", pathpfx, pattern);
191 free(pf);
192 pf = cp;
193 }
194
195 /* collect (one round of) glob results */
196 memset(&glob_block, '\0', sizeof(glob_block));
197 switch (glob(cp, glob_flags, nullptr, &glob_block)) {
198 case 0:
199 /* success */
200 if ((i = glob_block.gl_pathc) < 0) {
201 globfree(&glob_block);
202 errstr = "negative result set size";
203 goto errout;
204 }
205 break;
206 case GLOB_NOMATCH:
207 /* or close enough */
208 i = 0;
209 break;
210 case GLOB_NOSPACE:
211 errstr = "not enough space for glob";
212 if (false)
213 /* FALLTHROUGH */
214 case GLOB_ABORTED:
215 errstr = "glob was aborted due to an error";
216 if (false)
217 /* FALLTHROUGH */
218 case GLOB_NOSYS:
219 errstr = "glob library does not support requested function";
220 if (false)
221 /* FALLTHROUGH */
222 default:
223 errstr = "unknown glob error";
224 globfree(&glob_block);
225 goto errout;
226 }
227
228 /* if we have only one round, work directly on the results */
229 if (is_absolute) {
230 switch (i) {
231 case 0:
232 break;
233 case 1:
234 *result = globit_escape(glob_block.gl_pathv[0]);
235 break;
236 default:
237 if(callback) callback(cback_user_parm, glob_block.gl_pathv, i);
238 z = globit_pfxlen(glob_block.gl_pathv, i);
239 if (z == 0) {
240 i = 0;
241 break;
242 }
243 if (!(cp = strdup(glob_block.gl_pathv[0])))
244 goto oom;
245 cp[z] = '\0';
246 *result = globit_escape(cp);
247 free(cp);
248 break;
249 }
250 globfree(&glob_block);
251 ok_out:
252 if (i && !*result)
253 goto oom;
254 free(pf);
255 free(pathpfx);
256 free(pathstr);
257 free(pattern);
258 return (i);
259 }
260
261 /* otherwise, post-process the results */
262 if (i) {
263 j = 0;
264 char **r2;
265
266 if (((size_t)i > ((size_t)INT_MAX - nresults)) ||
267 ((SIZE_MAX / sizeof(char *)) < (z = nresults + i))) {
268 errstr = "too many results";
269 goto errout;
270 }
271 if ((r2 = (char**) realloc(results, z * sizeof(char *))) == nullptr) {
272 oom:
273 errstr = "not enough memory";
274 goto errout;
275 }
276 results = r2;
277 while (j < i) {
278 cp = glob_block.gl_pathv[j++];
279 if (strlen(cp) >= pathlen)
280 cp += pathlen;
281 results[nresults++] = strdup(cp);
282 }
283 }
284
285 /* next round */
286 globfree(&glob_block);
287 if (*pp)
288 goto glob_repeatedly;
289
290 /* operate on the postprocessed results */
291 switch ((i = (int)nresults)) {
292 case 0:
293 goto ok_out;
294 default:
295 if (callback) callback(cback_user_parm, results, i);
296 z = globit_pfxlen(results, i);
297 if (z == 0) {
298 i = 0;
299 goto ok_out;
300 }
301 results[0][z] = '\0';
302 /* FALLTHROUGH */
303 case 1:
304 *result = globit_escape(results[0]);
305 break;
306 }
307
308 while (nresults--)
309 free(results[nresults]);
310 free(results);
311 goto ok_out;
312
313 errout:
314 if (results) {
315 while (nresults--)
316 free(results[nresults]);
317 free(results);
318 }
319 free(pf);
320 free(pathpfx);
321 free(pathstr);
322 free(pattern);
323 *result = errstr ? strdup(errstr) : nullptr;
324 return (-1);
325 }
326