]> git.proxmox.com Git - mirror_lxcfs.git/blobdiff - src/lxcfs.c
lxcfs: handle NULL path in lxcfs_releasedir/lxcfs_release
[mirror_lxcfs.git] / src / lxcfs.c
index cbe58c104cb5f892b307f3f78e09a02384117e0a..92ed991217c1dc069fa9e5f7b59734faa06f7449 100644 (file)
@@ -1,27 +1,14 @@
-/* lxcfs
- *
- * Copyright © 2014-2016 Canonical, Inc
- * Author: Serge Hallyn <serge.hallyn@ubuntu.com>
- *
- * See COPYING file for details.
- */
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
-#ifndef FUSE_USE_VERSION
-#define FUSE_USE_VERSION 26
-#endif
-
-#define _FILE_OFFSET_BITS 64
+#include "config.h"
 
 #include <alloca.h>
 #include <dirent.h>
 #include <dlfcn.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <fuse.h>
+#include <getopt.h>
+#include <inttypes.h>
 #include <libgen.h>
 #include <pthread.h>
 #include <sched.h>
 #include <sys/socket.h>
 #include <linux/limits.h>
 
+#include "lxcfs_fuse.h"
+
 #include "bindings.h"
-#include "config.h"
+#include "lxcfs_fuse_compat.h"
 #include "macro.h"
 #include "memory_utils.h"
 
@@ -77,6 +66,22 @@ static inline void users_unlock(void)
        unlock_mutex(&user_count_mutex);
 }
 
+/* Returns file info type of custom type declaration carried
+ * in fuse_file_info */
+static inline enum lxcfs_virt_t file_info_type(struct fuse_file_info *fi)
+{
+       struct file_info *f;
+
+       f = INTTYPE_TO_PTR(fi->fh);
+       if (!f)
+               return -1;
+
+       if (!LXCFS_TYPE_OK(f->type))
+               return -1;
+
+       return f->type;
+}
+
 static pthread_t loadavg_pid = 0;
 
 /* Returns zero on success */
@@ -117,9 +122,25 @@ static int stop_loadavg(void)
 
 static volatile sig_atomic_t need_reload;
 
+static int lxcfs_init_library(void)
+{
+       char *error;
+       void *(*__lxcfs_fuse_init)(struct fuse_conn_info * conn, void * cfg);
+
+       dlerror();
+       __lxcfs_fuse_init = (void *(*)(struct fuse_conn_info * conn, void * cfg))dlsym(dlopen_handle, "lxcfs_fuse_init");
+       error = dlerror();
+       if (error)
+               return log_error(-1, "%s - Failed to find lxcfs_fuse_init()", error);
+
+       __lxcfs_fuse_init(NULL, NULL);
+
+       return 0;
+}
+
 /* do_reload - reload the dynamic library.  Done under
  * lock and when we know the user_count was 0 */
-static void do_reload(void)
+static void do_reload(bool reinit)
 {
        int ret;
        char lxcfs_lib_path[PATH_MAX];
@@ -133,7 +154,11 @@ static void do_reload(void)
        }
 
        /* First try loading using ld.so */
+#ifdef RESOLVE_NOW
+       dlopen_handle = dlopen("liblxcfs.so", RTLD_NOW);
+#else
        dlopen_handle = dlopen("liblxcfs.so", RTLD_LAZY);
+#endif
        if (dlopen_handle) {
                lxcfs_debug("Opened liblxcfs.so");
                goto good;
@@ -145,7 +170,7 @@ static void do_reload(void)
 #else
         ret = snprintf(lxcfs_lib_path, sizeof(lxcfs_lib_path), "/usr/local/lib/lxcfs/liblxcfs.so");
 #endif
-       if (ret < 0 || ret >= sizeof(lxcfs_lib_path))
+       if (ret < 0 || (size_t)ret >= sizeof(lxcfs_lib_path))
                log_exit("Failed to create path to open liblxcfs");
 
         dlopen_handle = dlopen(lxcfs_lib_path, RTLD_LAZY);
@@ -155,6 +180,11 @@ static void do_reload(void)
                lxcfs_debug("Opened %s", lxcfs_lib_path);
 
 good:
+       /* initialize the library */
+       if (reinit && lxcfs_init_library() < 0) {
+               log_exit("Failed to initialize liblxcfs.so");
+       }
+
        if (loadavg_pid > 0)
                start_loadavg();
 
@@ -167,7 +197,7 @@ static void up_users(void)
 {
        users_lock();
        if (users_count == 0 && need_reload)
-               do_reload();
+               do_reload(true);
        users_count++;
        users_unlock();
 }
@@ -179,7 +209,7 @@ static void down_users(void)
        users_unlock();
 }
 
