Add new function to filter fstab options
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 9 Jan 2015 21:58:29 +0000 (16:58 -0500)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 12 Jan 2015 04:41:41 +0000 (23:41 -0500)
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
src/core/mount.c
src/core/swap.c
src/fstab-generator/fstab-generator.c
src/shared/fstab-util.c [new file with mode: 0644]
src/shared/fstab-util.h [new file with mode: 0644]
src/shared/generator.c
src/shared/util.c
src/shared/util.h
src/test/test-fstab-util.c [new file with mode: 0644]

index 83d2877..225938d 100644 (file)
@@ -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
 
index 13c6348..a551235 100644 (file)
@@ -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;
 
index cb19d0f..b2ebed4 100644 (file)
@@ -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);
index 496657b..64eed49 100644 (file)
@@ -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 (file)
index 0000000..546c81b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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 (file)
index 0000000..39ddb71
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+#include <stddef.h>
+
+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);
index 465e5f6..4140afb 100644 (file)
@@ -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"
index 6520e51..280e42b 100644 (file)
@@ -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;
index 141a3fe..b337249 100644 (file)
@@ -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 (file)
index 0000000..ddf965d
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+***/
+
+#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();
+}