xref: /openbsd/lib/libfuse/fuse_opt.c (revision 4cfece93)
1 /* $OpenBSD: fuse_opt.c,v 1.26 2018/05/15 11:57:32 helg Exp $ */
2 /*
3  * Copyright (c) 2013 Sylvestre Gallon <ccna.syl@gmail.com>
4  * Copyright (c) 2013 Stefan Sperling <stsp@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <assert.h>
20 #include <stdint.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "debug.h"
25 #include "fuse_opt.h"
26 #include "fuse_private.h"
27 
28 #define IFUSE_OPT_DISCARD 0
29 #define IFUSE_OPT_KEEP 1
30 #define IFUSE_OPT_NEED_ANOTHER_ARG 2
31 
32 static void
33 free_argv(char **argv, int argc)
34 {
35 	int i;
36 
37 	for (i = 0; i < argc; i++)
38 		free(argv[i]);
39 	free(argv);
40 }
41 
42 static int
43 alloc_argv(struct fuse_args *args)
44 {
45 	char **argv;
46 	int i;
47 
48 	assert(!args->allocated);
49 
50 	argv = calloc(args->argc, sizeof(*argv));
51 	if (argv == NULL)
52 		return (-1);
53 
54 	if (args->argv) {
55 		for (i = 0; i < args->argc; i++) {
56 			argv[i] = strdup(args->argv[i]);
57 			if (argv[i] == NULL) {
58 				free_argv(argv, i + 1);
59 				return (-1);
60 			}
61 		}
62 	}
63 
64 	args->allocated = 1;
65 	args->argv = argv;
66 
67 	return (0);
68 }
69 
70 /*
71  * Returns the number of characters that matched for bounds checking later.
72  */
73 static size_t
74 match_opt(const char *templ, const char *opt)
75 {
76 	size_t sep, len;
77 
78 	len = strlen(templ);
79 	sep = strcspn(templ, "=");
80 
81 	if (sep == len)
82 		sep = strcspn(templ, " ");
83 
84 	/* key=, key=%, "-k ", -k % */
85 	if (sep < len && (templ[sep + 1] == '\0' || templ[sep + 1] == '%')) {
86 		if (strncmp(opt, templ, sep) == 0)
87 			return (sep);
88 		else
89 			return (0);
90 	}
91 
92 	if (strcmp(opt, templ) == 0)
93 		return (len);
94 
95 	return (0);
96 }
97 
98 static int
99 add_opt(char **opts, const char *opt)
100 {
101 	char *new_opts;
102 
103 	if (*opts == NULL) {
104 		*opts = strdup(opt);
105 		if (*opts == NULL)
106 			return (-1);
107 		return (0);
108 	}
109 
110 	if (asprintf(&new_opts, "%s,%s", *opts, opt) == -1)
111 		return (-1);
112 
113 	free(*opts);
114 	*opts = new_opts;
115 	return (0);
116 }
117 
118 int
119 fuse_opt_add_opt(char **opts, const char *opt)
120 {
121 	int ret;
122 
123 	if (opt == NULL || opt[0] == '\0')
124 		return (-1);
125 
126 	ret = add_opt(opts, opt);
127 	return (ret);
128 }
129 
130 int
131 fuse_opt_add_opt_escaped(char **opts, const char *opt)
132 {
133 	size_t size = 0, escaped = 0;
134 	const char *s = opt;
135 	char *escaped_opt, *p;
136 	int ret;
137 
138 	if (opt == NULL || opt[0] == '\0')
139 		return (-1);
140 
141 	while (*s) {
142 		/* malloc(size + escaped) overflow check */
143 		if (size >= (SIZE_MAX / 2))
144 			return (-1);
145 
146 		if (*s == ',' || *s == '\\')
147 			escaped++;
148 		s++;
149 		size++;
150 	}
151 	size++; /* trailing NUL */
152 
153 	if (escaped > 0) {
154 		escaped_opt = malloc(size + escaped);
155 		if (escaped_opt == NULL)
156 			return (-1);
157 		s = opt;
158 		p = escaped_opt;
159 		while (*s) {
160 			switch (*s) {
161 			case ',':
162 			case '\\':
163 				*p++ = '\\';
164 				/* FALLTHROUGH */
165 			default:
166 				*p++ = *s++;
167 			}
168 		}
169 		*p = '\0';
170 	} else {
171 		escaped_opt = strdup(opt);
172 		if (escaped_opt == NULL)
173 			return (-1);
174 	}
175 
176 	ret = add_opt(opts, escaped_opt);
177 	free(escaped_opt);
178 	return (ret);
179 }
180 
181 int
182 fuse_opt_add_arg(struct fuse_args *args, const char *name)
183 {
184 	return (fuse_opt_insert_arg(args, args->argc, name));
185 }
186 DEF(fuse_opt_add_arg);
187 
188 static int
189 parse_opt(const struct fuse_opt *o, const char *opt, void *data,
190     fuse_opt_proc_t f, struct fuse_args *arg)
191 {
192 	const char *val;
193 	int keyval, ret, found;
194 	size_t sep;
195 
196 	keyval = 0;
197 	found = 0;
198 
199 	for(; o != NULL && o->templ; o++) {
200 		sep = match_opt(o->templ, opt);
201 		if (sep == 0)
202 			continue;
203 
204 		found = 1;
205 		val = opt;
206 
207 		/* check key=value or -p n */
208 		if (o->templ[sep] == '=') {
209 			keyval = 1;
210 			val = &opt[sep + 1];
211 		} else if (o->templ[sep] == ' ') {
212 			keyval = 1;
213 			if (sep == strlen(opt)) {
214 				/* ask for next arg to be included */
215 				return (IFUSE_OPT_NEED_ANOTHER_ARG);
216 			} else if (strchr(o->templ, '%') != NULL) {
217 				val = &opt[sep];
218 			}
219 		}
220 
221 		if (o->val == FUSE_OPT_KEY_DISCARD)
222 			ret = IFUSE_OPT_DISCARD;
223 		else if (o->val == FUSE_OPT_KEY_KEEP)
224 			ret = IFUSE_OPT_KEEP;
225 		else if (FUSE_OPT_IS_OPT_KEY(o)) {
226 			if (f == NULL)
227 				return (IFUSE_OPT_KEEP);
228 
229 			ret = f(data, val, o->val, arg);
230 		} else if (data == NULL) {
231 			return (-1);
232 		} else if (strchr(o->templ, '%') == NULL) {
233 			*((int *)(data + o->off)) = o->val;
234 			ret = IFUSE_OPT_DISCARD;
235 		} else if (strstr(o->templ, "%s") != NULL) {
236 			*((char **)(data + o->off)) = strdup(val);
237 			ret = IFUSE_OPT_DISCARD;
238 		} else {
239 			/* All other templates, let sscanf deal with them. */
240 			if (sscanf(opt, o->templ, data + o->off) != 1) {
241 				fprintf(stderr, "fuse: Invalid value %s for "
242 				    "option %s\n", val, o->templ);
243 				return (-1);
244 			}
245 			ret = IFUSE_OPT_DISCARD;
246 		}
247 	}
248 
249 	if (found)
250 		return (ret);
251 
252 	if (f != NULL)
253 		return f(data, opt, FUSE_OPT_KEY_OPT, arg);
254 
255 	return (IFUSE_OPT_KEEP);
256 }
257 
258 /*
259  * this code is not very sexy but we are forced to follow
260  * the fuse api.
261  *
262  * when f() returns 1 we need to keep the arg
263  * when f() returns 0 we need to discard the arg
264  */
265 int
266 fuse_opt_parse(struct fuse_args *args, void *data,
267     const struct fuse_opt *opt, fuse_opt_proc_t f)
268 {
269 	struct fuse_args outargs;
270 	const char *arg, *ap;
271 	char *optlist, *tofree;
272 	int ret;
273 	int i;
274 
275 	if (!args || !args->argc || !args->argv)
276 		return (0);
277 
278 	memset(&outargs, 0, sizeof(outargs));
279 	fuse_opt_add_arg(&outargs, args->argv[0]);
280 
281 	for (i = 1; i < args->argc; i++) {
282 		arg = args->argv[i];
283 		ret = 0;
284 
285 		/* not - and not -- */
286 		if (arg[0] != '-') {
287 			if (f == NULL)
288 				ret = IFUSE_OPT_KEEP;
289 			else
290 				ret = f(data, arg, FUSE_OPT_KEY_NONOPT, &outargs);
291 
292 			if (ret == IFUSE_OPT_KEEP)
293 				fuse_opt_add_arg(&outargs, arg);
294 			if (ret == -1)
295 				goto err;
296 		} else if (arg[1] == 'o') {
297 			if (arg[2])
298 				arg += 2;	/* -ofoo,bar */
299 			else {
300 				if (++i >= args->argc)
301 					goto err;
302 
303 				arg = args->argv[i];
304 			}
305 
306 			tofree = optlist = strdup(arg);
307 			if (optlist == NULL)
308 				goto err;
309 
310 			while ((ap = strsep(&optlist, ",")) != NULL &&
311 			    ret != -1) {
312 				ret = parse_opt(opt, ap, data, f, &outargs);
313 				if (ret == IFUSE_OPT_KEEP) {
314 					fuse_opt_add_arg(&outargs, "-o");
315 					fuse_opt_add_arg(&outargs, ap);
316 				}
317 			}
318 
319 			free(tofree);
320 
321 			if (ret == -1)
322 				goto err;
323 		} else {
324 			ret = parse_opt(opt, arg, data, f, &outargs);
325 
326 			if (ret == IFUSE_OPT_KEEP)
327 				fuse_opt_add_arg(&outargs, arg);
328 			else if (ret == IFUSE_OPT_NEED_ANOTHER_ARG) {
329 				/* arg needs a value */
330 				if (++i >= args->argc) {
331 					fprintf(stderr, "fuse: missing argument after %s\n", arg);
332 					goto err;
333 				}
334 
335 				if (asprintf(&tofree, "%s%s", arg,
336 				    args->argv[i]) == -1)
337 					goto err;
338 
339 				ret = parse_opt(opt, tofree, data, f, &outargs);
340 				if (ret == IFUSE_OPT_KEEP)
341 					fuse_opt_add_arg(&outargs, tofree);
342 				free(tofree);
343 			}
344 
345 			if (ret == -1)
346 				goto err;
347 		}
348 	}
349 	ret = 0;
350 
351 err:
352 	/* Update args */
353 	fuse_opt_free_args(args);
354 	args->allocated = outargs.allocated;
355 	args->argc = outargs.argc;
356 	args->argv = outargs.argv;
357 	if (ret != 0)
358 		ret = -1;
359 
360 	return (ret);
361 }
362 DEF(fuse_opt_parse);
363 
364 int
365 fuse_opt_insert_arg(struct fuse_args *args, int p, const char *name)
366 {
367 	char **av;
368 	char *this_arg, *next_arg;
369 	int i;
370 
371 	if (name == NULL)
372 		return (-1);
373 
374 	if (!args->allocated && alloc_argv(args))
375 		return (-1);
376 
377 	if (p < 0 || p > args->argc)
378 		return (-1);
379 
380 	av = reallocarray(args->argv, args->argc + 2, sizeof(*av));
381 	if (av == NULL)
382 		return (-1);
383 
384 	this_arg = strdup(name);
385 	if (this_arg == NULL) {
386 		free(av);
387 		return (-1);
388 	}
389 
390 	args->argc++;
391 	args->argv = av;
392 	args->argv[args->argc] = NULL;
393 	for (i = p; i < args->argc; i++) {
394 		next_arg = args->argv[i];
395 		args->argv[i] = this_arg;
396 		this_arg = next_arg;
397 	}
398 	return (0);
399 }
400 DEF(fuse_opt_insert_arg);
401 
402 void
403 fuse_opt_free_args(struct fuse_args *args)
404 {
405 	if (!args->allocated)
406 		return;
407 
408 	free_argv(args->argv, args->argc);
409 	args->argv = 0;
410 	args->argc = 0;
411 	args->allocated = 0;
412 }
413 DEF(fuse_opt_free_args);
414 
415 int
416 fuse_opt_match(const struct fuse_opt *opts, const char *opt)
417 {
418 	const struct fuse_opt *this_opt = opts;
419 
420 	if (opt == NULL || opt[0] == '\0')
421 		return (0);
422 
423 	while (this_opt->templ) {
424 		if (match_opt(this_opt->templ, opt))
425 			return (1);
426 		this_opt++;
427 	}
428 
429 	return (0);
430 }
431 DEF(fuse_opt_match);
432