-static void reload_handler(int sig)
+static void sigusr1_reload(int signo, siginfo_t *info, void *extra)
 {
        need_reload = 1;
 }
@@ -291,6 +321,22 @@ static int do_cg_write(const char *path, const char *buf, size_t size,
        return __cg_write(path, buf, size, offset, fi);
 }
 
+static int do_sys_write(const char *path, const char *buf, size_t size,
+                      off_t offset, struct fuse_file_info *fi)
+{
+       char *error;
+       int (*__sys_write)(const char *path, const char *buf, size_t size,
+                         off_t offset, struct fuse_file_info *fi);
+
+       dlerror();
+       __sys_write = (int (*)(const char *, const char *, size_t, off_t, struct fuse_file_info *))dlsym(dlopen_handle, "sys_write");
+       error = dlerror();
+       if (error)
+               return log_error(-1, "%s - Failed to find sys_write()", error);
+
+       return __sys_write(path, buf, size, offset, fi);
+}
+
 static int do_cg_mkdir(const char *path, mode_t mode)
 {
        char *error;
@@ -395,6 +441,19 @@ static int do_sys_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
        return __sys_readdir(path, buf, filler, offset, fi);
 }
 
+static int do_sys_readlink(const char *path, char *buf, size_t size)
+{
+       char *error;
+       int (*__sys_readlink)(const char *path, char *buf, size_t size);
+
+       dlerror();
+       __sys_readlink = (int (*)(const char *, char *, size_t))dlsym(dlopen_handle, "sys_readlink");
+       error = dlerror();
+       if (error)
+               return log_error(-1, "%s - Failed to find sys_readlink()", error);
+
+       return __sys_readlink(path, buf, size);
+}
 
 static int do_cg_open(const char *path, struct fuse_file_info *fi)
 {
@@ -466,6 +525,20 @@ static int do_sys_open(const char *path, struct fuse_file_info *fi)
        return __sys_open(path, fi);
 }
 
+static int do_sys_opendir(const char *path, struct fuse_file_info *fi)
+{
+       char *error;
+       int (*__sys_opendir)(const char *path, struct fuse_file_info *fi);
+
+       dlerror();
+       __sys_opendir = (int (*)(const char *path, struct fuse_file_info *fi))dlsym(dlopen_handle, "sys_opendir");
+       error = dlerror();
+       if (error)
+               return log_error(-1, "%s - Failed to find sys_opendir()", error);
+
+       return __sys_opendir(path, fi);
+}
+
 static int do_sys_access(const char *path, int mode)
 {
        char *error;
@@ -564,7 +637,11 @@ static int do_sys_releasedir(const char *path, struct fuse_file_info *fi)
        return __sys_releasedir(path, fi);
 }
 
+#if HAVE_FUSE3
+static int lxcfs_getattr(const char *path, struct stat *sb, struct fuse_file_info *fi)
+#else
 static int lxcfs_getattr(const char *path, struct stat *sb)
+#endif
 {
        int ret;
        struct timespec now;
@@ -621,23 +698,32 @@ static int lxcfs_opendir(const char *path, struct fuse_file_info *fi)
        if (strcmp(path, "/proc") == 0)
                return 0;
 
-       if (strncmp(path, "/sys", 4) == 0)
-               return 0;
+       if (strncmp(path, "/sys", 4) == 0) {
+               up_users();
+               ret = do_sys_opendir(path, fi);
+               down_users();
+               return ret;
+       }
 
        return -ENOENT;
 }
 
