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.
This commit is contained in:
Alan Somers 2019-05-15 14:35:57 -06:00 committed by Nikolaus Rath
parent 7a5e1a9a9a
commit 1f842c996e
5 changed files with 163 additions and 27 deletions

View File

@ -44,11 +44,17 @@
#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;

View File

@ -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;
}

View 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);
}

View File

@ -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'),

View File

@ -11,8 +11,10 @@
#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);