passthrough: fix unix-domain sockets on FreeBSD (#413)
authorAlan Somers <asomers@gmail.com>
Wed, 15 May 2019 20:35:57 +0000 (14:35 -0600)
committerNikolaus Rath <Nikolaus@rath.org>
Wed, 15 May 2019 20:35:57 +0000 (21:35 +0100)
FreeBSD doesn't allow creating sockets using mknod(2). Instead, one has to use socket(2)
and bind(2).  Add appropriate logic to the examples and add a test case.

example/passthrough.c
example/passthrough_helpers.h [new file with mode: 0644]
example/passthrough_ll.c
test/test_examples.py
test/test_syscalls.c

index da91930bd5403edf61c88475176175be7187c276..6de9fc1ba1012b447334eff23953786ff66611d8 100644 (file)
 #include <sys/stat.h>
 #include <dirent.h>
 #include <errno.h>
+#ifdef __FreeBSD__
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif
 #include <sys/time.h>
 #ifdef HAVE_SETXATTR
 #include <sys/xattr.h>
 #endif
 
+#include "passthrough_helpers.h"
+
 static void *xmp_init(struct fuse_conn_info *conn,
                      struct fuse_config *cfg)
 {
@@ -138,16 +144,7 @@ static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
 {
        int res;
 
-       /* On Linux this could just be 'mknod(path, mode, rdev)' but this
-          is more portable */
-       if (S_ISREG(mode)) {
-               res = open(path, O_CREAT | O_EXCL | O_WRONLY, mode);
-               if (res >= 0)
-                       res = close(res);
-       } else if (S_ISFIFO(mode))
-               res = mkfifo(path, mode);
-       else
-               res = mknod(path, mode, rdev);
+       res = mknod_wrapper(AT_FDCWD, path, NULL, mode, rdev);
        if (res == -1)
                return -errno;
 
diff --git a/example/passthrough_helpers.h b/example/passthrough_helpers.h
new file mode 100644 (file)
index 0000000..6b77c33
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * FUSE: Filesystem in Userspace
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE
+ */
+
+/*
+ * Creates files on the underlying file system in response to a FUSE_MKNOD
+ * operation
+ */
+static int mknod_wrapper(int dirfd, const char *path, const char *link,
+       int mode, dev_t rdev)
+{
+       int res;
+
+       if (S_ISREG(mode)) {
+               res = openat(dirfd, path, O_CREAT | O_EXCL | O_WRONLY, mode);
+               if (res >= 0)
+                       res = close(res);
+       } else if (S_ISDIR(mode)) {
+               res = mkdirat(dirfd, path, mode);
+       } else if (S_ISLNK(mode) && link != NULL) {
+               res = symlinkat(link, dirfd, path);
+       } else if (S_ISFIFO(mode)) {
+               res = mkfifoat(dirfd, path, mode);
+#ifdef __FreeBSD__
+       } else if (S_ISSOCK(mode)) {
+               struct sockaddr_un su;
+               int fd;
+
+               if (strlen(path) >= sizeof(su.sun_path)) {
+                       errno = ENAMETOOLONG;
+                       return -1;
+               }
+               fd = socket(AF_UNIX, SOCK_STREAM, 0);
+               if (fd >= 0) {
+                       /*
+                        * We must bind the socket to the underlying file
+                        * system to create the socket file, even though
+                        * we'll never listen on this socket.
+                        */
+                       su.sun_family = AF_UNIX;
+                       strncpy(su.sun_path, path, sizeof(su.sun_path));
+                       res = bindat(dirfd, fd, (struct sockaddr*)&su,
+                               sizeof(su));
+                       if (res == 0)
+                               close(fd);
+               } else {
+                       res = -1;
+               }
+#endif
+       } else {
+               res = mknodat(dirfd, path, mode, rdev);
+       }
+
+       return res;
+}
index b6ecaffb64fdddc87558a5486f994dc643f97e2a..f0bc727ded2c6925ad99327d0b3f80c0b9f408e2 100644 (file)
@@ -56,6 +56,8 @@
 #include <sys/file.h>
 #include <sys/xattr.h>
 
+#include "passthrough_helpers.h"
+
 /* We are re-using pointers to our `struct lo_inode` and `struct
    lo_dirp` elements as inodes. This means that we must be able to
    store uintptr_t values in a fuse_ino_t variable. The following
@@ -381,7 +383,6 @@ static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
                             const char *name, mode_t mode, dev_t rdev,
                             const char *link)
 {
-       int newfd = -1;
        int res;
        int saverr;
        struct lo_inode *dir = lo_inode(req, parent);
@@ -389,12 +390,8 @@ static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
 
        saverr = ENOMEM;
 
-       if (S_ISDIR(mode))
-               res = mkdirat(dir->fd, name, mode);
-       else if (S_ISLNK(mode))
-               res = symlinkat(link, dir->fd, name);
-       else
-               res = mknodat(dir->fd, name, mode, rdev);
+       res = mknod_wrapper(dir->fd, name, link, mode, rdev);
+
        saverr = errno;
        if (res == -1)
                goto out;
@@ -411,8 +408,6 @@ static void lo_mknod_symlink(fuse_req_t req, fuse_ino_t parent,
        return;
 
 out:
-       if (newfd != -1)
-               close(newfd);
        fuse_reply_err(req, saverr);
 }
 
index 3aabd19cf8bac8ba6d8db497e9e12c6417e579fa..d0da69d743c8f6340ce6f8617cdab1b4451039ab 100755 (executable)
@@ -8,10 +8,12 @@ if __name__ == '__main__':
 import subprocess
 import os
 import sys
+import py
 import pytest
 import stat
 import shutil
 import filecmp
+import tempfile
 import time
 import errno
 import sys
@@ -56,6 +58,20 @@ def invoke_mount_fuse_drop_privileges(mnt_dir, name, options):
 
     return invoke_mount_fuse(mnt_dir, name, options + ('drop_privileges',))
 
+class raii_tmpdir:
+    def __init__(self):
+        self.d = tempfile.mkdtemp()
+
+    def __str__(self):
+        return str(self.d)
+
+    def mkdir(self, path):
+        return py.path.local(str(self.d)).mkdir(path)
+
+@pytest.fixture
+def short_tmpdir():
+    return raii_tmpdir()
+
 @pytest.mark.parametrize("cmdline_builder", (invoke_directly, invoke_mount_fuse,
                                              invoke_mount_fuse_drop_privileges))
 @pytest.mark.parametrize("options", powerset(options))
@@ -84,8 +100,7 @@ def test_hello(tmpdir, name, options, cmdline_builder):
 @pytest.mark.parametrize("writeback", (False, True))
 @pytest.mark.parametrize("name", ('passthrough', 'passthrough_fh', 'passthrough_ll'))
 @pytest.mark.parametrize("debug", (False, True))
-def test_passthrough(tmpdir, name, debug, capfd, writeback):
-    
+def test_passthrough(short_tmpdir, name, debug, capfd, writeback):
     # Avoid false positives from libfuse debug messages
     if debug:
         capfd.register_output(r'^   unique: [0-9]+, error: -[0-9]+ .+$',
@@ -95,8 +110,8 @@ def test_passthrough(tmpdir, name, debug, capfd, writeback):
     capfd.register_output(r"^ \d\d \[[^\]]+ message: 'No error: 0'\]",
                           count=0)
 
-    mnt_dir = str(tmpdir.mkdir('mnt'))
-    src_dir = str(tmpdir.mkdir('src'))
+    mnt_dir = str(short_tmpdir.mkdir('mnt'))
+    src_dir = str(short_tmpdir.mkdir('src'))
 
     cmdline = base_cmdline + \
               [ pjoin(basename, 'example', name),
@@ -145,7 +160,7 @@ def test_passthrough(tmpdir, name, debug, capfd, writeback):
             # When writeback caching is enabled, kernel has to open files for
             # reading even when userspace opens with O_WDONLY. This fails if the
             # filesystem process doesn't have special permission.
-            syscall_test_cmd.append('-52')
+            syscall_test_cmd.append('-53')
         subprocess.check_call(syscall_test_cmd)
     except:
         cleanup(mount_process, mnt_dir)
@@ -154,9 +169,9 @@ def test_passthrough(tmpdir, name, debug, capfd, writeback):
         umount(mount_process, mnt_dir)
 
 @pytest.mark.parametrize("cache", (False, True))
-def test_passthrough_hp(tmpdir, cache):
-    mnt_dir = str(tmpdir.mkdir('mnt'))
-    src_dir = str(tmpdir.mkdir('src'))
+def test_passthrough_hp(short_tmpdir, cache):
+    mnt_dir = str(short_tmpdir.mkdir('mnt'))
+    src_dir = str(short_tmpdir.mkdir('src'))
 
     cmdline = base_cmdline + \
               [ pjoin(basename, 'example', 'passthrough_hp'),
index a7e2bc7732ba6245bf75a8345fbe53814697ab13..1d776fd740321d5d9a3d7d85ff21ff2208cc9697 100644 (file)
 #include <utime.h>
 #include <errno.h>
 #include <assert.h>
+#include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/un.h>
 
 #ifndef ALLPERMS
 # define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
@@ -23,6 +25,7 @@ static char testfile[1024];
 static char testfile2[1024];
 static char testdir[1024];
 static char testdir2[1024];
+static char testsock[1024];
 static char subfile[1280];
 
 static char testfile_r[1024];
@@ -1734,6 +1737,53 @@ static int test_mkdir(void)
        return 0;
 }
 
+static int test_socket(void)
+{
+       struct sockaddr_un su;
+       int fd;
+       int res;
+       int err = 0;
+
+       start_test("socket");
+       if (strlen(testsock) + 1 > sizeof(su.sun_path)) {
+               fprintf(stderr, "Need to shorten mount point by %lu chars\n",
+                       strlen(testsock) + 1 - sizeof(su.sun_path));
+               return -1;
+       }
+       unlink(testsock);
+       fd = socket(AF_UNIX, SOCK_STREAM, 0);
+       if (fd < 0) {
+               PERROR("socket");
+               return -1;
+       }
+       su.sun_family = AF_UNIX;
+       strncpy(su.sun_path, testsock, sizeof(su.sun_path));
+       res = bind(fd, (struct sockaddr*)&su, sizeof(su));
+       if (res == -1) {
+               PERROR("bind");
+               return -1;
+       }
+
+       res = check_type(testsock, S_IFSOCK);
+       if (res == -1)
+               return -1;
+       err += check_nlink(testsock, 1);
+       close(fd);
+       res = unlink(testsock);
+       if (res == -1) {
+               PERROR("unlink");
+               return -1;
+       }
+       res = check_nonexist(testsock);
+       if (res == -1)
+               return -1;
+       if (err)
+               return -1;
+
+       success();
+       return 0;
+}
+
 #define test_create_ro_dir(flags)       \
        do_test_create_ro_dir(flags, #flags)
 
@@ -1822,6 +1872,7 @@ int main(int argc, char *argv[])
        sprintf(testdir, "%s/testdir", basepath);
        sprintf(testdir2, "%s/testdir2", basepath);
        sprintf(subfile, "%s/subfile", testdir2);
+       sprintf(testsock, "%s/testsock", basepath);
 
        sprintf(testfile_r, "%s/testfile", realpath);
        sprintf(testfile2_r, "%s/testfile2", realpath);
@@ -1845,6 +1896,7 @@ int main(int argc, char *argv[])
        err += test_rename_dir();
        err += test_rename_dir_loop();
        err += test_seekdir();
+       err += test_socket();
        err += test_utime();
        err += test_truncate(0);
        err += test_truncate(testdatalen / 2);
@@ -1903,6 +1955,7 @@ int main(int argc, char *argv[])
 
        unlink(testfile);
        unlink(testfile2);
+       unlink(testsock);
        rmdir(testdir);
        rmdir(testdir2);