/** * @file dll/fuse/fuse_opt.c * * @copyright 2015-2019 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the GNU * General Public License version 3 as published by the Free Software * Foundation. * * Licensees holding a valid commercial license may use this software * in accordance with the commercial license agreement provided in * conjunction with the software. The terms and conditions of any such * commercial license agreement shall govern, supersede, and render * ineffective any application of the GPLv3 license to this software, * notwithstanding of any reference thereto in the software or * associated repository. */ #include /* * Define the following symbol to support escaped commas (',') during fuse_opt_parse. */ #define FSP_FUSE_OPT_PARSE_ESCAPED_COMMAS #define fsp_fuse_opt_match_none ((const char *)0) /* no option match */ #define fsp_fuse_opt_match_exact ((const char *)1) /* exact option match */ #define fsp_fuse_opt_match_next ((const char *)2) /* option match, value is next arg */ static long long strtoint(const char *p, int base, int is_signed) { long long v; int maxdig, maxalp, sign = +1; if (is_signed) { if ('+' == *p) p++; else if ('-' == *p) p++, sign = -1; } if (0 == base) { if ('0' == *p) { p++; if ('x' == *p || 'X' == *p) { p++; base = 16; } else base = 8; } else { base = 10; } } maxdig = 10 < base ? '9' : (base - 1) + '0'; maxalp = 10 < base ? (base - 1 - 10) + 'a' : 0; for (v = 0; *p; p++) { int c = *p; if ('0' <= c && c <= maxdig) v = base * v + (c - '0'); else { c |= 0x20; if ('a' <= c && c <= maxalp) v = base * v + (c - 'a') + 10; else break; } } return sign * v; } static void fsp_fuse_opt_match_templ( const char *templ, const char **pspec, const char **parg) { const char *p, *q; *pspec = 0; for (p = templ, q = *parg;; p++, q++) if ('\0' == *q) { if ('\0' == *p) *parg = fsp_fuse_opt_match_exact; else if (' ' == *p) *pspec = p + 1, *parg = fsp_fuse_opt_match_next; else *parg = fsp_fuse_opt_match_none; break; } else if ('=' == *p) { if (*q == *p) { p++, q++; if ('%' == *p || '\0' == *p) *pspec = p, *parg = q; else *parg = 0 == invariant_strcmp(q, p) ? fsp_fuse_opt_match_exact : fsp_fuse_opt_match_none; } else *parg = fsp_fuse_opt_match_none; break; } else if (' ' == *p) { *pspec = p + 1, *parg = q; break; } else if (*q != *p) { *parg = fsp_fuse_opt_match_none; break; } } static const struct fuse_opt *fsp_fuse_opt_find( const struct fuse_opt opts[], const char **pspec, const char **parg) { const struct fuse_opt *opt; const char *arg; for (opt = opts; 0 != opt->templ; opt++) { arg = *parg; fsp_fuse_opt_match_templ(opt->templ, pspec, &arg); if (fsp_fuse_opt_match_none != arg) { *parg = arg; return opt; } } return 0; } static int fsp_fuse_opt_call_proc(struct fsp_fuse_env *env, void *data, fuse_opt_proc_t proc, const char *arg, const char *argl, int key, int is_opt, struct fuse_args *outargs) { int result, len0, len1; char *fullarg = 0; if (FUSE_OPT_KEY_DISCARD == key) return 0; len0 = lstrlenA(arg); if (0 != argl && !(arg <= argl && argl < arg + len0)) { len1 = lstrlenA(argl); fullarg = env->memalloc(len0 + len1 + 1); if (0 == fullarg) return -1; memcpy(fullarg, arg, len0); memcpy(fullarg + len0, argl, len1); fullarg[len0 + len1] = '\0'; arg = fullarg; } if (FUSE_OPT_KEY_KEEP != key && 0 != proc) { result = proc(data, arg, key, outargs); if (-1 == result || 0 == result) goto exit; } if (is_opt) { if (!(3 <= outargs->argc && '-' == outargs->argv[1][0] && 'o' == outargs->argv[1][1] && '\0' == outargs->argv[1][2])) { result = fsp_fuse_opt_insert_arg(env, outargs, 1, "-o"); if (-1 == result) goto exit; result = fsp_fuse_opt_insert_arg(env, outargs, 2, ""); if (-1 == result) goto exit; } #if defined(FSP_FUSE_OPT_PARSE_ESCAPED_COMMAS) result = fsp_fuse_opt_add_opt_escaped(env, &outargs->argv[2], arg); #else result = fsp_fuse_opt_add_opt(env, &outargs->argv[2], arg); #endif if (-1 == result) goto exit; } else { result = fsp_fuse_opt_add_arg(env, outargs, arg); if (-1 == result) goto exit; } result = 0; exit: if (0 != fullarg) env->memfree(fullarg); return result; } static int fsp_fuse_opt_process_arg(struct fsp_fuse_env *env, void *data, const struct fuse_opt *opt, fuse_opt_proc_t proc, const char *spec, const char *arg, const char *argl, int is_opt, struct fuse_args *outargs) { #define VAR(data, opt, type) *(type *)((char *)(data) + (opt)->offset) if (-1L == opt->offset) return fsp_fuse_opt_call_proc(env, data, proc, arg, argl, opt->value, is_opt, outargs); else { int h, j, l, t, z; long long llv; char *s; int len; if (0 == spec || '\0' == spec[0]) { VAR(data, opt, int) = opt->value; return 0; } if ('%' != spec[0]) return -1; /* bad option template */ h = j = l = t = z = 0; for (spec++; *spec; spec++) switch (*spec) { default: case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 'm': break; case 'h': h++; break; case 'j': j++; break; case 'l': l++; break; case 'L': case 'q': l += 2; break; case 't': t++; break; case 'z': z++; break; case 'd': llv = strtoint(argl, 10, 1); goto ivar; case 'i': llv = strtoint(argl, 0, 1); goto ivar; case 'o': llv = strtoint(argl, 8, 0); goto ivar; case 'u': llv = strtoint(argl, 10, 0); goto ivar; case 'x': case 'X': llv = strtoint(argl, 16, 0); ivar: if (z) VAR(data, opt, size_t) = (size_t)llv; else if (t) VAR(data, opt, ptrdiff_t) = (ptrdiff_t)llv; else if (j) VAR(data, opt, intmax_t) = (intmax_t)llv; else if (1 == h) VAR(data, opt, short) = (short)llv; else if (2 <= h) VAR(data, opt, char) = (char)llv; else if (1 == l) { #if defined(_WIN64) /* long is 8 bytes long in Cygwin64 and 4 bytes long in Win64 */ if ('C' == env->environment) VAR(data, opt, long long) = (long long)llv; else VAR(data, opt, long) = (long)llv; #else VAR(data, opt, long) = (long)llv; #endif } else if (2 <= l) VAR(data, opt, long long) = (long long)llv; else VAR(data, opt, int) = (int)llv; return 0; case 's': case 'c': len = lstrlenA(argl); s = env->memalloc(len + 1); if (0 == s) return -1; memcpy(s, argl, len); s[len] = '\0'; VAR(data, opt, const char *) = (const char *)s; return 0; case 'a': case 'e': case 'E': case 'f': case 'g': return -1; /* no float support */ } return -1; /* bad option template */ } #undef VAR } static int fsp_fuse_opt_parse_arg(struct fsp_fuse_env *env, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc, const char *arg, const char *nextarg, int *pconsumed_nextarg, int is_opt, struct fuse_args *outargs) { const struct fuse_opt *opt; const char *spec, *argl; int processed = 0; argl = arg; opt = opts; while (0 != (opt = fsp_fuse_opt_find(opt, &spec, &argl))) { if (fsp_fuse_opt_match_exact == argl) argl = arg; else if (fsp_fuse_opt_match_next == argl) { if (0 == nextarg) return -1; /* missing argument for option */ argl = nextarg; *pconsumed_nextarg = 1; } if (-1 == fsp_fuse_opt_process_arg(env, data, opt, proc, spec, arg, argl, is_opt, outargs)) return -1; processed++; argl = arg; opt++; } if (0 != processed) return 0; return fsp_fuse_opt_call_proc(env, data, proc, arg, arg, FUSE_OPT_KEY_OPT, is_opt, outargs); } static int fsp_fuse_opt_proc0(void *data, const char *arg, int key, struct fuse_args *outargs) { return 1; } FSP_FUSE_API int fsp_fuse_opt_parse(struct fsp_fuse_env *env, struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc) { static struct fuse_args args0 = FUSE_ARGS_INIT(0, 0); static struct fuse_opt opts0[1] = { FUSE_OPT_END }; struct fuse_args outargs = FUSE_ARGS_INIT(0, 0); const char *arg; char *argcopy, *argend; int dashdash = 0, consumed_nextarg; if (0 == args) args = &args0; if (0 == opts) opts = opts0; if (0 == proc) proc = fsp_fuse_opt_proc0; if (-1 == fsp_fuse_opt_add_arg(env, &outargs, args->argv[0])) return -1; for (int argi = 1; args->argc > argi; argi++) { arg = args->argv[argi]; if ('-' == arg[0] && !dashdash) { switch (arg[1]) { case 'o': if ('\0' == arg[2]) { if (args->argc <= argi + 1) goto fail; /* missing argument for option "-o" */ arg = args->argv[++argi]; } else arg += 2; argcopy = env->memalloc(lstrlenA(arg) + 1); if (0 == argcopy) goto fail; argend = argcopy; for (;;) { #if defined(FSP_FUSE_OPT_PARSE_ESCAPED_COMMAS) if ('\\' == *arg) { arg++; *argend++ = *arg++; continue; } #endif if ('\0' == *arg || ',' == *arg) { *argend = '\0'; if (-1 == fsp_fuse_opt_parse_arg(env, data, opts, proc, argcopy, 0, 0, 1, &outargs)) { env->memfree(argcopy); goto fail; } if ('\0' == *arg) break; arg++; argend = argcopy; } else *argend++ = *arg++; } env->memfree(argcopy); break; case '-': if ('\0' == arg[2]) { if (-1 == fsp_fuse_opt_add_arg(env, &outargs, arg)) return -1; dashdash = 1; break; } /* fall through */ default: consumed_nextarg = 0; if (-1 == fsp_fuse_opt_parse_arg(env, data, opts, proc, arg, args->argv[argi + 1], &consumed_nextarg, 0, &outargs)) goto fail; if (consumed_nextarg) argi++; break; } } else if (-1 == fsp_fuse_opt_call_proc(env, data, proc, arg, arg, FUSE_OPT_KEY_NONOPT, 0, &outargs)) goto fail; } /* if "--" is the last argument, remove it (fuse_opt compatibility) */ if (0 < outargs.argc && '-' == outargs.argv[outargs.argc - 1][0] && '-' == outargs.argv[outargs.argc - 1][1] && '\0' == outargs.argv[outargs.argc - 1][2]) { env->memfree(outargs.argv[--outargs.argc]); outargs.argv[outargs.argc] = 0; } fsp_fuse_opt_free_args(env, args); memcpy(args, &outargs, sizeof outargs); return 0; fail: fsp_fuse_opt_free_args(env, &outargs); return -1; } FSP_FUSE_API int fsp_fuse_opt_add_arg(struct fsp_fuse_env *env, struct fuse_args *args, const char *arg) { return fsp_fuse_opt_insert_arg(env, args, args->argc, arg); } FSP_FUSE_API int fsp_fuse_opt_insert_arg(struct fsp_fuse_env *env, struct fuse_args *args, int pos, const char *arg) { char **argv; int argsize; if (0 == args) return -1; if (0 != args->argv && !args->allocated) return -1; if (0 > pos || pos > args->argc) return -1; argv = env->memalloc((args->argc + 2) * sizeof(char *)); if (0 == argv) return -1; argsize = lstrlenA(arg) + 1; argv[pos] = env->memalloc(argsize); if (0 == argv[pos]) { env->memfree(argv); return -1; } memcpy(argv[pos], arg, argsize); memcpy(argv, args->argv, sizeof(char *) * pos); memcpy(argv + pos + 1, args->argv + pos, sizeof(char *) * (args->argc - pos)); env->memfree(args->argv); args->argc++; args->argv = argv; argv[args->argc] = 0; args->allocated = 1; return 0; } FSP_FUSE_API void fsp_fuse_opt_free_args(struct fsp_fuse_env *env, struct fuse_args *args) { if (0 == args) return; if (args->allocated && 0 != args->argv) { for (int argi = 0; args->argc > argi; argi++) env->memfree(args->argv[argi]); env->memfree(args->argv); } args->argc = 0; args->argv = 0; args->allocated = 0; } static int fsp_fuse_opt_add_opt_internal(struct fsp_fuse_env *env, char **opts, const char *opt, int escaped) { size_t optsize, optlen; char *newopts; const char *p; optsize = 0 != *opts && '\0' != (*opts)[0] ? lstrlenA(*opts) + 1 : 0; for (p = opt, optlen = 0; *p; p++, optlen++) if (escaped && (',' == *p || '\\' == *p)) optlen++; newopts = env->memalloc(optsize + optlen + 1); if (0 == newopts) return -1; if (0 != optsize) { memcpy(newopts, *opts, optsize - 1); newopts[optsize - 1] = ','; } env->memfree(*opts); *opts = newopts; newopts += optsize; for (p = opt; *p; p++, newopts++) { if (escaped && (',' == *p || '\\' == *p)) *newopts++ = '\\'; *newopts = *p; } *newopts = '\0'; return 0; } FSP_FUSE_API int fsp_fuse_opt_add_opt(struct fsp_fuse_env *env, char **opts, const char *opt) { return fsp_fuse_opt_add_opt_internal(env, opts, opt, 0); } FSP_FUSE_API int fsp_fuse_opt_add_opt_escaped(struct fsp_fuse_env *env, char **opts, const char *opt) { return fsp_fuse_opt_add_opt_internal(env, opts, opt, 1); } FSP_FUSE_API int fsp_fuse_opt_match(struct fsp_fuse_env *env, const struct fuse_opt opts[], const char *arg) { if (0 == opts) return 0; const char *spec; return !!fsp_fuse_opt_find(opts, &spec, &arg); }