From 1f842c996e46788115d6b5ca142fad949712c8e9 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 15 May 2019 14:35:57 -0600 Subject: [PATCH] passthrough: fix unix-domain sockets on FreeBSD (#413) 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 | 17 ++++---- example/passthrough_helpers.h | 76 +++++++++++++++++++++++++++++++++++ example/passthrough_ll.c | 13 ++---- test/test_examples.py | 31 ++++++++++---- test/test_syscalls.c | 53 ++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 example/passthrough_helpers.h diff --git a/example/passthrough.c b/example/passthrough.c index da91930..6de9fc1 100644 --- a/example/passthrough.c +++ b/example/passthrough.c @@ -44,11 +44,17 @@ #include #include #include +#ifdef __FreeBSD__ +#include +#include +#endif #include #ifdef HAVE_SETXATTR #include #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 index 0000000..6b77c33 --- /dev/null +++ b/example/passthrough_helpers.h @@ -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; +} diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c index b6ecaff..f0bc727 100644 --- a/example/passthrough_ll.c +++ b/example/passthrough_ll.c @@ -56,6 +56,8 @@ #include #include +#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); } diff --git a/test/test_examples.py b/test/test_examples.py index 3aabd19..d0da69d 100755 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -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'), diff --git a/test/test_syscalls.c b/test/test_syscalls.c index a7e2bc7..1d776fd 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include #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); -- 2.30.2