diff options
-rw-r--r-- | src/bindfs.1 | 8 | ||||
-rw-r--r-- | src/bindfs.c | 28 | ||||
-rwxr-xr-x | tests/test_bindfs.rb | 21 |
3 files changed, 57 insertions, 0 deletions
diff --git a/src/bindfs.1 b/src/bindfs.1 index d98cb74..fd2095d 100644 --- a/src/bindfs.1 +++ b/src/bindfs.1 @@ -316,6 +316,14 @@ will be reflected in a mirrored file's ctime. The underlying file's ctime will still be updated normally. .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 +performed as the mounter, not the calling user. No efforts are made to check +whether the calling user would ordinarily have the permissions to make the +ioctl. This may be a security concern, especially when mounting as root. + +.TP .B \-\-multithreaded, \-o multithreaded Run bindfs in multithreaded mode. While bindfs is designed to be otherwise thread-safe, there is currently a race condition that may pose diff --git a/src/bindfs.c b/src/bindfs.c index d4a77ab..039393b 100644 --- a/src/bindfs.c +++ b/src/bindfs.c @@ -53,6 +53,7 @@ #endif #include <sys/time.h> #include <sys/statvfs.h> +#include <sys/ioctl.h> #include <unistd.h> #include <fcntl.h> #include <dirent.h> @@ -170,6 +171,8 @@ static struct Settings { int ctime_from_mtime; + int enable_ioctl; + uid_t uid_offset; gid_t gid_offset; @@ -233,6 +236,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_ioctl(const char *path, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + void *data); static int bindfs_statfs(const char *path, struct statvfs *stbuf); static int bindfs_release(const char *path, struct fuse_file_info *fi); static int bindfs_fsync(const char *path, int isdatasync, @@ -1090,6 +1096,16 @@ static int bindfs_write(const char *path, const char *buf, size_t size, return res; } +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) { + return -errno; + } + return ret; +} + static int bindfs_statfs(const char *path, struct statvfs *stbuf) { int res; @@ -1335,6 +1351,7 @@ static struct fuse_operations bindfs_oper = { .open = bindfs_open, .read = bindfs_read, .write = bindfs_write, + .ioctl = bindfs_ioctl, .statfs = bindfs_statfs, .release = bindfs_release, .fsync = bindfs_fsync, @@ -1450,6 +1467,7 @@ enum OptionKey { OPTKEY_XATTR_READ_WRITE, OPTKEY_REALISTIC_PERMISSIONS, OPTKEY_CTIME_FROM_MTIME, + OPTKEY_ENABLE_IOCTL, OPTKEY_HIDE_HARD_LINKS, OPTKEY_RESOLVE_SYMLINKS, OPTKEY_MULTITHREADED @@ -1530,6 +1548,9 @@ 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_IOCTL: + settings.enable_ioctl = 1; + return 0; case OPTKEY_HIDE_HARD_LINKS: settings.hide_hard_links = 1; return 0; @@ -1870,6 +1891,7 @@ 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-ioctl", "enable-ioctl", OPTKEY_ENABLE_IOCTL), OPT_OFFSET2("--multithreaded", "multithreaded", multithreaded, -1), OPT_OFFSET2("--uid-offset=%s", "uid-offset=%s", uid_offset, 0), @@ -1914,6 +1936,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_ioctl = 0; settings.uid_offset = 0; settings.gid_offset = 0; @@ -2155,6 +2178,11 @@ int main(int argc, char *argv[]) bindfs_oper.removexattr = NULL; } + /* Remove the ioctl implementation unless the user has enabled it */ + if (!settings.enable_ioctl) { + bindfs_oper.ioctl = NULL; + } + /* fuse_main will daemonize by fork()'ing. The signal handler will persist. */ setup_signal_handling(); diff --git a/tests/test_bindfs.rb b/tests/test_bindfs.rb index b4fd7f5..04c853d 100755 --- a/tests/test_bindfs.rb +++ b/tests/test_bindfs.rb @@ -616,6 +616,27 @@ testenv("", :title => "many files in a directory") do assert { Dir.entries('mnt/dir').sort == expected_entries.sort } end +# Issue #37 +root_testenv("--enable-ioctl", :title => "append-only ioctl") do + touch('mnt/file') + system('chattr +a mnt/file') + raise 'chattr +a failed' unless $?.success? + begin + File.open('mnt/file', 'a') do |f| + f.write('stuff') + end + assert { File.read('mnt/file') == 'stuff' } + assert_exception(EPERM) { File.write('mnt/file', 'newstuff') } + ensure + system('chattr -a mnt/file') + end +end + +root_testenv("", :title => "ioctl not enabled by default") do + touch('mnt/file') + assert { `chattr +a mnt/file 2>&1`; !$?.success? } +end + # FIXME: this stuff around testenv is a hax, and testenv may also exit(), which defeats the 'ensure' below. # the test setup ought to be refactored. It might well use MiniTest or something. if Process.uid == 0 |