xref: /openbsd-src/lib/libfuse/fuse_opt.c (revision 3c787698cf534aa28878c487d517954430d81e85)
1*3c787698Snaddy /* $OpenBSD: fuse_opt.c,v 1.27 2022/01/16 20:06:18 naddy Exp $ */
288a7ba5eStedu /*
388a7ba5eStedu  * Copyright (c) 2013 Sylvestre Gallon <ccna.syl@gmail.com>
4d80670c5Ssyl  * Copyright (c) 2013 Stefan Sperling <stsp@openbsd.org>
588a7ba5eStedu  *
688a7ba5eStedu  * Permission to use, copy, modify, and distribute this software for any
788a7ba5eStedu  * purpose with or without fee is hereby granted, provided that the above
888a7ba5eStedu  * copyright notice and this permission notice appear in all copies.
988a7ba5eStedu  *
1088a7ba5eStedu  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1188a7ba5eStedu  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1288a7ba5eStedu  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1388a7ba5eStedu  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1488a7ba5eStedu  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1588a7ba5eStedu  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1688a7ba5eStedu  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1788a7ba5eStedu  */
1888a7ba5eStedu 
19d80670c5Ssyl #include <assert.h>
204239b822Smillert #include <stdint.h>
2188a7ba5eStedu #include <stdlib.h>
2288a7ba5eStedu #include <string.h>
2388a7ba5eStedu 
2488a7ba5eStedu #include "debug.h"
2588a7ba5eStedu #include "fuse_opt.h"
2688a7ba5eStedu #include "fuse_private.h"
2788a7ba5eStedu 
28315174fcShelg #define IFUSE_OPT_DISCARD 0
29315174fcShelg #define IFUSE_OPT_KEEP 1
30315174fcShelg #define IFUSE_OPT_NEED_ANOTHER_ARG 2
31315174fcShelg 
32d80670c5Ssyl static void
free_argv(char ** argv,int argc)33d80670c5Ssyl free_argv(char **argv, int argc)
34d80670c5Ssyl {
35d80670c5Ssyl 	int i;
36d80670c5Ssyl 
37d80670c5Ssyl 	for (i = 0; i < argc; i++)
38d80670c5Ssyl 		free(argv[i]);
39d80670c5Ssyl 	free(argv);
40d80670c5Ssyl }
41d80670c5Ssyl 
42d80670c5Ssyl static int
alloc_argv(struct fuse_args * args)43d80670c5Ssyl alloc_argv(struct fuse_args *args)
44d80670c5Ssyl {
45d80670c5Ssyl 	char **argv;
46d80670c5Ssyl 	int i;
47d80670c5Ssyl 
48d80670c5Ssyl 	assert(!args->allocated);
49d80670c5Ssyl 
50d80670c5Ssyl 	argv = calloc(args->argc, sizeof(*argv));
51d80670c5Ssyl 	if (argv == NULL)
52d80670c5Ssyl 		return (-1);
53d80670c5Ssyl 
54d80670c5Ssyl 	if (args->argv) {
55d80670c5Ssyl 		for (i = 0; i < args->argc; i++) {
56d80670c5Ssyl 			argv[i] = strdup(args->argv[i]);
57d80670c5Ssyl 			if (argv[i] == NULL) {
58d80670c5Ssyl 				free_argv(argv, i + 1);
59d80670c5Ssyl 				return (-1);
60d80670c5Ssyl 			}
61d80670c5Ssyl 		}
62d80670c5Ssyl 	}
63d80670c5Ssyl 
64d80670c5Ssyl 	args->allocated = 1;
65d80670c5Ssyl 	args->argv = argv;
66d80670c5Ssyl 
67d80670c5Ssyl 	return (0);
68d80670c5Ssyl }
69d80670c5Ssyl 
7060376174Shelg /*
7160376174Shelg  * Returns the number of characters that matched for bounds checking later.
7260376174Shelg  */
7360376174Shelg static size_t
match_opt(const char * templ,const char * opt)74d80670c5Ssyl match_opt(const char *templ, const char *opt)
75d80670c5Ssyl {
7660376174Shelg 	size_t sep, len;
77d80670c5Ssyl 
7860376174Shelg 	len = strlen(templ);
7960376174Shelg 	sep = strcspn(templ, "=");
80d80670c5Ssyl 
8160376174Shelg 	if (sep == len)
8260376174Shelg 		sep = strcspn(templ, " ");
8360376174Shelg 
8460376174Shelg 	/* key=, key=%, "-k ", -k % */
8560376174Shelg 	if (sep < len && (templ[sep + 1] == '\0' || templ[sep + 1] == '%')) {
8660376174Shelg 		if (strncmp(opt, templ, sep) == 0)
8760376174Shelg 			return (sep);
8860376174Shelg 		else
89d80670c5Ssyl 			return (0);
90d80670c5Ssyl 	}
91d80670c5Ssyl 
9260376174Shelg 	if (strcmp(opt, templ) == 0)
9360376174Shelg 		return (len);
9460376174Shelg 
9560376174Shelg 	return (0);
96d80670c5Ssyl }
97d80670c5Ssyl 
98d80670c5Ssyl static int
add_opt(char ** opts,const char * opt)99d80670c5Ssyl add_opt(char **opts, const char *opt)
100d80670c5Ssyl {
101d80670c5Ssyl 	char *new_opts;
102d80670c5Ssyl 
103d80670c5Ssyl 	if (*opts == NULL) {
104d80670c5Ssyl 		*opts = strdup(opt);
105d80670c5Ssyl 		if (*opts == NULL)
106d80670c5Ssyl 			return (-1);
107d80670c5Ssyl 		return (0);
108d80670c5Ssyl 	}
109d80670c5Ssyl 
110d80670c5Ssyl 	if (asprintf(&new_opts, "%s,%s", *opts, opt) == -1)
111d80670c5Ssyl 		return (-1);
112d80670c5Ssyl 
113d80670c5Ssyl 	free(*opts);
114d80670c5Ssyl 	*opts = new_opts;
115d80670c5Ssyl 	return (0);
116d80670c5Ssyl }
117d80670c5Ssyl 
118d80670c5Ssyl int
fuse_opt_add_opt(char ** opts,const char * opt)119d80670c5Ssyl fuse_opt_add_opt(char **opts, const char *opt)
120d80670c5Ssyl {
121d80670c5Ssyl 	int ret;
122d80670c5Ssyl 
123d80670c5Ssyl 	if (opt == NULL || opt[0] == '\0')
124d80670c5Ssyl 		return (-1);
125d80670c5Ssyl 
126d80670c5Ssyl 	ret = add_opt(opts, opt);
127d80670c5Ssyl 	return (ret);
128d80670c5Ssyl }
129d80670c5Ssyl 
130d80670c5Ssyl int
fuse_opt_add_opt_escaped(char ** opts,const char * opt)131d80670c5Ssyl fuse_opt_add_opt_escaped(char **opts, const char *opt)
132d80670c5Ssyl {
133d80670c5Ssyl 	size_t size = 0, escaped = 0;
134d80670c5Ssyl 	const char *s = opt;
135d80670c5Ssyl 	char *escaped_opt, *p;
136d80670c5Ssyl 	int ret;
137d80670c5Ssyl 
138d80670c5Ssyl 	if (opt == NULL || opt[0] == '\0')
139d80670c5Ssyl 		return (-1);
140d80670c5Ssyl 
141d80670c5Ssyl 	while (*s) {
142d80670c5Ssyl 		/* malloc(size + escaped) overflow check */
1434239b822Smillert 		if (size >= (SIZE_MAX / 2))
144d80670c5Ssyl 			return (-1);
145d80670c5Ssyl 
146d80670c5Ssyl 		if (*s == ',' || *s == '\\')
147d80670c5Ssyl 			escaped++;
148d80670c5Ssyl 		s++;
149d80670c5Ssyl 		size++;
150d80670c5Ssyl 	}
1515037151cSjca 	size++; /* trailing NUL */
152d80670c5Ssyl 
153d80670c5Ssyl 	if (escaped > 0) {
154d80670c5Ssyl 		escaped_opt = malloc(size + escaped);
155d80670c5Ssyl 		if (escaped_opt == NULL)
156d80670c5Ssyl 			return (-1);
157d80670c5Ssyl 		s = opt;
158d80670c5Ssyl 		p = escaped_opt;
159d80670c5Ssyl 		while (*s) {
160d80670c5Ssyl 			switch (*s) {
161d80670c5Ssyl 			case ',':
162d80670c5Ssyl 			case '\\':
163d80670c5Ssyl 				*p++ = '\\';
164d80670c5Ssyl 				/* FALLTHROUGH */
165d80670c5Ssyl 			default:
166d80670c5Ssyl 				*p++ = *s++;
167d80670c5Ssyl 			}
168d80670c5Ssyl 		}
169d80670c5Ssyl 		*p = '\0';
170d80670c5Ssyl 	} else {
171d80670c5Ssyl 		escaped_opt = strdup(opt);
172d80670c5Ssyl 		if (escaped_opt == NULL)
173d80670c5Ssyl 			return (-1);
174d80670c5Ssyl 	}
175d80670c5Ssyl 
176d80670c5Ssyl 	ret = add_opt(opts, escaped_opt);
177d80670c5Ssyl 	free(escaped_opt);
178d80670c5Ssyl 	return (ret);
179d80670c5Ssyl }
180d80670c5Ssyl 
18188a7ba5eStedu int
fuse_opt_add_arg(struct fuse_args * args,const char * name)18288a7ba5eStedu fuse_opt_add_arg(struct fuse_args *args, const char *name)
18388a7ba5eStedu {
184d80670c5Ssyl 	return (fuse_opt_insert_arg(args, args->argc, name));
18588a7ba5eStedu }
1867c0bee30Sjca DEF(fuse_opt_add_arg);
18788a7ba5eStedu 
188f8cc4b14Ssyl static int
parse_opt(const struct fuse_opt * o,const char * opt,void * data,fuse_opt_proc_t f,struct fuse_args * arg)189ec9508f9Shelg parse_opt(const struct fuse_opt *o, const char *opt, void *data,
1904823e311Sjca     fuse_opt_proc_t f, struct fuse_args *arg)
191f8cc4b14Ssyl {
192ec9508f9Shelg 	const char *val;
193*3c787698Snaddy 	int ret, found;
194ec9508f9Shelg 	size_t sep;
1951b8c4fd6Ssyl 
196ec9508f9Shelg 	found = 0;
1971b8c4fd6Ssyl 
198d997417fShelg 	for(; o != NULL && o->templ; o++) {
199ec9508f9Shelg 		sep = match_opt(o->templ, opt);
200ec9508f9Shelg 		if (sep == 0)
201ec9508f9Shelg 			continue;
202315174fcShelg 
203ec9508f9Shelg 		found = 1;
204ec9508f9Shelg 		val = opt;
205ec9508f9Shelg 
206ec9508f9Shelg 		/* check key=value or -p n */
207*3c787698Snaddy 		if (o->templ[sep] == '=')
208ec9508f9Shelg 			val = &opt[sep + 1];
209*3c787698Snaddy 		else if (o->templ[sep] == ' ') {
210ec9508f9Shelg 			if (sep == strlen(opt)) {
211315174fcShelg 				/* ask for next arg to be included */
212ec9508f9Shelg 				return (IFUSE_OPT_NEED_ANOTHER_ARG);
213315174fcShelg 			} else if (strchr(o->templ, '%') != NULL) {
214ec9508f9Shelg 				val = &opt[sep];
215315174fcShelg 			}
2161b8c4fd6Ssyl 		}
217f8cc4b14Ssyl 
218f8cc4b14Ssyl 		if (o->val == FUSE_OPT_KEY_DISCARD)
219ec9508f9Shelg 			ret = IFUSE_OPT_DISCARD;
220ec9508f9Shelg 		else if (o->val == FUSE_OPT_KEY_KEEP)
221ec9508f9Shelg 			ret = IFUSE_OPT_KEEP;
222ec9508f9Shelg 		else if (FUSE_OPT_IS_OPT_KEY(o)) {
223ec9508f9Shelg 			if (f == NULL)
224315174fcShelg 				return (IFUSE_OPT_KEEP);
225f8cc4b14Ssyl 
226ec9508f9Shelg 			ret = f(data, val, o->val, arg);
227315174fcShelg 		} else if (data == NULL) {
228315174fcShelg 			return (-1);
229315174fcShelg 		} else if (strchr(o->templ, '%') == NULL) {
230315174fcShelg 			*((int *)(data + o->off)) = o->val;
231ec9508f9Shelg 			ret = IFUSE_OPT_DISCARD;
232315174fcShelg 		} else if (strstr(o->templ, "%s") != NULL) {
233315174fcShelg 			*((char **)(data + o->off)) = strdup(val);
234ec9508f9Shelg 			ret = IFUSE_OPT_DISCARD;
235315174fcShelg 		} else {
236c32970f0Shelg 			/* All other templates, let sscanf deal with them. */
237c32970f0Shelg 			if (sscanf(opt, o->templ, data + o->off) != 1) {
238c32970f0Shelg 				fprintf(stderr, "fuse: Invalid value %s for "
239c32970f0Shelg 				    "option %s\n", val, o->templ);
240c32970f0Shelg 				return (-1);
241c32970f0Shelg 			}
242ec9508f9Shelg 			ret = IFUSE_OPT_DISCARD;
243ec9508f9Shelg 		}
2441b8c4fd6Ssyl 	}
245f8cc4b14Ssyl 
246ec9508f9Shelg 	if (found)
247ec9508f9Shelg 		return (ret);
248f8cc4b14Ssyl 
249315174fcShelg 	if (f != NULL)
250ec9508f9Shelg 		return f(data, opt, FUSE_OPT_KEY_OPT, arg);
251f8cc4b14Ssyl 
252315174fcShelg 	return (IFUSE_OPT_KEEP);
253f8cc4b14Ssyl }
254f8cc4b14Ssyl 
255f8cc4b14Ssyl /*
256f8cc4b14Ssyl  * this code is not very sexy but we are forced to follow
257f8cc4b14Ssyl  * the fuse api.
258f8cc4b14Ssyl  *
259f8cc4b14Ssyl  * when f() returns 1 we need to keep the arg
260f8cc4b14Ssyl  * when f() returns 0 we need to discard the arg
261f8cc4b14Ssyl  */
2629a9e04d1Stedu int
fuse_opt_parse(struct fuse_args * args,void * data,const struct fuse_opt * opt,fuse_opt_proc_t f)2634823e311Sjca fuse_opt_parse(struct fuse_args *args, void *data,
2644823e311Sjca     const struct fuse_opt *opt, fuse_opt_proc_t f)
26588a7ba5eStedu {
2666590e1b9Ssyl 	struct fuse_args outargs;
267315174fcShelg 	const char *arg, *ap;
268315174fcShelg 	char *optlist, *tofree;
269315174fcShelg 	int ret;
270f8cc4b14Ssyl 	int i;
27188a7ba5eStedu 
272315174fcShelg 	if (!args || !args->argc || !args->argv)
273f8cc4b14Ssyl 		return (0);
274f8cc4b14Ssyl 
275bb72b40bShelg 	memset(&outargs, 0, sizeof(outargs));
276f8cc4b14Ssyl 	fuse_opt_add_arg(&outargs, args->argv[0]);
277f8cc4b14Ssyl 
278f8cc4b14Ssyl 	for (i = 1; i < args->argc; i++) {
27988a7ba5eStedu 		arg = args->argv[i];
280315174fcShelg 		ret = 0;
28188a7ba5eStedu 
28288a7ba5eStedu 		/* not - and not -- */
28388a7ba5eStedu 		if (arg[0] != '-') {
284315174fcShelg 			if (f == NULL)
285315174fcShelg 				ret = IFUSE_OPT_KEEP;
286315174fcShelg 			else
28708d664a0Smpi 				ret = f(data, arg, FUSE_OPT_KEY_NONOPT, &outargs);
28888a7ba5eStedu 
289315174fcShelg 			if (ret == IFUSE_OPT_KEEP)
290f8cc4b14Ssyl 				fuse_opt_add_arg(&outargs, arg);
29188a7ba5eStedu 			if (ret == -1)
292f8cc4b14Ssyl 				goto err;
293f8cc4b14Ssyl 		} else if (arg[1] == 'o') {
294f8cc4b14Ssyl 			if (arg[2])
295f8cc4b14Ssyl 				arg += 2;	/* -ofoo,bar */
296315174fcShelg 			else {
297315174fcShelg 				if (++i >= args->argc)
298315174fcShelg 					goto err;
2991b8c4fd6Ssyl 
300315174fcShelg 				arg = args->argv[i];
301315174fcShelg 			}
302315174fcShelg 
303315174fcShelg 			tofree = optlist = strdup(arg);
304315174fcShelg 			if (optlist == NULL)
305315174fcShelg 				goto err;
306315174fcShelg 
307315174fcShelg 			while ((ap = strsep(&optlist, ",")) != NULL &&
308315174fcShelg 			    ret != -1) {
309315174fcShelg 				ret = parse_opt(opt, ap, data, f, &outargs);
310315174fcShelg 				if (ret == IFUSE_OPT_KEEP) {
311315174fcShelg 					fuse_opt_add_arg(&outargs, "-o");
312315174fcShelg 					fuse_opt_add_arg(&outargs, ap);
313315174fcShelg 				}
314315174fcShelg 			}
315315174fcShelg 
316315174fcShelg 			free(tofree);
3171b8c4fd6Ssyl 
3181b8c4fd6Ssyl 			if (ret == -1)
3191b8c4fd6Ssyl 				goto err;
32088a7ba5eStedu 		} else {
321f8cc4b14Ssyl 			ret = parse_opt(opt, arg, data, f, &outargs);
32288a7ba5eStedu 
323315174fcShelg 			if (ret == IFUSE_OPT_KEEP)
324315174fcShelg 				fuse_opt_add_arg(&outargs, arg);
325315174fcShelg 			else if (ret == IFUSE_OPT_NEED_ANOTHER_ARG) {
326315174fcShelg 				/* arg needs a value */
327315174fcShelg 				if (++i >= args->argc) {
328315174fcShelg 					fprintf(stderr, "fuse: missing argument after %s\n", arg);
329315174fcShelg 					goto err;
330315174fcShelg 				}
331315174fcShelg 
332315174fcShelg 				if (asprintf(&tofree, "%s%s", arg,
333315174fcShelg 				    args->argv[i]) == -1)
334315174fcShelg 					goto err;
335315174fcShelg 
336315174fcShelg 				ret = parse_opt(opt, tofree, data, f, &outargs);
337315174fcShelg 				if (ret == IFUSE_OPT_KEEP)
338315174fcShelg 					fuse_opt_add_arg(&outargs, tofree);
339315174fcShelg 				free(tofree);
340315174fcShelg 			}
341315174fcShelg 
34288a7ba5eStedu 			if (ret == -1)
343f8cc4b14Ssyl 				goto err;
344f8cc4b14Ssyl 		}
345f8cc4b14Ssyl 	}
346f8cc4b14Ssyl 	ret = 0;
347f8cc4b14Ssyl 
348f8cc4b14Ssyl err:
349f8cc4b14Ssyl 	/* Update args */
350f8cc4b14Ssyl 	fuse_opt_free_args(args);
351f8cc4b14Ssyl 	args->allocated = outargs.allocated;
352f8cc4b14Ssyl 	args->argc = outargs.argc;
353f8cc4b14Ssyl 	args->argv = outargs.argv;
354315174fcShelg 	if (ret != 0)
355315174fcShelg 		ret = -1;
356f8cc4b14Ssyl 
35788a7ba5eStedu 	return (ret);
35888a7ba5eStedu }
3597c0bee30Sjca DEF(fuse_opt_parse);
36088a7ba5eStedu 
36188a7ba5eStedu int
fuse_opt_insert_arg(struct fuse_args * args,int p,const char * name)362d80670c5Ssyl fuse_opt_insert_arg(struct fuse_args *args, int p, const char *name)
36388a7ba5eStedu {
364e76f67aaStedu 	char **av;
365d80670c5Ssyl 	char *this_arg, *next_arg;
36688a7ba5eStedu 	int i;
36788a7ba5eStedu 
368e509d4c9Sstsp 	if (name == NULL)
369d80670c5Ssyl 		return (-1);
37088a7ba5eStedu 
371d80670c5Ssyl 	if (!args->allocated && alloc_argv(args))
372d80670c5Ssyl 		return (-1);
373d80670c5Ssyl 
374d80670c5Ssyl 	if (p < 0 || p > args->argc)
375d80670c5Ssyl 		return (-1);
376d80670c5Ssyl 
377aa516fc2Sokan 	av = reallocarray(args->argv, args->argc + 2, sizeof(*av));
378e76f67aaStedu 	if (av == NULL)
37988a7ba5eStedu 		return (-1);
38088a7ba5eStedu 
381d80670c5Ssyl 	this_arg = strdup(name);
382d80670c5Ssyl 	if (this_arg == NULL) {
383d80670c5Ssyl 		free(av);
38488a7ba5eStedu 		return (-1);
38588a7ba5eStedu 	}
386d80670c5Ssyl 
387d80670c5Ssyl 	args->argc++;
388d80670c5Ssyl 	args->argv = av;
389aa516fc2Sokan 	args->argv[args->argc] = NULL;
390d80670c5Ssyl 	for (i = p; i < args->argc; i++) {
391d80670c5Ssyl 		next_arg = args->argv[i];
392d80670c5Ssyl 		args->argv[i] = this_arg;
393d80670c5Ssyl 		this_arg = next_arg;
394d80670c5Ssyl 	}
39588a7ba5eStedu 	return (0);
39688a7ba5eStedu }
3977c0bee30Sjca DEF(fuse_opt_insert_arg);
39888a7ba5eStedu 
399d80670c5Ssyl void
fuse_opt_free_args(struct fuse_args * args)40088a7ba5eStedu fuse_opt_free_args(struct fuse_args *args)
40188a7ba5eStedu {
402d80670c5Ssyl 	if (!args->allocated)
403d80670c5Ssyl 		return;
40488a7ba5eStedu 
405d80670c5Ssyl 	free_argv(args->argv, args->argc);
406d80670c5Ssyl 	args->argv = 0;
40788a7ba5eStedu 	args->argc = 0;
408d80670c5Ssyl 	args->allocated = 0;
409d80670c5Ssyl }
4107c0bee30Sjca DEF(fuse_opt_free_args);
411d80670c5Ssyl 
412d80670c5Ssyl int
fuse_opt_match(const struct fuse_opt * opts,const char * opt)413d80670c5Ssyl fuse_opt_match(const struct fuse_opt *opts, const char *opt)
414d80670c5Ssyl {
415d80670c5Ssyl 	const struct fuse_opt *this_opt = opts;
416d80670c5Ssyl 
417d80670c5Ssyl 	if (opt == NULL || opt[0] == '\0')
418d80670c5Ssyl 		return (0);
419d80670c5Ssyl 
420d80670c5Ssyl 	while (this_opt->templ) {
421d80670c5Ssyl 		if (match_opt(this_opt->templ, opt))
422d80670c5Ssyl 			return (1);
423d80670c5Ssyl 		this_opt++;
424d80670c5Ssyl 	}
42588a7ba5eStedu 
42688a7ba5eStedu 	return (0);
42788a7ba5eStedu }
4287c0bee30Sjca DEF(fuse_opt_match);
429