+#if HAVE_FUSE3
+static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+                        off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
+#else
 static int lxcfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                         off_t offset, struct fuse_file_info *fi)
+#endif
 {
        int ret;
 
        if (strcmp(path, "/") == 0) {
-               if (filler(buf, ".", NULL, 0) != 0 ||
-                   filler(buf, "..", NULL, 0) != 0 ||
-                   filler(buf, "proc", NULL, 0) != 0 ||
-                   filler(buf, "sys", NULL, 0) != 0 ||
-                   filler(buf, "cgroup", NULL, 0) != 0)
+               if (dir_filler(filler, buf, ".", 0) != 0 ||
+                   dir_filler(filler, buf, "..", 0) != 0 ||
+                   dir_filler(filler, buf, "proc", 0) != 0 ||
+                   dir_filler(filler, buf, "sys", 0) != 0 ||
+                   dir_filler(filler, buf, "cgroup", 0) != 0)
                        return -ENOMEM;
 
                return 0;
@@ -701,27 +787,34 @@ static int lxcfs_access(const char *path, int mode)
 static int lxcfs_releasedir(const char *path, struct fuse_file_info *fi)
 {
        int ret;
+       enum lxcfs_virt_t type;
 
-       if (strcmp(path, "/") == 0)
-               return 0;
+       type = file_info_type(fi);
 
-       if (strncmp(path, "/cgroup", 7) == 0) {
+       if (LXCFS_TYPE_CGROUP(type)) {
                up_users();
                ret = do_cg_releasedir(path, fi);
                down_users();
                return ret;
        }
 
-       if (strcmp(path, "/proc") == 0)
-               return 0;
-
-       if (strncmp(path, "/sys", 4) == 0) {
+       if (LXCFS_TYPE_SYS(type)) {
                up_users();
                ret = do_sys_releasedir(path, fi);
                down_users();
                return ret;
        }
 
+       if (path) {
+               if (strcmp(path, "/") == 0)
+                       return 0;
+               if (strcmp(path, "/proc") == 0)
+                       return 0;
+       }
+
+       lxcfs_error("unknown file type: path=%s, type=%d, fi->fh=%" PRIu64,
+                       path, type, fi->fh);
+
        return -EINVAL;
 }
 
@@ -794,6 +887,27 @@ int lxcfs_write(const char *path, const char *buf, size_t size, off_t offset,
                return ret;
        }
 
+       if (strncmp(path, "/sys", 4) == 0) {
+               up_users();
+               ret = do_sys_write(path, buf, size, offset, fi);
+               down_users();
+               return ret;
+       }
+
+       return -EINVAL;
+}
+
+int lxcfs_readlink(const char *path, char *buf, size_t size)
+{
+       int ret;
+
+       if (strncmp(path, "/sys", 4) == 0) {
+               up_users();
+               ret = do_sys_readlink(path, buf, size);
+               down_users();
+               return ret;
+       }
+
        return -EINVAL;
 }
 
@@ -805,28 +919,34 @@ static int lxcfs_flush(const char *path, struct fuse_file_info *fi)
 static int lxcfs_release(const char *path, struct fuse_file_info *fi)
 {
        int ret;
+       enum lxcfs_virt_t type;
 
-       if (strncmp(path, "/cgroup", 7) == 0) {
+       type = file_info_type(fi);
+
+       if (LXCFS_TYPE_CGROUP(type)) {
                up_users();
                ret = do_cg_release(path, fi);
                down_users();
                return ret;
        }
 
-       if (strncmp(path, "/proc", 5) == 0) {
+       if (LXCFS_TYPE_PROC(type)) {
                up_users();
                ret = do_proc_release(path, fi);
                down_users();
                return ret;
        }
 
-       if (strncmp(path, "/sys", 4) == 0) {
+       if (LXCFS_TYPE_SYS(type)) {
                up_users();
                ret = do_sys_release(path, fi);
                down_users();
                return ret;
        }
 
+       lxcfs_error("unknown file type: path=%s, type=%d, fi->fh=%" PRIu64,
+                       path, type, fi->fh);
+
        return -EINVAL;
 }
 
@@ -849,7 +969,11 @@ int lxcfs_mkdir(const char *path, mode_t mode)
        return -EPERM;
 }
 
+#if HAVE_FUSE3
+int lxcfs_chown(const char *path, uid_t uid, gid_t gid, struct fuse_file_info *fi)
+#else
 int lxcfs_chown(const char *path, uid_t uid, gid_t gid)
+#endif
 {
        int ret;
 
@@ -874,11 +998,18 @@ int lxcfs_chown(const char *path, uid_t uid, gid_t gid)
  * really make sense for cgroups.  So just return 0 always but do
  * nothing.
  */
+#if HAVE_FUSE3
+int lxcfs_truncate(const char *path, off_t newsize, struct fuse_file_info *fi)
+#else
 int lxcfs_truncate(const char *path, off_t newsize)
+#endif
 {
        if (strncmp(path, "/cgroup", 7) == 0)
                return 0;
 
+       if (strncmp(path, "/sys", 4) == 0)
+               return 0;
+
        return -EPERM;
 }
 
@@ -896,7 +1027,11 @@ int lxcfs_rmdir(const char *path)
        return -EPERM;
 }
 
+#if HAVE_FUSE3
+int lxcfs_chmod(const char *path, mode_t mode, struct fuse_file_info *fi)
+#else
 int lxcfs_chmod(const char *path, mode_t mode)
+#endif
 {
        int ret;
 
@@ -916,6 +1051,22 @@ int lxcfs_chmod(const char *path, mode_t mode)
        return -ENOENT;
 }
 
+#if HAVE_FUSE3
+static void *lxcfs_init(struct fuse_conn_info *conn, struct fuse_config *cfg)
+#else
+static void *lxcfs_init(struct fuse_conn_info *conn)
+#endif
+{
+       if (lxcfs_init_library() < 0)
+               return NULL;
+
+#if HAVE_FUSE3
+       cfg->direct_io = 1;
+#endif
+
+       return fuse_get_context()->private_data;
+}
+
 const struct fuse_operations lxcfs_ops = {
        .access         = lxcfs_access,
        .chmod          = lxcfs_chmod,
@@ -923,6 +1074,7 @@ const struct fuse_operations lxcfs_ops = {
        .flush          = lxcfs_flush,
        .fsync          = lxcfs_fsync,
        .getattr        = lxcfs_getattr,
+       .init           = lxcfs_init,
        .mkdir          = lxcfs_mkdir,
        .open           = lxcfs_open,
        .opendir        = lxcfs_opendir,
@@ -933,115 +1085,65 @@ const struct fuse_operations lxcfs_ops = {
        .rmdir          = lxcfs_rmdir,
        .truncate       = lxcfs_truncate,
        .write          = lxcfs_write,
+       .readlink       = lxcfs_readlink,
 
        .create         = NULL,
        .destroy        = NULL,
+#if !HAVE_FUSE3
        .fgetattr       = NULL,
+#endif
        .fsyncdir       = NULL,
+#if !HAVE_FUSE3
        .ftruncate      = NULL,
        .getdir         = NULL,
+#endif
        .getxattr       = NULL,
-       .init           = NULL,
        .link           = NULL,
        .listxattr      = NULL,
        .mknod          = NULL,
-       .readlink       = NULL,
        .rename         = NULL,
        .removexattr    = NULL,
        .setxattr       = NULL,
        .statfs         = NULL,
        .symlink        = NULL,
        .unlink         = NULL,
+#if !HAVE_FUSE3
        .utime          = NULL,
+#endif
 };
 
-static void usage()
+static void usage(void)
 {
        lxcfs_info("Usage: lxcfs <directory>\n");
-       lxcfs_info("lxcfs set up fuse- and cgroup-based virtualizing filesystem\n");
+       lxcfs_info("lxcfs is a FUSE-based proc, sys and cgroup virtualizing filesystem\n");
        lxcfs_info("Options :");
-       lxcfs_info("-d, --debug                 Run lxcfs with debugging enabled");
-       lxcfs_info("--disable-cfs               Disable cpu virtualization via cpu shares");
-       lxcfs_info("-f, --foreground            Run lxcfs in the foreground");
-       lxcfs_info("-n, --help                  Print help");
-       lxcfs_info("-l, --enable-loadavg        Enable loadavg virtualization");
-       lxcfs_info("-o                          Options to pass directly through fuse");
-       lxcfs_info("-p, --pidfile=FILE          Path to use for storing lxcfs pid");
-       lxcfs_info("                            Default pidfile is %s/lxcfs.pid", RUNTIME_PATH);
-       lxcfs_info("-u, --disable-swap          Disable swap virtualization");
-       lxcfs_info("-v, --version               Print lxcfs version");
-       lxcfs_info("--enable-pidfd              Use pidfd for process tracking");
+       lxcfs_info("  -d, --debug          Run lxcfs with debugging enabled");
+       lxcfs_info("  -f, --foreground     Run lxcfs in the foreground");
+       lxcfs_info("  -n, --help           Print help");
+       lxcfs_info("  -l, --enable-loadavg Enable loadavg virtualization");
+       lxcfs_info("  -o                   Options to pass directly through fuse");
+       lxcfs_info("  -p, --pidfile=FILE   Path to use for storing lxcfs pid");
+       lxcfs_info("                       Default pidfile is %s/lxcfs.pid", RUNTIME_PATH);
+       lxcfs_info("  -u, --disable-swap   Disable swap virtualization");
+       lxcfs_info("  -v, --version        Print lxcfs version");
+       lxcfs_info("  --enable-cfs         Enable CPU virtualization via CPU shares");
+       lxcfs_info("  --enable-pidfd       Use pidfd for process tracking");
        exit(EXIT_FAILURE);
 }
 
-static inline bool is_help(char *w)
-{
-       return strcmp(w, "-h") == 0 ||
-              strcmp(w, "--help") == 0 ||
-              strcmp(w, "-help") == 0 ||
-              strcmp(w, "help") == 0;
-}
-
-static inline bool is_version(char *w)
-{
-       return strcmp(w, "-v") == 0 ||
-              strcmp(w, "--version") == 0 ||
-              strcmp(w, "-version") == 0 ||
-              strcmp(w, "version") == 0;
-}
-
-static bool swallow_arg(int *argcp, char *argv[], char *which)
-{
-       for (int i = 1; argv[i]; i++) {
-               if (strcmp(argv[i], which) != 0)
-                       continue;
-
-               for (; argv[i]; i++)
-                       argv[i] = argv[i + 1];
-
-               (*argcp)--;
-               return true;
-       }
-
-       return false;
-}
-
-static bool swallow_option(int *argcp, char *argv[], char *opt, char **v)
-{
-       for (int i = 1; argv[i]; i++) {
-               if (!argv[i + 1])
-                       continue;
-
-               if (strcmp(argv[i], opt) != 0)
-                       continue;
-
-               do {
-                       *v = strdup(argv[i + 1]);
-               } while (!*v);
-
-               for (; argv[i + 1]; i++)
-                       argv[i] = argv[i + 2];
-
-               (*argcp) -= 2;
-               return true;
-       }
-
-       return false;
-}
-
 static int set_pidfile(char *pidfile)
 {
-       __do_close_prot_errno int fd = -EBADF;
+       __do_close int fd = -EBADF;
        char buf[INTTYPE_TO_STRLEN(long)];
        int ret;
        struct flock fl = {
-               fl.l_type       = F_WRLCK,
-               fl.l_whence     = SEEK_SET,
-               fl.l_start      = 0,
-               fl.l_len        = 0,
+               .l_type         = F_WRLCK,
+               .l_whence       = SEEK_SET,
+               .l_start        = 0,
+               .l_len          = 0,
        };
 
-       fd = open(pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+       fd = open(pidfile, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | O_CLOEXEC);
        if (fd < 0)
                return log_error(-1, "Could not open pidfile %s: %m", pidfile);
 
@@ -1055,7 +1157,7 @@ static int set_pidfile(char *pidfile)
                return log_error(-1, "Error truncating PID file '%s': %m", pidfile);
 
        ret = snprintf(buf, sizeof(buf), "%ld\n", (long)getpid());
-       if (ret < 0 || ret >= sizeof(buf))
+       if (ret < 0 || (size_t)ret >= sizeof(buf))
                return log_error(-1, "Failed to convert pid to string %m");
 
        if (write(fd, buf, ret) != ret)
@@ -1064,20 +1166,76 @@ static int set_pidfile(char *pidfile)
        return move_fd(fd);
 }
 
+static const struct option long_options[] = {
+       {"debug",               no_argument,            0,      'd'     },
+       {"disable-swap",        no_argument,            0,      'u'     },
+       {"enable-loadavg",      no_argument,            0,      'l'     },
+       {"foreground",          no_argument,            0,      'f'     },
+       {"help",                no_argument,            0,      'h'     },
+       {"version",             no_argument,            0,      'v'     },
+
+       {"enable-cfs",          no_argument,            0,        0     },
+       {"enable-pidfd",        no_argument,            0,        0     },
+
+       {"pidfile",             required_argument,      0,      'p'     },
+       {                                                               },
+};
+
+static int append_comma_separate(char **s, const char *append)
+{
+       int ret;
+       char *news;
+       size_t append_len, len;
+
+       if (!append)
+               return 0;
+
+       append_len = strlen(append);
+       if (!append_len)
+               return 0;
+
+       if (*s) {
+               len = strlen(*s);
+               news = realloc(*s, len + append_len + 2);
+       } else {
+               len = 0;
+               news = realloc(NULL, append_len + 1);
+       }
+       if (!news)
+               return -ENOMEM;
+
+       if (*s)
+               ret = snprintf(news + len, append_len + 2, ",%s", append);
+       else
+               ret = snprintf(news, append_len + 1, "%s", append);
+       if (ret < 0)
+               return -EIO;
+
+       *s = news;
+       return 0;
+}
+
 int main(int argc, char *argv[])
 {
-       __do_close_prot_errno int pidfile_fd = -EBADF;
+       int pidfile_fd = -EBADF;
        int ret = EXIT_FAILURE;
-       char *pidfile = NULL, *saveptr = NULL, *token = NULL, *v = NULL;
+       char *pidfile = NULL, *token = NULL;
        char pidfile_buf[STRLITERALLEN(RUNTIME_PATH) + STRLITERALLEN("/lxcfs.pid") + 1] = {};
-       bool debug = false, foreground = false, nonempty = false;
+       bool debug = false, foreground = false;
+#if !HAVE_FUSE3
+       bool nonempty = false;
+#endif
        bool load_use = false;
        /*
         * what we pass to fuse_main is:
         * argv[0] -s [-f|-d] -o allow_other,directio argv[1] NULL
         */
-       int nargs = 5, cnt = 0;
-       char *newargv[6];
+       int fuse_argc = 0;
+       int c, idx, new_argc;
+       char *fuse_argv[7];
+       const char *fuse_opts = NULL;
+       char *new_fuse_opts = NULL;
+       char *const *new_argv;
        struct lxcfs_opts *opts;
 
        opts = malloc(sizeof(struct lxcfs_opts));
@@ -1085,92 +1243,164 @@ int main(int argc, char *argv[])
                lxcfs_error("Error allocating memory for options");
                goto out;
        }
+
        opts->swap_off = false;
        opts->use_pidfd = false;
-       opts->use_cfs = true;
-
-       /* accomodate older init scripts */
-       swallow_arg(&argc, argv, "-s");
-
-       /* -f / --foreground */
-       foreground = swallow_arg(&argc, argv, "-f");
-       if (swallow_arg(&argc, argv, "--foreground"))
-               foreground = true;
+       opts->use_cfs = false;
+       opts->version = 1;
+
+       while ((c = getopt_long(argc, argv, "dulfhvso:p:", long_options, &idx)) != -1) {
+               switch (c) {
+               case 0:
+                       if (strcmp(long_options[idx].name, "enable-pidfd") == 0)
+                               opts->use_pidfd = true;
+                       else if (strcmp(long_options[idx].name, "enable-cfs") == 0)
+                               opts->use_cfs = true;
+                       else
+                               usage();
+                       break;
+               case 'd':
+                       debug = true;
+                       break;
+               case 'f':
+                       foreground = true;
+                       break;
+               case 'l':
+                       load_use = true;
+                       break;
+               case 'o':
+                       if (fuse_opts) {
+                               lxcfs_error("Specifying -o multiple times is unsupported");
+                               usage();
+                       }
 
-       /* -d / --debug */
-       debug = swallow_arg(&argc, argv, "-d");
-       if (swallow_arg(&argc, argv, "--debug"))
-               debug = true;
+                       fuse_opts = optarg;
+                       break;
+               case 'p':
+                       pidfile = optarg;
+                       break;
+               case 's':
+                       /* legacy argument: ignore */
+                       break;
+               case 'u':
+                       opts->swap_off = true;
+                       break;
+               case 'v':
+                       lxcfs_info("%s", STRINGIFY(PROJECT_VERSION));
+                       exit(EXIT_SUCCESS);
+               default:
+                       usage();
+               }
+       }
 
        if (foreground && debug)
                log_exit("Both --debug and --forgreound specified");
 
-       /* -l / --enable-loadavg */
-       load_use = swallow_arg(&argc, argv, "-l");
-       if (swallow_arg(&argc, argv, "--enable-loadavg"))
-               load_use = true;
-
-       /* -u / --disable-swap */
-       opts->swap_off = swallow_arg(&argc, argv, "-u");
-       if (swallow_arg(&argc, argv, "--disable-swap"))
-               opts->swap_off = true;
-
-       /* --enable-pidfd */
-       opts->use_pidfd = swallow_arg(&argc, argv, "--enable-pidfd");
-
-       /* --disable-cfs */
-       if (swallow_arg(&argc, argv, "--disable-cfs"))
-               opts->use_cfs = false;
-
-       if (swallow_option(&argc, argv, "-o", &v)) {
-               /* Parse multiple values */
-               for (; (token = strtok_r(v, ",", &saveptr)); v = NULL) {
-                       if (strcmp(token, "allow_other") == 0) {
-                               /* Noop. this is the default. Always enabled. */
-                       } else if (strcmp(token, "nonempty") == 0) {
+       new_argv = &argv[optind];
+       new_argc = argc - optind;
+
+       /* Older LXCFS versions printed help when used without any argument. */
+       if (new_argc == 0)
+               usage();
+
+       if (new_argc != 1) {
+               lxcfs_error("Missing mountpoint");
+               goto out;
+       }
+
+       fuse_argv[fuse_argc++] = argv[0];
+       if (debug)
+               fuse_argv[fuse_argc++] = "-d";
+       else
+               fuse_argv[fuse_argc++] = "-f";
+       fuse_argv[fuse_argc++] = "-o";
+
+       /* Parse additional fuse options. */
+       if (fuse_opts) {
+               char *dup;
+
+               dup = strdup(fuse_opts);
+               if (!dup) {
+                       lxcfs_error("Failed to copy fuse options");
+                       goto out;
+               }
+
+               lxc_iterate_parts(token, dup, ",") {
+                       /* default */
+                       if (strcmp(token, "allow_other") == 0)
+                               continue;
+
+                       /* default for LXCFS */
+                       if (strcmp(token, "direct_io") == 0)
+                               continue;
+
+                       /* default for LXCFS */
+                       if (strncmp(token, "entry_timeout", STRLITERALLEN("entry_timeout")) == 0)
+                               continue;
+
+                       /* default for LXCFS */
+                       if (strncmp(token, "attr_timeout", STRLITERALLEN("entry_timeout")) == 0)
+                               continue;
+
+                       /* default for LXCFS */
+                       if (strncmp(token, "allow_other", STRLITERALLEN("allow_other")) == 0)
+                               continue;
+
+                       /* default with fuse3 */
+                       if (strcmp(token, "nonempty") == 0) {
+                               #if !HAVE_FUSE3
                                nonempty = true;
-                       } else {
-                               free(v);
-                               lxcfs_error("Warning: unexpected fuse option %s", v);
-                               exit(EXIT_FAILURE);
+                               #endif
+                               continue;
+                       }
+
+                       if (append_comma_separate(&new_fuse_opts, token)) {
+                               lxcfs_error("Failed to copy fuse argument \"%s\"", token);
+                               free(dup);
+                               goto out;
                        }
                }
-               free(v);
-               v = NULL;
+               free(dup);
        }
 
-       /* -p / --pidfile */
-       if (swallow_option(&argc, argv, "-p", &v))
-               pidfile = v;
-       if (!pidfile && swallow_option(&argc, argv, "--pidfile", &v))
-               pidfile = v;
-
-       if (argc == 2  && is_version(argv[1])) {
-               lxcfs_info("%s", VERSION);
-               exit(EXIT_SUCCESS);
+       if (append_comma_separate(&new_fuse_opts, "allow_other,entry_timeout=0.5,attr_timeout=0.5")) {
+               lxcfs_error("Failed to copy fuse argument \"allow_other,entry_timeout=0.5,attr_timeout=0.5\"");
+               goto out;
        }
 
-       if (argc != 2 || is_help(argv[1]))
-               usage();
+#if !HAVE_FUSE3
+       if (nonempty) {
+               if (append_comma_separate(&new_fuse_opts, "nonempty")) {
+                       lxcfs_error("Failed to copy fuse argument \"nonempty\"");
+                       goto out;
+               }
+       }
 
-       do_reload();
-       if (signal(SIGUSR1, reload_handler) == SIG_ERR) {
-               lxcfs_error("Error setting USR1 signal handler: %m");
+       if (append_comma_separate(&new_fuse_opts, "direct_io")) {
+               lxcfs_error("Failed to copy fuse argument \"direct_io\"");
                goto out;
        }
+#endif
 
-       newargv[cnt++] = argv[0];
-       if (debug)
-               newargv[cnt++] = "-d";
-       else
-               newargv[cnt++] = "-f";
-       newargv[cnt++] = "-o";
-       if (nonempty)
-               newargv[cnt++] = "default_permissions,allow_other,direct_io,entry_timeout=0.5,attr_timeout=0.5,nonempty";
-       else
-               newargv[cnt++] = "default_permissions,allow_other,direct_io,entry_timeout=0.5,attr_timeout=0.5";
-       newargv[cnt++] = argv[1];
-       newargv[cnt++] = NULL;
+       /*
+        * We can't use default_permissions since we still support systems that
+        * don't have kernels with cgroup namespace support. On such kernels
+        * lxcfs will provide a namespaced cgroup view and needs explicit
+        * access helpers to make that work.
+        * Another reason that came to me is that we can't or at least
+        * shouldn't guarantee that we don't need more complicated access
+        * helpers for proc and sys virtualization in the future.
+        */
+
+       fuse_argv[fuse_argc++] = new_fuse_opts;
+       fuse_argv[fuse_argc++] = new_argv[0];
+       fuse_argv[fuse_argc] = NULL;
+
+       do_reload(false);
+       if (install_signal_handler(SIGUSR1, sigusr1_reload)) {
+               lxcfs_error("%s - Failed to install SIGUSR1 signal handler", strerror(errno));
+               goto out;
+       }
 
        if (!pidfile) {
                snprintf(pidfile_buf, sizeof(pidfile_buf), "%s/lxcfs.pid", RUNTIME_PATH);
@@ -1184,8 +1414,9 @@ int main(int argc, char *argv[])
        if (load_use && start_loadavg() != 0)
                goto out;
 
-       if (!fuse_main(nargs, newargv, &lxcfs_ops, opts))
+       if (!fuse_main(fuse_argc, fuse_argv, &lxcfs_ops, opts))
                ret = EXIT_SUCCESS;
+
        if (load_use)
                stop_loadavg();
 
@@ -1194,5 +1425,8 @@ out:
                dlclose(dlopen_handle);
        if (pidfile)
                unlink(pidfile);
+       free(new_fuse_opts);
+       free(opts);
+       close_prot_errno_disarm(pidfile_fd);
        exit(ret);
 }