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