1/*
2    FUSE: Filesystem in Userspace
3    Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
4
5    This program can be distributed under the terms of the GNU LGPLv2.
6    See the file COPYING.LIB
7*/
8
9#include "config.h"
10#include "fuse_opt.h"
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <assert.h>
16
17struct fuse_opt_context {
18    void *data;
19    const struct fuse_opt *opt;
20    fuse_opt_proc_t proc;
21    int argctr;
22    int argc;
23    char **argv;
24    struct fuse_args outargs;
25    char *opts;
26    int nonopt;
27};
28
29void fuse_opt_free_args(struct fuse_args *args)
30{
31    if (args) {
32        if (args->argv && args->allocated) {
33            int i;
34            for (i = 0; i < args->argc; i++)
35                free(args->argv[i]);
36            free(args->argv);
37        }
38        args->argc = 0;
39        args->argv = NULL;
40        args->allocated = 0;
41    }
42}
43
44static int alloc_failed(void)
45{
46    fprintf(stderr, "fuse: memory allocation failed\n");
47    return -1;
48}
49
50int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
51{
52    char **newargv;
53    char *newarg;
54
55    assert(!args->argv || args->allocated);
56
57    newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
58    newarg = newargv ? strdup(arg) : NULL;
59    if (!newargv || !newarg)
60        return alloc_failed();
61
62    args->argv = newargv;
63    args->allocated = 1;
64    args->argv[args->argc++] = newarg;
65    args->argv[args->argc] = NULL;
66    return 0;
67}
68
69int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
70{
71    assert(pos <= args->argc);
72    if (fuse_opt_add_arg(args, arg) == -1)
73        return -1;
74
75    if (pos != args->argc - 1) {
76        char *newarg = args->argv[args->argc - 1];
77        memmove(&args->argv[pos + 1], &args->argv[pos],
78                sizeof(char *) * (args->argc - pos - 1));
79        args->argv[pos] = newarg;
80    }
81    return 0;
82}
83
84static int next_arg(struct fuse_opt_context *ctx, const char *opt)
85{
86    if (ctx->argctr + 1 >= ctx->argc) {
87        fprintf(stderr, "fuse: missing argument after `%s'\n", opt);
88        return -1;
89    }
90    ctx->argctr++;
91    return 0;
92}
93
94static int add_arg(struct fuse_opt_context *ctx, const char *arg)
95{
96    return fuse_opt_add_arg(&ctx->outargs, arg);
97}
98
99int fuse_opt_add_opt(char **opts, const char *opt)
100{
101    char *newopts;
102    if (!*opts)
103        newopts = strdup(opt);
104    else {
105        unsigned oldlen = strlen(*opts);
106        newopts = realloc(*opts, oldlen + 1 + strlen(opt) + 1);
107        if (newopts) {
108            newopts[oldlen] = ',';
109            strcpy(newopts + oldlen + 1, opt);
110        }
111    }
112    if (!newopts)
113        return alloc_failed();
114
115    *opts = newopts;
116    return 0;
117}
118
119static int add_opt(struct fuse_opt_context *ctx, const char *opt)
120{
121    return fuse_opt_add_opt(&ctx->opts, opt);
122}
123
124static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
125                     int iso)
126{
127    if (key == FUSE_OPT_KEY_DISCARD)
128        return 0;
129
130    if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
131        int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
132        if (res == -1 || !res)
133            return res;
134    }
135    if (iso)
136        return add_opt(ctx, arg);
137    else
138        return add_arg(ctx, arg);
139}
140
141static int match_template(const char *t, const char *arg, unsigned *sepp)
142{
143    int arglen = strlen(arg);
144    const char *sep = strchr(t, '=');
145    sep = sep ? sep : strchr(t, ' ');
146    if (sep && (!sep[1] || sep[1] == '%')) {
147        int tlen = sep - t;
148        if (sep[0] == '=')
149            tlen ++;
150        if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
151            *sepp = sep - t;
152            return 1;
153        }
154    }
155    if (strcmp(t, arg) == 0) {
156        *sepp = 0;
157        return 1;
158    }
159    return 0;
160}
161
162static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
163                                       const char *arg, unsigned *sepp)
164{
165    for (; opt && opt->templ; opt++)
166        if (match_template(opt->templ, arg, sepp))
167            return opt;
168    return NULL;
169}
170
171int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
172{
173    unsigned dummy;
174    return find_opt(opts, opt, &dummy) ? 1 : 0;
175}
176
177static int process_opt_param(void *var, const char *format, const char *param,
178                             const char *arg)
179{
180    assert(format[0] == '%');
181    if (format[1] == 's') {
182        char *copy = strdup(param);
183        if (!copy)
184            return alloc_failed();
185
186        *(char **) var = copy;
187    } else {
188        if (sscanf(param, format, var) != 1) {
189            fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg);
190            return -1;
191        }
192    }
193    return 0;
194}
195
196static int process_opt(struct fuse_opt_context *ctx,
197                       const struct fuse_opt *opt, unsigned sep,
198                       const char *arg, int iso)
199{
200    if (opt->offset == -1U) {
201        if (call_proc(ctx, arg, opt->value, iso) == -1)
202            return -1;
203    } else {
204        void *var = (char *)ctx->data + opt->offset;
205        if (sep && opt->templ[sep + 1]) {
206            const char *param = arg + sep;
207            if (opt->templ[sep] == '=')
208                param ++;
209            if (process_opt_param(var, opt->templ + sep + 1,
210                                  param, arg) == -1)
211                return -1;
212        } else
213            *(int *)var = opt->value;
214    }
215    return 0;
216}
217
218static int process_opt_sep_arg(struct fuse_opt_context *ctx,
219                               const struct fuse_opt *opt, unsigned sep,
220                               const char *arg, int iso)
221{
222    int res;
223    char *newarg;
224    char *param;
225
226    if (next_arg(ctx, arg) == -1)
227        return -1;
228
229    param = ctx->argv[ctx->argctr];
230    newarg = malloc(sep + strlen(param) + 1);
231    if (!newarg)
232        return alloc_failed();
233
234    memcpy(newarg, arg, sep);
235    strcpy(newarg + sep, param);
236    res = process_opt(ctx, opt, sep, newarg, iso);
237    free(newarg);
238
239    return res;
240}
241
242static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
243{
244    unsigned sep;
245    const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
246    if (opt) {
247        for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
248            int res;
249            if (sep && opt->templ[sep] == ' ' && !arg[sep])
250                res = process_opt_sep_arg(ctx, opt, sep, arg, iso);
251            else
252                res = process_opt(ctx, opt, sep, arg, iso);
253            if (res == -1)
254                return -1;
255        }
256        return 0;
257    } else
258        return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
259}
260
261static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
262{
263    char *sep;
264
265    do {
266        int res;
267        sep = strchr(opts, ',');
268        if (sep)
269            *sep = '\0';
270        res = process_gopt(ctx, opts, 1);
271        if (res == -1)
272            return -1;
273        opts = sep + 1;
274    } while (sep);
275
276    return 0;
277}
278
279static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
280{
281    int res;
282    char *copy;
283    const char *sep = strchr(opts, ',');
284    if (!sep)
285        return process_gopt(ctx, opts, 1);
286
287    copy = strdup(opts);
288    if (!copy) {
289        fprintf(stderr, "fuse: memory allocation failed\n");
290        return -1;
291    }
292    res = process_real_option_group(ctx, copy);
293    free(copy);
294    return res;
295}
296
297static int process_one(struct fuse_opt_context *ctx, const char *arg)
298{
299    if (ctx->nonopt || arg[0] != '-')
300        return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
301    else if (arg[1] == 'o') {
302        if (arg[2])
303            return process_option_group(ctx, arg + 2);
304        else {
305            if (next_arg(ctx, arg) == -1)
306                return -1;
307
308            return process_option_group(ctx, ctx->argv[ctx->argctr]);
309        }
310    } else if (arg[1] == '-' && !arg[2]) {
311        if (add_arg(ctx, arg) == -1)
312            return -1;
313        ctx->nonopt = ctx->outargs.argc;
314        return 0;
315    } else
316        return process_gopt(ctx, arg, 0);
317}
318
319static int opt_parse(struct fuse_opt_context *ctx)
320{
321    if (ctx->argc) {
322        if (add_arg(ctx, ctx->argv[0]) == -1)
323            return -1;
324    }
325
326    for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
327        if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
328            return -1;
329
330    if (ctx->opts) {
331        if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
332            fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
333            return -1;
334    }
335    if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc) {
336        free(ctx->outargs.argv[ctx->outargs.argc - 1]);
337        ctx->outargs.argv[--ctx->outargs.argc] = NULL;
338    }
339
340    return 0;
341}
342
343int fuse_opt_parse(struct fuse_args *args, void *data,
344                   const struct fuse_opt opts[], fuse_opt_proc_t proc)
345{
346    int res;
347    struct fuse_opt_context ctx = {
348        .data = data,
349        .opt = opts,
350        .proc = proc,
351    };
352
353    if (!args || !args->argv || !args->argc)
354        return 0;
355
356    ctx.argc = args->argc;
357    ctx.argv = args->argv;
358
359    res = opt_parse(&ctx);
360    if (res != -1) {
361        struct fuse_args tmp = *args;
362        *args = ctx.outargs;
363        ctx.outargs = tmp;
364    }
365    free(ctx.opts);
366    fuse_opt_free_args(&ctx.outargs);
367    return res;
368}
369