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