429 lines
8.3 KiB
C
429 lines
8.3 KiB
C
/* $OpenBSD: fuse_opt.c,v 1.27 2022/01/16 20:06:18 naddy Exp $ */
|
|
/*
|
|
* Copyright (c) 2013 Sylvestre Gallon <ccna.syl@gmail.com>
|
|
* Copyright (c) 2013 Stefan Sperling <stsp@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "debug.h"
|
|
#include "fuse_opt.h"
|
|
#include "fuse_private.h"
|
|
|
|
#define IFUSE_OPT_DISCARD 0
|
|
#define IFUSE_OPT_KEEP 1
|
|
#define IFUSE_OPT_NEED_ANOTHER_ARG 2
|
|
|
|
static void
|
|
free_argv(char **argv, int argc)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < argc; i++)
|
|
free(argv[i]);
|
|
free(argv);
|
|
}
|
|
|
|
static int
|
|
alloc_argv(struct fuse_args *args)
|
|
{
|
|
char **argv;
|
|
int i;
|
|
|
|
assert(!args->allocated);
|
|
|
|
argv = calloc(args->argc, sizeof(*argv));
|
|
if (argv == NULL)
|
|
return (-1);
|
|
|
|
if (args->argv) {
|
|
for (i = 0; i < args->argc; i++) {
|
|
argv[i] = strdup(args->argv[i]);
|
|
if (argv[i] == NULL) {
|
|
free_argv(argv, i + 1);
|
|
return (-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
args->allocated = 1;
|
|
args->argv = argv;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Returns the number of characters that matched for bounds checking later.
|
|
*/
|
|
static size_t
|
|
match_opt(const char *templ, const char *opt)
|
|
{
|
|
size_t sep, len;
|
|
|
|
len = strlen(templ);
|
|
sep = strcspn(templ, "=");
|
|
|
|
if (sep == len)
|
|
sep = strcspn(templ, " ");
|
|
|
|
/* key=, key=%, "-k ", -k % */
|
|
if (sep < len && (templ[sep + 1] == '\0' || templ[sep + 1] == '%')) {
|
|
if (strncmp(opt, templ, sep) == 0)
|
|
return (sep);
|
|
else
|
|
return (0);
|
|
}
|
|
|
|
if (strcmp(opt, templ) == 0)
|
|
return (len);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
add_opt(char **opts, const char *opt)
|
|
{
|
|
char *new_opts;
|
|
|
|
if (*opts == NULL) {
|
|
*opts = strdup(opt);
|
|
if (*opts == NULL)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
|
|
if (asprintf(&new_opts, "%s,%s", *opts, opt) == -1)
|
|
return (-1);
|
|
|
|
free(*opts);
|
|
*opts = new_opts;
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
fuse_opt_add_opt(char **opts, const char *opt)
|
|
{
|
|
int ret;
|
|
|
|
if (opt == NULL || opt[0] == '\0')
|
|
return (-1);
|
|
|
|
ret = add_opt(opts, opt);
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
fuse_opt_add_opt_escaped(char **opts, const char *opt)
|
|
{
|
|
size_t size = 0, escaped = 0;
|
|
const char *s = opt;
|
|
char *escaped_opt, *p;
|
|
int ret;
|
|
|
|
if (opt == NULL || opt[0] == '\0')
|
|
return (-1);
|
|
|
|
while (*s) {
|
|
/* malloc(size + escaped) overflow check */
|
|
if (size >= (SIZE_MAX / 2))
|
|
return (-1);
|
|
|
|
if (*s == ',' || *s == '\\')
|
|
escaped++;
|
|
s++;
|
|
size++;
|
|
}
|
|
size++; /* trailing NUL */
|
|
|
|
if (escaped > 0) {
|
|
escaped_opt = malloc(size + escaped);
|
|
if (escaped_opt == NULL)
|
|
return (-1);
|
|
s = opt;
|
|
p = escaped_opt;
|
|
while (*s) {
|
|
switch (*s) {
|
|
case ',':
|
|
case '\\':
|
|
*p++ = '\\';
|
|
/* FALLTHROUGH */
|
|
default:
|
|
*p++ = *s++;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
} else {
|
|
escaped_opt = strdup(opt);
|
|
if (escaped_opt == NULL)
|
|
return (-1);
|
|
}
|
|
|
|
ret = add_opt(opts, escaped_opt);
|
|
free(escaped_opt);
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
fuse_opt_add_arg(struct fuse_args *args, const char *name)
|
|
{
|
|
return (fuse_opt_insert_arg(args, args->argc, name));
|
|
}
|
|
DEF(fuse_opt_add_arg);
|
|
|
|
static int
|
|
parse_opt(const struct fuse_opt *o, const char *opt, void *data,
|
|
fuse_opt_proc_t f, struct fuse_args *arg)
|
|
{
|
|
const char *val;
|
|
int ret, found;
|
|
size_t sep;
|
|
|
|
found = 0;
|
|
|
|
for(; o != NULL && o->templ; o++) {
|
|
sep = match_opt(o->templ, opt);
|
|
if (sep == 0)
|
|
continue;
|
|
|
|
found = 1;
|
|
val = opt;
|
|
|
|
/* check key=value or -p n */
|
|
if (o->templ[sep] == '=')
|
|
val = &opt[sep + 1];
|
|
else if (o->templ[sep] == ' ') {
|
|
if (sep == strlen(opt)) {
|
|
/* ask for next arg to be included */
|
|
return (IFUSE_OPT_NEED_ANOTHER_ARG);
|
|
} else if (strchr(o->templ, '%') != NULL) {
|
|
val = &opt[sep];
|
|
}
|
|
}
|
|
|
|
if (o->val == FUSE_OPT_KEY_DISCARD)
|
|
ret = IFUSE_OPT_DISCARD;
|
|
else if (o->val == FUSE_OPT_KEY_KEEP)
|
|
ret = IFUSE_OPT_KEEP;
|
|
else if (FUSE_OPT_IS_OPT_KEY(o)) {
|
|
if (f == NULL)
|
|
return (IFUSE_OPT_KEEP);
|
|
|
|
ret = f(data, val, o->val, arg);
|
|
} else if (data == NULL) {
|
|
return (-1);
|
|
} else if (strchr(o->templ, '%') == NULL) {
|
|
*((int *)(data + o->off)) = o->val;
|
|
ret = IFUSE_OPT_DISCARD;
|
|
} else if (strstr(o->templ, "%s") != NULL) {
|
|
*((char **)(data + o->off)) = strdup(val);
|
|
ret = IFUSE_OPT_DISCARD;
|
|
} else {
|
|
/* All other templates, let sscanf deal with them. */
|
|
if (sscanf(opt, o->templ, data + o->off) != 1) {
|
|
fprintf(stderr, "fuse: Invalid value %s for "
|
|
"option %s\n", val, o->templ);
|
|
return (-1);
|
|
}
|
|
ret = IFUSE_OPT_DISCARD;
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
return (ret);
|
|
|
|
if (f != NULL)
|
|
return f(data, opt, FUSE_OPT_KEY_OPT, arg);
|
|
|
|
return (IFUSE_OPT_KEEP);
|
|
}
|
|
|
|
/*
|
|
* this code is not very sexy but we are forced to follow
|
|
* the fuse api.
|
|
*
|
|
* when f() returns 1 we need to keep the arg
|
|
* when f() returns 0 we need to discard the arg
|
|
*/
|
|
int
|
|
fuse_opt_parse(struct fuse_args *args, void *data,
|
|
const struct fuse_opt *opt, fuse_opt_proc_t f)
|
|
{
|
|
struct fuse_args outargs;
|
|
const char *arg, *ap;
|
|
char *optlist, *tofree;
|
|
int ret;
|
|
int i;
|
|
|
|
if (!args || !args->argc || !args->argv)
|
|
return (0);
|
|
|
|
memset(&outargs, 0, sizeof(outargs));
|
|
fuse_opt_add_arg(&outargs, args->argv[0]);
|
|
|
|
for (i = 1; i < args->argc; i++) {
|
|
arg = args->argv[i];
|
|
ret = 0;
|
|
|
|
/* not - and not -- */
|
|
if (arg[0] != '-') {
|
|
if (f == NULL)
|
|
ret = IFUSE_OPT_KEEP;
|
|
else
|
|
ret = f(data, arg, FUSE_OPT_KEY_NONOPT, &outargs);
|
|
|
|
if (ret == IFUSE_OPT_KEEP)
|
|
fuse_opt_add_arg(&outargs, arg);
|
|
if (ret == -1)
|
|
goto err;
|
|
} else if (arg[1] == 'o') {
|
|
if (arg[2])
|
|
arg += 2; /* -ofoo,bar */
|
|
else {
|
|
if (++i >= args->argc)
|
|
goto err;
|
|
|
|
arg = args->argv[i];
|
|
}
|
|
|
|
tofree = optlist = strdup(arg);
|
|
if (optlist == NULL)
|
|
goto err;
|
|
|
|
while ((ap = strsep(&optlist, ",")) != NULL &&
|
|
ret != -1) {
|
|
ret = parse_opt(opt, ap, data, f, &outargs);
|
|
if (ret == IFUSE_OPT_KEEP) {
|
|
fuse_opt_add_arg(&outargs, "-o");
|
|
fuse_opt_add_arg(&outargs, ap);
|
|
}
|
|
}
|
|
|
|
free(tofree);
|
|
|
|
if (ret == -1)
|
|
goto err;
|
|
} else {
|
|
ret = parse_opt(opt, arg, data, f, &outargs);
|
|
|
|
if (ret == IFUSE_OPT_KEEP)
|
|
fuse_opt_add_arg(&outargs, arg);
|
|
else if (ret == IFUSE_OPT_NEED_ANOTHER_ARG) {
|
|
/* arg needs a value */
|
|
if (++i >= args->argc) {
|
|
fprintf(stderr, "fuse: missing argument after %s\n", arg);
|
|
goto err;
|
|
}
|
|
|
|
if (asprintf(&tofree, "%s%s", arg,
|
|
args->argv[i]) == -1)
|
|
goto err;
|
|
|
|
ret = parse_opt(opt, tofree, data, f, &outargs);
|
|
if (ret == IFUSE_OPT_KEEP)
|
|
fuse_opt_add_arg(&outargs, tofree);
|
|
free(tofree);
|
|
}
|
|
|
|
if (ret == -1)
|
|
goto err;
|
|
}
|
|
}
|
|
ret = 0;
|
|
|
|
err:
|
|
/* Update args */
|
|
fuse_opt_free_args(args);
|
|
args->allocated = outargs.allocated;
|
|
args->argc = outargs.argc;
|
|
args->argv = outargs.argv;
|
|
if (ret != 0)
|
|
ret = -1;
|
|
|
|
return (ret);
|
|
}
|
|
DEF(fuse_opt_parse);
|
|
|
|
int
|
|
fuse_opt_insert_arg(struct fuse_args *args, int p, const char *name)
|
|
{
|
|
char **av;
|
|
char *this_arg, *next_arg;
|
|
int i;
|
|
|
|
if (name == NULL)
|
|
return (-1);
|
|
|
|
if (!args->allocated && alloc_argv(args))
|
|
return (-1);
|
|
|
|
if (p < 0 || p > args->argc)
|
|
return (-1);
|
|
|
|
av = reallocarray(args->argv, args->argc + 2, sizeof(*av));
|
|
if (av == NULL)
|
|
return (-1);
|
|
|
|
this_arg = strdup(name);
|
|
if (this_arg == NULL) {
|
|
free(av);
|
|
return (-1);
|
|
}
|
|
|
|
args->argc++;
|
|
args->argv = av;
|
|
args->argv[args->argc] = NULL;
|
|
for (i = p; i < args->argc; i++) {
|
|
next_arg = args->argv[i];
|
|
args->argv[i] = this_arg;
|
|
this_arg = next_arg;
|
|
}
|
|
return (0);
|
|
}
|
|
DEF(fuse_opt_insert_arg);
|
|
|
|
void
|
|
fuse_opt_free_args(struct fuse_args *args)
|
|
{
|
|
if (!args->allocated)
|
|
return;
|
|
|
|
free_argv(args->argv, args->argc);
|
|
args->argv = 0;
|
|
args->argc = 0;
|
|
args->allocated = 0;
|
|
}
|
|
DEF(fuse_opt_free_args);
|
|
|
|
int
|
|
fuse_opt_match(const struct fuse_opt *opts, const char *opt)
|
|
{
|
|
const struct fuse_opt *this_opt = opts;
|
|
|
|
if (opt == NULL || opt[0] == '\0')
|
|
return (0);
|
|
|
|
while (this_opt->templ) {
|
|
if (match_opt(this_opt->templ, opt))
|
|
return (1);
|
|
this_opt++;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
DEF(fuse_opt_match);
|