From d15d0333be6a1ca7fdd99a1881d967b6be8f387a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 9 Jan 2015 16:58:29 -0500 Subject: [PATCH] Add new function to filter fstab options This fixes parsing of options in shared/generator.c. Existing code had some issues: - it would treate whitespace and semicolons as seperators. fstab(5) is pretty clear that only commas matter. And the syntax does not allow for spaces to be inserted in the field in fstab. Whitespace might be escaped, but then it should not seperate options. Treat whitespace and semicolons as any other character. - it assumed that x-systemd.device-timeout would always be followed by "=". But this is not guaranteed, hasmntopt will return this option even if there's no value. Uninitialized memory could be read. - some error paths would log, and inconsistently, some would just return an error code. Filtering is split out to a separate function and tests are added. Similar code paths in other places are adjusted to use the new function. --- Makefile.am | 9 ++ src/core/mount.c | 24 ++--- src/core/swap.c | 71 +------------ src/fstab-generator/fstab-generator.c | 42 ++------ src/shared/fstab-util.c | 145 ++++++++++++++++++++++++++ src/shared/fstab-util.h | 34 ++++++ src/shared/generator.c | 41 ++------ src/shared/util.c | 17 --- src/shared/util.h | 2 - src/test/test-fstab-util.c | 112 ++++++++++++++++++++ 10 files changed, 326 insertions(+), 171 deletions(-) create mode 100644 src/shared/fstab-util.c create mode 100644 src/shared/fstab-util.h create mode 100644 src/test/test-fstab-util.c diff --git a/Makefile.am b/Makefile.am index 83d28773c..225938d66 100644 --- a/Makefile.am +++ b/Makefile.am @@ -769,6 +769,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/architecture.h \ src/shared/efivars.c \ src/shared/efivars.h \ + src/shared/fstab-util.c \ + src/shared/fstab-util.h \ src/shared/path-util.c \ src/shared/path-util.h \ src/shared/time-util.c \ @@ -1367,6 +1369,7 @@ tests += \ test-calendarspec \ test-strip-tab-ansi \ test-cgroup-util \ + test-fstab-util \ test-prioq \ test-fileio \ test-time \ @@ -1609,6 +1612,12 @@ test_fdset_LDADD = \ libsystemd-shared.la \ libsystemd-internal.la +test_fstab_util_SOURCES = \ + src/test/test-fstab-util.c + +test_fstab_util_LDADD = \ + libsystemd-shared.la + test_ratelimit_SOURCES = \ src/test/test-ratelimit.c diff --git a/src/core/mount.c b/src/core/mount.c index 13c634807..a551235f1 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -44,6 +44,7 @@ #include "bus-common-errors.h" #include "exit-status.h" #include "def.h" +#include "fstab-util.h" #define RETRY_UMOUNT_MAX 32 @@ -70,7 +71,7 @@ static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *user static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); static bool mount_needs_network(const char *options, const char *fstype) { - if (mount_test_option(options, "_netdev")) + if (fstab_test_option(options, "_netdev\0")) return true; if (fstype && fstype_is_network(fstype)) @@ -88,16 +89,10 @@ static bool mount_is_network(const MountParameters *p) { static bool mount_is_bind(const MountParameters *p) { assert(p); - if (mount_test_option(p->options, "bind")) + if (fstab_test_option(p->options, "bind\0" "rbind\0")) return true; - if (p->fstype && streq(p->fstype, "bind")) - return true; - - if (mount_test_option(p->options, "rbind")) - return true; - - if (p->fstype && streq(p->fstype, "rbind")) + if (p->fstype && STR_IN_SET(p->fstype, "bind", "rbind")) return true; return false; @@ -106,7 +101,7 @@ static bool mount_is_bind(const MountParameters *p) { static bool mount_is_auto(const MountParameters *p) { assert(p); - return !mount_test_option(p->options, "noauto"); + return !fstab_test_option(p->options, "noauto\0"); } static bool needs_quota(const MountParameters *p) { @@ -118,11 +113,8 @@ static bool needs_quota(const MountParameters *p) { if (mount_is_bind(p)) return false; - return mount_test_option(p->options, "usrquota") || - mount_test_option(p->options, "grpquota") || - mount_test_option(p->options, "quota") || - mount_test_option(p->options, "usrjquota") || - mount_test_option(p->options, "grpjquota"); + return fstab_test_option(p->options, + "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0"); } static void mount_init(Unit *u) { @@ -369,7 +361,7 @@ static bool should_umount(Mount *m) { return false; p = get_mount_parameters(m); - if (p && mount_test_option(p->options, "x-initrd.mount") && + if (p && fstab_test_option(p->options, "x-initrd.mount\0") && !in_initrd()) return false; diff --git a/src/core/swap.c b/src/core/swap.c index cb19d0f12..b2ebed4b7 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -41,6 +41,7 @@ #include "path-util.h" #include "virt.h" #include "udev-util.h" +#include "fstab-util.h" static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { [SWAP_DEAD] = UNIT_INACTIVE, @@ -704,71 +705,6 @@ fail: swap_enter_dead(s, SWAP_FAILURE_RESOURCES); } -static int mount_find_pri(const char *options, int *ret) { - const char *opt; - char *end; - unsigned long r; - - assert(ret); - - if (!options) - return 0; - - opt = mount_test_option(options, "pri"); - if (!opt) - return 0; - - opt += strlen("pri"); - if (*opt != '=') - return -EINVAL; - - errno = 0; - r = strtoul(opt + 1, &end, 10); - if (errno > 0) - return -errno; - - if (end == opt + 1 || (*end != ',' && *end != 0)) - return -EINVAL; - - *ret = (int) r; - return 1; -} - -static int mount_find_discard(const char *options, char **ret) { - const char *opt; - char *ans; - size_t len; - - assert(ret); - - if (!options) - return 0; - - opt = mount_test_option(options, "discard"); - if (!opt) - return 0; - - opt += strlen("discard"); - if (*opt == ',' || *opt == '\0') - ans = strdup("all"); - else { - if (*opt != '=') - return -EINVAL; - - len = strcspn(opt + 1, ","); - if (len == 0) - return -EINVAL; - - ans = strndup(opt + 1, len); - } - - if (!ans) - return -ENOMEM; - - *ret = ans; - return 1; -} - static void swap_enter_activating(Swap *s) { _cleanup_free_ char *discard = NULL; int r, priority = -1; @@ -779,11 +715,12 @@ static void swap_enter_activating(Swap *s) { s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; if (s->from_fragment) { - mount_find_discard(s->parameters_fragment.options, &discard); + fstab_filter_options(s->parameters_fragment.options, "discard\0", + NULL, &discard, NULL); priority = s->parameters_fragment.priority; if (priority < 0) - mount_find_pri(s->parameters_fragment.options, &priority); + fstab_find_pri(s->parameters_fragment.options, &priority); } r = exec_command_set(s->control_command, "/sbin/swapon", NULL); diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 496657bdc..64eed4952 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -29,6 +29,7 @@ #include "util.h" #include "unit-name.h" #include "path-util.h" +#include "fstab-util.h" #include "mount-setup.h" #include "special.h" #include "mkdir.h" @@ -47,33 +48,6 @@ static char *arg_usr_what = NULL; static char *arg_usr_fstype = NULL; static char *arg_usr_options = NULL; -static int mount_find_pri(struct mntent *me, int *ret) { - char *end, *opt; - unsigned long r; - - assert(me); - assert(ret); - - opt = hasmntopt(me, "pri"); - if (!opt) - return 0; - - opt += strlen("pri"); - if (*opt != '=') - return -EINVAL; - - errno = 0; - r = strtoul(opt + 1, &end, 10); - if (errno > 0) - return -errno; - - if (end == opt + 1 || (*end != ',' && *end != 0)) - return -EINVAL; - - *ret = (int) r; - return 1; -} - static int add_swap( const char *what, struct mntent *me, @@ -97,11 +71,9 @@ static int add_swap( return 0; } - r = mount_find_pri(me, &pri); - if (r < 0) { - log_error("Failed to parse priority"); - return r; - } + r = fstab_find_pri(me->mnt_opts, &pri); + if (r < 0) + return log_error_errno(r, "Failed to parse priority: %m"); name = unit_name_from_path(what, ".swap"); if (!name) @@ -434,8 +406,7 @@ static int add_root_mount(void) { if (!arg_root_options) opts = arg_root_rw > 0 ? "rw" : "ro"; else if (arg_root_rw >= 0 || - (!mount_test_option(arg_root_options, "ro") && - !mount_test_option(arg_root_options, "rw"))) + !fstab_test_option(arg_root_options, "ro\0" "rw\0")) opts = strappenda(arg_root_options, ",", arg_root_rw > 0 ? "rw" : "ro"); else opts = arg_root_options; @@ -492,8 +463,7 @@ static int add_usr_mount(void) { if (!arg_usr_options) opts = arg_root_rw > 0 ? "rw" : "ro"; - else if (!mount_test_option(arg_usr_options, "ro") && - !mount_test_option(arg_usr_options, "rw")) + else if (!fstab_test_option(arg_usr_options, "ro\0" "rw\0")) opts = strappenda(arg_usr_options, ",", arg_root_rw > 0 ? "rw" : "ro"); else opts = arg_usr_options; diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c new file mode 100644 index 000000000..546c81b80 --- /dev/null +++ b/src/shared/fstab-util.c @@ -0,0 +1,145 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "fstab-util.h" +#include "strv.h" +#include "util.h" + +int fstab_filter_options(const char *opts, const char *names, + const char **namefound, char **value, char **filtered) { + const char *name, *n = NULL, *x; + _cleanup_strv_free_ char **stor = NULL; + _cleanup_free_ char *v = NULL, **strv = NULL; + + assert(names && *names); + + if (!opts) + goto answer; + + /* If !value and !filtered, this function is not allowed to fail. */ + + if (!filtered) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, opts, ",", state) + NULSTR_FOREACH(name, names) { + if (l < strlen(name)) + continue; + if (!strneq(word, name, strlen(name))) + continue; + + /* we know that the string is NUL + * terminated, so *x is valid */ + x = word + strlen(name); + if (IN_SET(*x, '\0', '=', ',')) { + n = name; + if (value) { + free(v); + if (IN_SET(*x, '\0', ',')) + v = NULL; + else { + assert(*x == '='); + x++; + v = strndup(x, l - strlen(name) - 1); + if (!v) + return -ENOMEM; + } + } + } + } + } else { + char **t, **s; + + stor = strv_split(opts, ","); + if (!stor) + return -ENOMEM; + strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); + if (!strv) + return -ENOMEM; + + for (s = t = strv; *s; s++) { + NULSTR_FOREACH(name, names) { + x = startswith(*s, name); + if (x && IN_SET(*x, '\0', '=')) + goto found; + } + + *t = *s; + t++; + continue; + found: + /* Keep the last occurence found */ + n = name; + if (value) { + free(v); + if (*x == '\0') + v = NULL; + else { + assert(*x == '='); + x++; + v = strdup(x); + if (!v) + return -ENOMEM; + } + } + } + *t = NULL; + } + +answer: + if (namefound) + *namefound = n; + if (filtered) { + char *f; + + f = strv_join(strv, ","); + if (!f) + return -ENOMEM; + + *filtered = f; + } + if (value) { + *value = v; + v = NULL; + } + + return !!n; +} + +int fstab_find_pri(const char *options, int *ret) { + _cleanup_free_ char *opt = NULL; + int r; + unsigned pri; + + assert(ret); + + r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL); + if (r <= 0) + return r; + + r = safe_atou(opt, &pri); + if (r < 0) + return r; + + *ret = (int) r; + return 1; +} diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h new file mode 100644 index 000000000..39ddb71ef --- /dev/null +++ b/src/shared/fstab-util.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +int fstab_filter_options(const char *opts, const char *names, + const char **namefound, char **value, char **filtered); + +static inline bool fstab_test_option(const char *opts, const char *names) { + return !!fstab_filter_options(opts, names, NULL, NULL, NULL); +} + +int fstab_find_pri(const char *options, int *ret); diff --git a/src/shared/generator.c b/src/shared/generator.c index 465e5f6cc..4140afba8 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -28,6 +28,7 @@ #include "unit-name.h" #include "generator.h" #include "path-util.h" +#include "fstab-util.h" #include "dropin.h" int generator_write_fsck_deps( @@ -92,42 +93,16 @@ int generator_write_timeouts(const char *dir, const char *what, const char *wher * endless device timeouts for devices that show up only after * user input, like crypto devices. */ - _cleanup_free_ char *node = NULL, *unit = NULL, *t = NULL; - char *start, *timeout; + _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL; usec_t u; int r; - size_t len; - - if ((start = mount_test_option(opts, "comment=systemd.device-timeout"))) - timeout = start + 31; - else if ((start = mount_test_option(opts, "x-systemd.device-timeout"))) - timeout = start + 25; - else { - if (filtered) { - *filtered = strdup(opts ?: ""); - if (!*filtered) - return log_oom(); - } - return 0; - } - - len = strcspn(timeout, ",;" WHITESPACE); - t = strndup(timeout, len); - if (!t) - return -ENOMEM; - - if (filtered) { - char *prefix, *postfix; + r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" "x-systemd.device-timeout\0", + NULL, &timeout, filtered); + if (r <= 0) + return r; - prefix = strndupa(opts, start - opts - (start != opts)); - postfix = timeout + len + (start == opts && timeout[len] != '\0'); - *filtered = strjoin(prefix, *postfix ? postfix : NULL, NULL); - if (!*filtered) - return log_oom(); - } - - r = parse_sec(t, &u); + r = parse_sec(timeout, &u); if (r < 0) { log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); @@ -140,7 +115,7 @@ int generator_write_timeouts(const char *dir, const char *what, const char *wher unit = unit_name_from_path(node, ".device"); if (!unit) - return -ENOMEM; + return log_oom(); return write_drop_in_format(dir, unit, 50, "device-timeout", "# Automatically generated by %s\n\n" diff --git a/src/shared/util.c b/src/shared/util.c index 6520e511f..280e42b30 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -6744,23 +6744,6 @@ uint64_t physical_memory(void) { return (uint64_t) mem * (uint64_t) page_size(); } -char* mount_test_option(const char *haystack, const char *needle) { - - struct mntent me = { - .mnt_opts = (char*) haystack - }; - - assert(needle); - - /* Like glibc's hasmntopt(), but works on a string, not a - * struct mntent */ - - if (!haystack) - return NULL; - - return hasmntopt(&me, needle); -} - void hexdump(FILE *f, const void *p, size_t s) { const uint8_t *b = p; unsigned n = 0; diff --git a/src/shared/util.h b/src/shared/util.h index 141a3feab..b337249fa 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -1010,8 +1010,6 @@ const char *personality_to_string(unsigned long); uint64_t physical_memory(void); -char* mount_test_option(const char *haystack, const char *needle); - void hexdump(FILE *f, const void *p, size_t s); union file_handle_union { diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c new file mode 100644 index 000000000..ddf965dde --- /dev/null +++ b/src/test/test-fstab-util.c @@ -0,0 +1,112 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "fstab-util.h" +#include "util.h" +#include "log.h" + +/* +int fstab_filter_options(const char *opts, const char *names, + const char **namefound, char **value, char **filtered); +*/ + +static void do_fstab_filter_options(const char *opts, + const char *remove, + int r_expected, + const char *name_expected, + const char *value_expected, + const char *filtered_expected) { + + int r; + const char *name; + _cleanup_free_ char *value, *filtered; + + r = fstab_filter_options(opts, remove, &name, &value, &filtered); + log_info("\"%s\" → %d, \"%s\", \"%s\", \"%s\", expected %d, \"%s\", \"%s\", \"%s\"", + opts, r, name, value, filtered, + r_expected, name_expected, value_expected, filtered_expected ?: opts); + assert_se(r == r_expected); + assert_se(streq_ptr(name, name_expected)); + assert_se(streq_ptr(value, value_expected)); + assert_se(streq_ptr(filtered, filtered_expected ?: opts)); + + /* also test the malloc-less mode */ + r = fstab_filter_options(opts, remove, &name, NULL, NULL); + log_info("\"%s\" → %d, \"%s\", expected %d, \"%s\"", + opts, r, name, + r_expected, name_expected); + assert_se(r == r_expected); + assert_se(streq_ptr(name, name_expected)); +} + +static void test_fstab_filter_options(void) { + do_fstab_filter_options("opt=0", "opt\0x-opt\0", 1, "opt", "0", ""); + do_fstab_filter_options("opt=0", "x-opt\0opt\0", 1, "opt", "0", ""); + do_fstab_filter_options("opt", "opt\0x-opt\0", 1, "opt", NULL, ""); + do_fstab_filter_options("opt", "x-opt\0opt\0", 1, "opt", NULL, ""); + do_fstab_filter_options("x-opt", "x-opt\0opt\0", 1, "x-opt", NULL, ""); + + do_fstab_filter_options("opt=0,other", "opt\0x-opt\0", 1, "opt", "0", "other"); + do_fstab_filter_options("opt=0,other", "x-opt\0opt\0", 1, "opt", "0", "other"); + do_fstab_filter_options("opt,other", "opt\0x-opt\0", 1, "opt", NULL, "other"); + do_fstab_filter_options("opt,other", "x-opt\0opt\0", 1, "opt", NULL, "other"); + do_fstab_filter_options("x-opt,other", "opt\0x-opt\0", 1, "x-opt", NULL, "other"); + + do_fstab_filter_options("opto=0,other", "opt\0x-opt\0", 0, NULL, NULL, NULL); + do_fstab_filter_options("opto,other", "opt\0x-opt\0", 0, NULL, NULL, NULL); + do_fstab_filter_options("x-opto,other", "opt\0x-opt\0", 0, NULL, NULL, NULL); + + do_fstab_filter_options("first,opt=0", "opt\0x-opt\0", 1, "opt", "0", "first"); + do_fstab_filter_options("first=1,opt=0", "opt\0x-opt\0", 1, "opt", "0", "first=1"); + do_fstab_filter_options("first,opt=", "opt\0x-opt\0", 1, "opt", "", "first"); + do_fstab_filter_options("first=1,opt", "opt\0x-opt\0", 1, "opt", NULL, "first=1"); + do_fstab_filter_options("first=1,x-opt", "opt\0x-opt\0", 1, "x-opt", NULL, "first=1"); + + do_fstab_filter_options("first,opt=0,last=1", "opt\0x-opt\0", 1, "opt", "0", "first,last=1"); + do_fstab_filter_options("first=1,opt=0,last=2", "x-opt\0opt\0", 1, "opt", "0", "first=1,last=2"); + do_fstab_filter_options("first,opt,last", "opt\0", 1, "opt", NULL, "first,last"); + do_fstab_filter_options("first=1,opt,last", "x-opt\0opt\0", 1, "opt", NULL, "first=1,last"); + do_fstab_filter_options("first=,opt,last", "opt\0noopt\0", 1, "opt", NULL, "first=,last"); + + /* check repeated options */ + do_fstab_filter_options("first,opt=0,noopt=1,last=1", "opt\0noopt\0", 1, "noopt", "1", "first,last=1"); + do_fstab_filter_options("first=1,opt=0,last=2,opt=1", "opt\0", 1, "opt", "1", "first=1,last=2"); + do_fstab_filter_options("x-opt=0,x-opt=1", "opt\0x-opt\0", 1, "x-opt", "1", ""); + do_fstab_filter_options("opt=0,x-opt=1", "opt\0x-opt\0", 1, "x-opt", "1", ""); + + /* check that semicolons are not misinterpreted */ + do_fstab_filter_options("opt=0;", "opt\0", 1, "opt", "0;", ""); + do_fstab_filter_options("opt;=0", "x-opt\0opt\0noopt\0x-noopt\0", 0, NULL, NULL, NULL); + do_fstab_filter_options("opt;", "opt\0x-opt\0", 0, NULL, NULL, NULL); + + /* check that spaces are not misinterpreted */ + do_fstab_filter_options("opt=0 ", "opt\0", 1, "opt", "0 ", ""); + do_fstab_filter_options("opt =0", "x-opt\0opt\0noopt\0x-noopt\0", 0, NULL, NULL, NULL); + do_fstab_filter_options(" opt ", "opt\0x-opt\0", 0, NULL, NULL, NULL); + + /* check function will NULL args */ + do_fstab_filter_options(NULL, "opt\0", 0, NULL, NULL, ""); + do_fstab_filter_options("", "opt\0", 0, NULL, NULL, ""); +} + +int main(void) { + test_fstab_filter_options(); +} -- 2.30.2