mirror of
https://github.com/libfuse/libfuse.git
synced 2024-11-23 04:04:31 +08:00
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:
parent
7a5e1a9a9a
commit
1f842c996e
@ -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;
|
||||
|
||||
|
76
example/passthrough_helpers.h
Normal file
76
example/passthrough_helpers.h
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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'),
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user