diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/bindfs.1 | 17 | ||||
-rw-r--r-- | src/bindfs.c | 59 | ||||
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/fcntl_locker.c | 67 | ||||
-rwxr-xr-x | tests/test_bindfs.rb | 41 |
6 files changed, 184 insertions, 4 deletions
@@ -34,6 +34,7 @@ Makefile # Products src/bindfs +tests/fcntl_locker tests/readdir_inode tests/utimens_nofollow tests/*.log diff --git a/src/bindfs.1 b/src/bindfs.1 index fd2095d..f74cfc8 100644 --- a/src/bindfs.1 +++ b/src/bindfs.1 @@ -316,6 +316,23 @@ will be reflected in a mirrored file's ctime. The underlying file's ctime will still be updated normally. .TP +.B \-\-enable\-lock\-forwarding, \-o enable\-lock\-forwarding +Forwards \fBflock\fP and \fBfcntl\fP locking requests to the source directory. +This way, locking a file in the bindfs mount will also lock the file in the +source directory. + +This option \fBmust\fP be used with \fB\-\-multithreaded\fP because otherwise +bindfs will deadlock as soon as there is lock contention. However, see +\fB\%BUGS\fP below for caveats about \fB\-\-multithreaded\fP with the current +implementation. + +.TP +.B \-\-disable\-lock\-forwarding, \-o disable\-lock\-forwarding +Currently does nothing, but a future release may default to enabling lock +forwarding. If you depend on this behaviour, it's recommended to set this flag +explicitly. + +.TP .B \-\-enable\-ioctl, \-o enable\-ioctl Enables forwarding of ioctl, which is needed for some advanced features such as append-only files (\fBchattr +a\fP). Note that the ioctl action will be diff --git a/src/bindfs.c b/src/bindfs.c index 8ba92bf..82cbe0d 100644 --- a/src/bindfs.c +++ b/src/bindfs.c @@ -53,6 +53,7 @@ #endif #include <sys/time.h> #include <sys/statvfs.h> +#include <sys/file.h> #include <sys/ioctl.h> #include <unistd.h> #include <fcntl.h> @@ -171,6 +172,8 @@ static struct Settings { int ctime_from_mtime; + int enable_lock_forwarding; + int enable_ioctl; uid_t uid_offset; @@ -236,6 +239,9 @@ static int bindfs_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi); static int bindfs_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi); +static int bindfs_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock); +static int bindfs_flock(const char *path, struct fuse_file_info *fi, int op); static int bindfs_ioctl(const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data); @@ -1096,15 +1102,34 @@ static int bindfs_write(const char *path, const char *buf, size_t size, return res; } +static int bindfs_lock(const char *path, struct fuse_file_info *fi, int cmd, + struct flock *lock) +{ + int res = fcntl(fi->fh, cmd, lock); + if (res == -1) { + return -errno; + } + return 0; +} + +static int bindfs_flock(const char *path, struct fuse_file_info *fi, int op) +{ + int res = flock(fi->fh, op); + if (res == -1) { + return -errno; + } + return 0; +} + static int bindfs_ioctl(const char *path, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data) { - int ret = ioctl(fi->fh, cmd, data); - if (ret == -1) { + int res = ioctl(fi->fh, cmd, data); + if (res == -1) { return -errno; } - return ret; + return res; } static int bindfs_statfs(const char *path, struct statvfs *stbuf) @@ -1352,6 +1377,8 @@ static struct fuse_operations bindfs_oper = { .open = bindfs_open, .read = bindfs_read, .write = bindfs_write, + .lock = bindfs_lock, + .flock = bindfs_flock, .ioctl = bindfs_ioctl, .statfs = bindfs_statfs, .release = bindfs_release, @@ -1428,6 +1455,7 @@ static void print_usage(const char *progname) " --realistic-permissions Hide permission bits for actions mounter can't do.\n" " --ctime-from-mtime Read file properties' change time\n" " from file content modification time.\n" + " --enable-lock-forwarding Forward locks to the underlying FS.\n" " --enable-ioctl Forward ioctl() calls (as the mounter).\n" " --hide-hard-links Always report a hard link count of 1.\n" " --resolve-symlinks Resolve symbolic links.\n" @@ -1469,6 +1497,8 @@ enum OptionKey { OPTKEY_XATTR_READ_WRITE, OPTKEY_REALISTIC_PERMISSIONS, OPTKEY_CTIME_FROM_MTIME, + OPTKEY_ENABLE_LOCK_FORWARDING, + OPTKEY_DISABLE_LOCK_FORWARDING, OPTKEY_ENABLE_IOCTL, OPTKEY_HIDE_HARD_LINKS, OPTKEY_RESOLVE_SYMLINKS, @@ -1550,6 +1580,12 @@ static int process_option(void *data, const char *arg, int key, case OPTKEY_CTIME_FROM_MTIME: settings.ctime_from_mtime = 1; return 0; + case OPTKEY_ENABLE_LOCK_FORWARDING: + settings.enable_lock_forwarding = 1; + return 0; + case OPTKEY_DISABLE_LOCK_FORWARDING: + settings.enable_lock_forwarding = 0; + return 0; case OPTKEY_ENABLE_IOCTL: settings.enable_ioctl = 1; return 0; @@ -1893,6 +1929,8 @@ int main(int argc, char *argv[]) OPT2("--realistic-permissions", "realistic-permissions", OPTKEY_REALISTIC_PERMISSIONS), OPT2("--ctime-from-mtime", "ctime-from-mtime", OPTKEY_CTIME_FROM_MTIME), + OPT2("--enable-lock-forwarding", "enable-lock-forwarding", OPTKEY_ENABLE_LOCK_FORWARDING), + OPT2("--disable-lock-forwarding", "disable-lock-forwarding", OPTKEY_DISABLE_LOCK_FORWARDING), OPT2("--enable-ioctl", "enable-ioctl", OPTKEY_ENABLE_IOCTL), OPT_OFFSET2("--multithreaded", "multithreaded", multithreaded, -1), @@ -1938,6 +1976,7 @@ int main(int argc, char *argv[]) settings.resolved_symlink_deletion_policy = RESOLVED_SYMLINK_DELETION_SYMLINK_ONLY; settings.realistic_permissions = 0; settings.ctime_from_mtime = 0; + settings.enable_lock_forwarding = 0; settings.enable_ioctl = 0; settings.uid_offset = 0; settings.gid_offset = 0; @@ -2180,6 +2219,20 @@ int main(int argc, char *argv[]) bindfs_oper.removexattr = NULL; } + /* Check that lock forwarding is not enabled in single-threaded mode. */ + if (settings.enable_lock_forwarding && !od.multithreaded) { + fprintf(stderr, "To use --enable-lock-forwarding, you must use " + "--multithreaded, but see the man page for caveats!\n"); + return 1; + } + + /* Remove the locking implementation unless the user has enabled lock + forwarding. FUSE implements locking inside the mountpoint by default. */ + if (!settings.enable_lock_forwarding) { + bindfs_oper.lock = NULL; + bindfs_oper.flock = NULL; + } + /* Remove the ioctl implementation unless the user has enabled it */ if (!settings.enable_ioctl) { bindfs_oper.ioctl = NULL; diff --git a/tests/Makefile.am b/tests/Makefile.am index eca8563..66713e2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,9 +1,10 @@ UNAME_S := $(shell uname -s) -noinst_PROGRAMS = readdir_inode utimens_nofollow +noinst_PROGRAMS = readdir_inode utimens_nofollow fcntl_locker readdir_inode_SOURCES = readdir_inode.c utimens_nofollow_SOURCES = utimens_nofollow.c +fcntl_locker_SOURCES = fcntl_locker.c TESTS = test_bindfs.rb SUBDIRS = internals diff --git a/tests/fcntl_locker.c b/tests/fcntl_locker.c new file mode 100644 index 0000000..fa2e9dc --- /dev/null +++ b/tests/fcntl_locker.c @@ -0,0 +1,67 @@ +// Takes two files and exits with 0 if fcntl-locking one fcntl-locks the other. +// If the files don't fcntl-lock each other, returns 1. +// If any other error occurs, returns 2. + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +int main(int argc, const char* argv[]) { + if (argc != 3) { + fprintf(stderr, "expecting exactly two arguments\n"); + return 2; + } + + int fd1 = -1; + int fd2 = -1; + int result = 2; + + fd1 = open(argv[1], O_RDWR); + if (fd1 == -1) { + perror("failed to open the first file"); + goto exit; + } + fd2 = open(argv[2], O_RDWR); + if (fd2 == -1) { + perror("failed to open the second file"); + goto exit; + } + + struct flock lock; + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd1, F_SETLK, &lock) == -1) { + perror("fcntl F_SETLK on the first file failed"); + goto exit; + } + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(fd2, F_SETLK, &lock) == -1) { + if (errno == EACCES || errno == EAGAIN) { + result = 0; + goto exit; + } else { + perror("fcntl F_SETLK on the second file failed"); + goto exit; + } + } else { + result = 1; + goto exit; + } + +exit: + close(fd1); + close(fd2); + return result; +} diff --git a/tests/test_bindfs.rb b/tests/test_bindfs.rb index 04c853d..c0ae5dd 100755 --- a/tests/test_bindfs.rb +++ b/tests/test_bindfs.rb @@ -616,6 +616,47 @@ testenv("", :title => "many files in a directory") do assert { Dir.entries('mnt/dir').sort == expected_entries.sort } end +testenv("--enable-lock-forwarding --multithreaded", :title => "lock forwarding") do + File.write('src/file', 'some contents for fcntl lockng') + # (this test passes with an empty file as well, but this way is clearer) + + # flock + File.open('mnt/file') do |f1| + File.open('src/file') do |f2| + assert { f1.flock(File::LOCK_EX | File::LOCK_NB) } + assert { !f2.flock(File::LOCK_EX | File::LOCK_NB) } + assert { f1.flock(File::LOCK_UN) } + + assert { f2.flock(File::LOCK_EX | File::LOCK_NB) } + assert { !f1.flock(File::LOCK_EX | File::LOCK_NB) } + end + assert { f1.flock(File::LOCK_EX | File::LOCK_NB) } + end + + # fcntl locking + system("#{$tests_dir}/fcntl_locker src/file mnt/file") + raise "fcntl lock sharing test failed" unless $?.success? +end + +testenv("--disable-lock-forwarding", :title => "no lock forwarding") do + File.write('src/file', 'some contents for fcntl lockng') + + # flock + File.open('mnt/file') do |f1| + File.open('src/file') do |f2| + assert { f1.flock(File::LOCK_EX | File::LOCK_NB) } + assert { f2.flock(File::LOCK_EX | File::LOCK_NB) } + end + File.open('mnt/file') do |f2| + assert { !f2.flock(File::LOCK_EX | File::LOCK_NB) } + end + end + + # fcntl locking + system("#{$tests_dir}/fcntl_locker src/file mnt/file") + raise "fcntl lock sharing test failed" unless $?.exitstatus == 1 +end + # Issue #37 root_testenv("--enable-ioctl", :title => "append-only ioctl") do touch('mnt/file') |