Hello community, here is the log from the commit of package bindfs for openSUSE:Factory checked in at 2015-11-15 12:46:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/bindfs (Old) and /work/SRC/openSUSE:Factory/.bindfs.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "bindfs" Changes: -------- --- /work/SRC/openSUSE:Factory/bindfs/bindfs.changes 2015-09-27 08:39:47.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.bindfs.new/bindfs.changes 2015-11-15 12:48:03.000000000 +0100 @@ -1,0 +2,9 @@ +Fri Nov 13 16:07:59 UTC 2015 - jeng...@inai.de + +- Update to new upstream release 1.13.0 +* Implemented --resolved-symlink-deletion and added tests. +* Improved and documented --resolve_symlinks in some edge cases. +* Better handle symlinks with destructive system calls. +* Add functionality to transparently resolve symbolic links. + +------------------------------------------------------------------- Old: ---- bindfs-1.12.7.tar.gz New: ---- bindfs-1.13.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ bindfs.spec ++++++ --- /var/tmp/diff_new_pack.o8eDeT/_old 2015-11-15 12:48:04.000000000 +0100 +++ /var/tmp/diff_new_pack.o8eDeT/_new 2015-11-15 12:48:04.000000000 +0100 @@ -17,7 +17,7 @@ Name: bindfs -Version: 1.12.7 +Version: 1.13.0 Release: 0 Summary: Mount Directories to other Locations and alter Permission Bits License: GPL-2.0+ ++++++ bindfs-1.12.7.tar.gz -> bindfs-1.13.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/ChangeLog new/bindfs-1.13.0/ChangeLog --- old/bindfs-1.12.7/ChangeLog 2015-09-09 11:40:21.000000000 +0200 +++ new/bindfs-1.13.0/ChangeLog 2015-09-27 00:11:40.000000000 +0200 @@ -1,3 +1,9 @@ +2015-09-26 Martin Pärtel <martin dot partel at gmail dot com> + + * Merged --resolve-symlinks by hstern@ (#23). Thanks! + * Implemented --resolved-symlink-deletion. + * Released 1.13.0 + 2015-09-09 Martin Pärtel <martin dot partel at gmail dot com> * OS X xattr fixes prompted, assisted and tested by @retrography (#21). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/configure new/bindfs-1.13.0/configure --- old/bindfs-1.12.7/configure 2015-09-09 11:40:25.000000000 +0200 +++ new/bindfs-1.13.0/configure 2015-09-27 00:11:42.000000000 +0200 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for bindfs 1.12.7. +# Generated by GNU Autoconf 2.69 for bindfs 1.13.0. # # Report bugs to <martin.par...@gmail.com>. # @@ -590,8 +590,8 @@ # Identity of this package. PACKAGE_NAME='bindfs' PACKAGE_TARNAME='bindfs' -PACKAGE_VERSION='1.12.7' -PACKAGE_STRING='bindfs 1.12.7' +PACKAGE_VERSION='1.13.0' +PACKAGE_STRING='bindfs 1.13.0' PACKAGE_BUGREPORT='martin.par...@gmail.com' PACKAGE_URL='' @@ -1327,7 +1327,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures bindfs 1.12.7 to adapt to many kinds of systems. +\`configure' configures bindfs 1.13.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1397,7 +1397,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of bindfs 1.12.7:";; + short | recursive ) echo "Configuration of bindfs 1.13.0:";; esac cat <<\_ACEOF @@ -1513,7 +1513,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -bindfs configure 1.12.7 +bindfs configure 1.13.0 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1791,7 +1791,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by bindfs $as_me 1.12.7, which was +It was created by bindfs $as_me 1.13.0, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2655,7 +2655,7 @@ # Define the identity of the package. PACKAGE='bindfs' - VERSION='1.12.7' + VERSION='1.13.0' cat >>confdefs.h <<_ACEOF @@ -12253,7 +12253,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by bindfs $as_me 1.12.7, which was +This file was extended by bindfs $as_me 1.13.0, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -12319,7 +12319,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -bindfs config.status 1.12.7 +bindfs config.status 1.13.0 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/configure.ac new/bindfs-1.13.0/configure.ac --- old/bindfs-1.12.7/configure.ac 2015-09-09 11:40:21.000000000 +0200 +++ new/bindfs-1.13.0/configure.ac 2015-09-27 00:11:40.000000000 +0200 @@ -1,4 +1,4 @@ -AC_INIT([bindfs],[1.12.7],[martin.par...@gmail.com]) +AC_INIT([bindfs],[1.13.0],[martin.par...@gmail.com]) AM_INIT_AUTOMAKE([foreign]) AC_CONFIG_HEADERS([config.h]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/src/bindfs.1 new/bindfs-1.13.0/src/bindfs.1 --- old/bindfs-1.12.7/src/bindfs.1 2015-09-09 11:40:21.000000000 +0200 +++ new/bindfs-1.13.0/src/bindfs.1 2015-09-27 00:11:40.000000000 +0200 @@ -168,7 +168,7 @@ Makes chmod always fail with a 'permission denied' error. .TP -.B \-\-chmod\-filter=\fIpermissions\fP,, \-o chmod\-filter=... +.B \-\-chmod\-filter=\fIpermissions\fP, \-o chmod\-filter=... Changes the permission bits of a chmod request before it is applied to the original file. Accepts the same permission syntax as \-\-perms. See \fB\%PERMISSION \%SPECIFICATION\fP below for details. @@ -221,6 +221,48 @@ .B \-\-write\-rate=\fIN\fP, \-o write\-rate=\fIN\fP Same as above, but for writes. +.SH LINK HANDLING + +.TP +.B \-\-hide\-hard\-links, \-o hide\-hard\-links +Shows the hard link count of all files as 1. + +.TP +.B \-\-resolve\-symlinks, \-o resolve-symlinks +Transparently resolves symbolic links. Disables creation of new symbolic +links. + +With the following exceptions, operations will operate directly on the target +file instead of the symlink. Renaming/moving a resolved symlink (inside the same +mount point) will move the symlink instead of the underlying file. Deleting a +resolved symlink will delete the underlying symlink but not the destination +file. This can be configured with \fB\-\-resolved-symlink-deletion\fP. + +Note that when some programs, such as \fBvim\fP, save files, they actually move +the old file out of the way, create a new file in its place, and finally delete +the old file. Doing these operations on a resolved symlink will replace it with +a regular file. + +Symlinks pointing outside the source directory are supported with the following +exception: accessing the mountpoint recursively through a resolved symlink is +not supported and will return an error. This is because a FUSE filesystem cannot +reliably call itself recursively without deadlocking, especially in +single-threaded mode. + +.TP +.B \-\-resolved\-symlink\-deletion=\fIpolicy\fP, \-o resolved\-symlink\-deletion=\fIpolicy\fP +If \fB\-\-resolve\-symlinks\fP is enabled, decides what happens when a resolved +symlink is deleted. The options are: \fBdeny\fP (resolved symlinks cannot be +deleted), \fBsymlink-only\fP (the underlying symlink is deleted, its target is +not), \fBsymlink-first\fP (the symlink is deleted, and if that succeeds, +the target is deleted but no error is reported if that fails) or +\fBtarget-first\fP (the target is deleted first, and the symlink is deleted +only if deleting the target succeeded). The default is \fBsymlink-only\fP. + +Note that deleting files inside symlinked directories is always possible with +all settings, including \fBdeny\fP, unless something else protects those files. + + .SH MISCELLANEOUS OPTIONS .TP @@ -259,10 +301,6 @@ The underlying file's ctime will still be updated normally. .TP -.B \-\-hide-hard-links, \-o hide-hard-links -Shows the hard link count of all files as 1. - -.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 @@ -402,6 +440,10 @@ and a similar rate of requests are treated fairly as long as the kernel orders their requests fairly. +Some features relying on xattrs might not work properly on OS X +(\fBhttps://github.com/mpartel/bindfs/issues/21\fP). +For instance, Finder tags seem to work but comments might not. + Please report bugs and/or send pull requests to \fBhttps://github.com/mpartel/bindfs/issues\fP. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/src/bindfs.c new/bindfs-1.13.0/src/bindfs.c --- old/bindfs-1.12.7/src/bindfs.c 2015-09-09 11:40:21.000000000 +0200 +++ new/bindfs-1.13.0/src/bindfs.c 2015-09-27 00:11:40.000000000 +0200 @@ -40,6 +40,7 @@ #include <stdlib.h> #include <stddef.h> #include <stdio.h> +#include <stdbool.h> #include <string.h> #include <ctype.h> #ifdef HAVE_SYS_TYPES_H @@ -92,18 +93,19 @@ gid_t new_gid; /* user-specified gid */ uid_t create_for_uid; gid_t create_for_gid; - const char *mntsrc; - const char *mntdest; + char *mntsrc; + char *mntdest; + int mntdest_len; /* caches strlen(mntdest) */ int mntsrc_fd; - char* original_working_dir; + char *original_working_dir; mode_t original_umask; - UserMap* usermap; /* From the --map option. */ - UserMap* usermap_reverse; + UserMap *usermap; /* From the --map option. */ + UserMap *usermap_reverse; - RateLimiter* read_limiter; - RateLimiter* write_limiter; + RateLimiter *read_limiter; + RateLimiter *write_limiter; enum CreatePolicy { CREATE_AS_USER, @@ -141,15 +143,24 @@ } xattr_policy; int mirrored_users_only; - uid_t* mirrored_users; + uid_t *mirrored_users; int num_mirrored_users; gid_t *mirrored_members; int num_mirrored_members; + int hide_hard_links; + int resolve_symlinks; + + enum ResolvedSymlinkDeletion { + RESOLVED_SYMLINK_DELETION_DENY, + RESOLVED_SYMLINK_DELETION_SYMLINK_ONLY, + RESOLVED_SYMLINK_DELETION_SYMLINK_FIRST, + RESOLVED_SYMLINK_DELETION_TARGET_FIRST + } resolved_symlink_deletion_policy; + int realistic_permissions; int ctime_from_mtime; - int hide_hard_links; } settings; @@ -162,8 +173,8 @@ /* Checks whether the uid is to be the mirrored owner of all files. */ static int is_mirrored_user(uid_t uid); -/* Processes the virtual path to a real path. Don't free() the result. */ -static const char *process_path(const char *path); +/* Processes the virtual path to a real path. Always free() the result. */ +static char *process_path(const char *path, bool resolve_symlinks); /* The common parts of getattr and fgetattr. */ static int getattr_common(const char *path, struct stat *stbuf); @@ -171,6 +182,9 @@ /* Chowns a new file if necessary. */ static void chown_new_file(const char *path, struct fuse_context *fc, int (*chown_func)(const char*, uid_t, gid_t)); +/* Unified implementation of unlink and rmdir. */ +static int delete_file(const char *path, int (*target_delete_func)(const char *)); + /* FUSE callbacks */ static void *bindfs_init(); static void bindfs_destroy(void *private_data); @@ -245,18 +259,39 @@ } -static const char *process_path(const char *path) +static char *process_path(const char *path, bool resolve_symlinks) { - if (path == NULL) /* possible? */ + if (path == NULL) { /* possible? */ + errno = EINVAL; return NULL; + } while (*path == '/') ++path; if (*path == '\0') - return "."; - else - return path; + path = "."; + + if (resolve_symlinks && settings.resolve_symlinks) { + char* result = realpath(path, NULL); + if (result == NULL) { + if (errno == ENOENT) { + /* Broken symlink (or missing file). Don't return null because + we want to be able to operate on broken symlinks. */ + return strdup(path); + } + } else if (strncmp(result, settings.mntdest, settings.mntdest_len) == 0) { + /* Recursive call. We cannot handle this without deadlocking, + especially in single-threaded mode. */ + DPRINTF("Denying recursive access to mountpoint `%s'", result); + free(result); + errno = EPERM; + return NULL; + } + return result; + } else { + return strdup(path); + } } static int getattr_common(const char *procpath, struct stat *stbuf) @@ -356,6 +391,75 @@ } } +static int delete_file(const char *path, int (*target_delete_func)(const char *)) { + int res; + char *real_path; + struct stat st; + char *also_try_delete = NULL; + char *unlink_first = NULL; + int (*main_delete_func)(const char*) = target_delete_func; + + real_path = process_path(path, false); + if (real_path == NULL) + return -errno; + + if (settings.resolve_symlinks) { + if (lstat(real_path, &st) == -1) { + free(real_path); + return -errno; + } + + if (S_ISLNK(st.st_mode)) { + switch(settings.resolved_symlink_deletion_policy) { + case RESOLVED_SYMLINK_DELETION_DENY: + free(real_path); + return -EPERM; + case RESOLVED_SYMLINK_DELETION_SYMLINK_ONLY: + main_delete_func = &unlink; + break; + case RESOLVED_SYMLINK_DELETION_SYMLINK_FIRST: + main_delete_func = &unlink; + + also_try_delete = realpath(real_path, NULL); + if (also_try_delete == NULL && errno != ENOENT) { + free(real_path); + return -errno; + } + break; + case RESOLVED_SYMLINK_DELETION_TARGET_FIRST: + unlink_first = realpath(real_path, NULL); + if (unlink_first == NULL && errno != ENOENT) { + free(real_path); + return -errno; + } + + if (unlink_first != NULL) { + res = unlink(unlink_first); + free(unlink_first); + if (res == -1) { + free(real_path); + return -errno; + } + } + break; + } + } + } + + res = main_delete_func(real_path); + free(real_path); + if (res == -1) { + free(also_try_delete); + return -errno; + } + + if (also_try_delete != NULL) { + (void)target_delete_func(also_try_delete); + free(also_try_delete); + } + + return 0; +} static void *bindfs_init() @@ -384,34 +488,57 @@ static int bindfs_getattr(const char *path, struct stat *stbuf) { - path = process_path(path); + int res; + char *real_path; - if (lstat(path, stbuf) == -1) + real_path = process_path(path, true); + if (real_path == NULL) return -errno; - return getattr_common(path, stbuf); + + if (lstat(real_path, stbuf) == -1) { + free(real_path); + return -errno; + } + + res = getattr_common(real_path, stbuf); + free(real_path); + return res; } static int bindfs_fgetattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) { - path = process_path(path); + int res; + char *real_path; - if (fstat(fi->fh, stbuf) == -1) + real_path = process_path(path, true); + if (real_path == NULL) return -errno; - return getattr_common(path, stbuf); + + if (fstat(fi->fh, stbuf) == -1) { + free(real_path); + return -errno; + } + res = getattr_common(real_path, stbuf); + free(real_path); + return res; } static int bindfs_readlink(const char *path, char *buf, size_t size) { int res; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; /* No need to check for access to the link itself, since symlink permissions don't matter. Access to the path components of the symlink are automatically queried by FUSE. */ - res = readlink(path, buf, size - 1); + res = readlink(real_path, buf, size - 1); + free(real_path); if (res == -1) return -errno; @@ -422,10 +549,14 @@ static int bindfs_opendir(const char *path, struct fuse_file_info *fi) { DIR *dp; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; - dp = opendir(path); + dp = opendir(real_path); + free(real_path); if (dp == NULL) return -errno; @@ -447,10 +578,14 @@ struct stat st; int result = 0; long pc_ret; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; + pc_ret = pathconf(real_path, _PC_NAME_MAX); + free(real_path); - pc_ret = pathconf(path, _PC_NAME_MAX); if (pc_ret < 0) { DPRINTF("pathconf failed: %s (%d)", strerror(errno), errno); pc_ret = NAME_MAX; @@ -491,20 +626,26 @@ { int res; struct fuse_context *fc; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; mode = permchain_apply(settings.create_permchain, mode); if (S_ISFIFO(mode)) - res = mkfifo(path, mode); + res = mkfifo(real_path, mode); else - res = mknod(path, mode, rdev); - if (res == -1) + res = mknod(real_path, mode, rdev); + if (res == -1) { + free(real_path); return -errno; + } fc = fuse_get_context(); - chown_new_file(path, fc, &chown); + chown_new_file(real_path, fc, &chown); + free(real_path); return 0; } @@ -513,61 +654,60 @@ { int res; struct fuse_context *fc; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; mode |= S_IFDIR; /* tell permchain_apply this is a directory */ mode = permchain_apply(settings.create_permchain, mode); - res = mkdir(path, mode & 0777); - if (res == -1) + res = mkdir(real_path, mode & 0777); + if (res == -1) { + free(real_path); return -errno; + } fc = fuse_get_context(); - chown_new_file(path, fc, &chown); + chown_new_file(real_path, fc, &chown); + free(real_path); return 0; } static int bindfs_unlink(const char *path) { - int res; - - path = process_path(path); - - res = unlink(path); - if (res == -1) - return -errno; - - return 0; + return delete_file(path, &unlink); } static int bindfs_rmdir(const char *path) { - int res; - - path = process_path(path); - - res = rmdir(path); - if (res == -1) - return -errno; - - return 0; + return delete_file(path, &rmdir); } static int bindfs_symlink(const char *from, const char *to) { int res; struct fuse_context *fc; + char *real_to; - to = process_path(to); + if (settings.resolve_symlinks) + return -EPERM; - res = symlink(from, to); - if (res == -1) + real_to = process_path(to, false); + if (real_to == NULL) + return -errno; + + res = symlink(from, real_to); + if (res == -1) { + free(real_to); return -errno; + } fc = fuse_get_context(); - chown_new_file(to, fc, &lchown); + chown_new_file(real_to, fc, &lchown); + free(real_to); return 0; } @@ -575,11 +715,21 @@ static int bindfs_rename(const char *from, const char *to) { int res; + char *real_from, *real_to; + + real_from = process_path(from, false); + if (real_from == NULL) + return -errno; - from = process_path(from); - to = process_path(to); + real_to = process_path(to, true); + if (real_to == NULL) { + free(real_from); + return -errno; + } - res = rename(from, to); + res = rename(real_from, real_to); + free(real_from); + free(real_to); if (res == -1) return -errno; @@ -589,11 +739,21 @@ static int bindfs_link(const char *from, const char *to) { int res; + char *real_from, *real_to; - from = process_path(from); - to = process_path(to); + real_from = process_path(from, true); + if (real_from == NULL) + return -errno; - res = link(from, to); + real_to = process_path(to, true); + if (real_to == NULL) { + free(real_from); + return -errno; + } + + res = link(real_from, real_to); + free(real_from); + free(real_to); if (res == -1) return -errno; @@ -605,13 +765,18 @@ int file_execute_only = 0; struct stat st; mode_t diff = 0; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; if (settings.chmod_allow_x) { /* Get the old permission bits and see which bits would change. */ - if (lstat(path, &st) == -1) + if (lstat(real_path, &st) == -1) { + free(real_path); return -errno; + } if (S_ISREG(st.st_mode)) { diff = (st.st_mode & 07777) ^ (mode & 07777); @@ -622,26 +787,36 @@ switch (settings.chmod_policy) { case CHMOD_NORMAL: mode = permchain_apply(settings.chmod_permchain, mode); - if (chmod(path, mode) == -1) + if (chmod(real_path, mode) == -1) { + free(real_path); return -errno; + } + free(real_path); return 0; case CHMOD_IGNORE: if (file_execute_only) { diff &= 00111; /* See which execute bits were flipped. Forget about other differences. */ - if (chmod(path, st.st_mode ^ diff) == -1) + if (chmod(real_path, st.st_mode ^ diff) == -1) { + free(real_path); return -errno; + } } + free(real_path); return 0; case CHMOD_DENY: if (file_execute_only) { if ((diff & 07666) == 0) { /* Only execute bits have changed, so we can allow this. */ - if (chmod(path, mode) == -1) + if (chmod(real_path, mode) == -1) { + free(real_path); return -errno; + } + free(real_path); return 0; } } + free(real_path); return -EPERM; default: assert(0); @@ -651,6 +826,7 @@ static int bindfs_chown(const char *path, uid_t uid, gid_t gid) { int res; + char *real_path; if (uid != -1) { switch (settings.chown_policy) { @@ -679,8 +855,12 @@ } if (uid != -1 || gid != -1) { - path = process_path(path); - res = lchown(path, uid, gid); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; + + res = lchown(real_path, uid, gid); + free(real_path); if (res == -1) return -errno; } @@ -691,10 +871,14 @@ static int bindfs_truncate(const char *path, off_t size) { int res; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; - res = truncate(path, size); + res = truncate(real_path, size); + free(real_path); if (res == -1) return -errno; @@ -717,22 +901,26 @@ static int bindfs_utimens(const char *path, const struct timespec ts[2]) { int res; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; #ifdef HAVE_UTIMENSAT - res = utimensat(settings.mntsrc_fd, path, ts, AT_SYMLINK_NOFOLLOW); + res = utimensat(settings.mntsrc_fd, real_path, ts, AT_SYMLINK_NOFOLLOW); #elif HAVE_LUTIMES struct timeval tv[2]; tv[0].tv_sec = ts[0].tv_sec; tv[0].tv_usec = ts[0].tv_nsec / 1000; tv[1].tv_sec = ts[1].tv_sec; tv[1].tv_usec = ts[1].tv_nsec / 1000; - res = lutimes(path, tv); + res = lutimes(real_path, tv); #else #error "No symlink-compatible utime* function available." #endif - + + free(real_path); if (res == -1) return -errno; @@ -743,18 +931,24 @@ { int fd; struct fuse_context *fc; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; mode |= S_IFREG; /* tell permchain_apply this is a regular file */ mode = permchain_apply(settings.create_permchain, mode); - fd = open(path, fi->flags, mode & 0777); - if (fd == -1) + fd = open(real_path, fi->flags, mode & 0777); + if (fd == -1) { + free(real_path); return -errno; + } fc = fuse_get_context(); - chown_new_file(path, fc, &chown); + chown_new_file(real_path, fc, &chown); + free(real_path); fi->fh = fd; return 0; @@ -763,10 +957,14 @@ static int bindfs_open(const char *path, struct fuse_file_info *fi) { int fd; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; - fd = open(path, fi->flags); + fd = open(real_path, fi->flags); + free(real_path); if (fd == -1) return -errno; @@ -811,10 +1009,14 @@ static int bindfs_statfs(const char *path, struct statvfs *stbuf) { int res; + char *real_path; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; - res = statvfs(path, stbuf); + res = statvfs(real_path, stbuf); + free(real_path); if (res == -1) return -errno; @@ -864,13 +1066,16 @@ #endif { int res; + char *real_path; DPRINTF("setxattr %s %s=%s", path, name, value); if (settings.xattr_policy == XATTR_READ_ONLY) return -EACCES; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; #if defined(__APPLE__) if (!strncmp(name, XATTR_APPLE_PREFIX, sizeof(XATTR_APPLE_PREFIX) - 1)) { @@ -881,16 +1086,17 @@ char new_name[MAXPATHLEN]; memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); - res = setxattr(path, new_name, value, size, position, flags); + res = setxattr(real_path, new_name, value, size, position, flags); } else { - res = setxattr(path, name, value, size, position, flags); + res = setxattr(real_path, name, value, size, position, flags); } #elif defined(HAVE_LSETXATTR) - res = lsetxattr(path, name, value, size, flags); + res = lsetxattr(real_path, name, value, size, flags); #else - res = setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW); + res = setxattr(real_path, name, value, size, 0, flags | XATTR_NOFOLLOW); #endif + free(real_path); if (res == -1) return -errno; return 0; @@ -905,25 +1111,29 @@ #endif { int res; + char *real_path; DPRINTF("getxattr %s %s", path, name); - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; #if defined(__APPLE__) if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) { char new_name[MAXPATHLEN]; memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); - res = getxattr(path, new_name, value, size, position, XATTR_NOFOLLOW); + res = getxattr(real_path, new_name, value, size, position, XATTR_NOFOLLOW); } else { - res = getxattr(path, name, value, size, position, XATTR_NOFOLLOW); + res = getxattr(real_path, name, value, size, position, XATTR_NOFOLLOW); } #elif defined(HAVE_LGETXATTR) - res = lgetxattr(path, name, value, size); + res = lgetxattr(real_path, name, value, size); #else - res = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); + res = getxattr(real_path, name, value, size, 0, XATTR_NOFOLLOW); #endif + free(real_path); if (res == -1) return -errno; return res; @@ -931,12 +1141,16 @@ static int bindfs_listxattr(const char *path, char* list, size_t size) { + char *real_path; + DPRINTF("listxattr %s", path); - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; #if defined(__APPLE__) - ssize_t res = listxattr(path, list, size, XATTR_NOFOLLOW); + ssize_t res = listxattr(real_path, list, size, XATTR_NOFOLLOW); if (res > 0) { if (list) { size_t len = 0; @@ -955,7 +1169,7 @@ // TODO: https://github.com/osxfuse/fuse/blob/master/example/fusexmp_fh.c // had this commented out bit here o_O /* - ssize_t res2 = getxattr(path, G_KAUTH_FILESEC_XATTR, NULL, 0, 0, + ssize_t res2 = getxattr(real_path, G_KAUTH_FILESEC_XATTR, NULL, 0, 0, XATTR_NOFOLLOW); if (res2 >= 0) { res -= sizeof(G_KAUTH_FILESEC_XATTR); @@ -964,10 +1178,11 @@ } } #elif defined(HAVE_LLISTXATTR) - int res = llistxattr(path, list, size); + int res = llistxattr(real_path, list, size); #else - int res = listxattr(path, list, size, XATTR_NOFOLLOW); + int res = listxattr(real_path, list, size, XATTR_NOFOLLOW); #endif + free(real_path); if (res == -1) return -errno; return res; @@ -976,29 +1191,33 @@ static int bindfs_removexattr(const char *path, const char *name) { int res; + char *real_path; DPRINTF("removexattr %s %s", path, name); if (settings.xattr_policy == XATTR_READ_ONLY) return -EACCES; - path = process_path(path); + real_path = process_path(path, true); + if (real_path == NULL) + return -errno; #if defined(__APPLE__) if (strcmp(name, A_KAUTH_FILESEC_XATTR) == 0) { char new_name[MAXPATHLEN]; memcpy(new_name, A_KAUTH_FILESEC_XATTR, sizeof(A_KAUTH_FILESEC_XATTR)); memcpy(new_name, G_PREFIX, sizeof(G_PREFIX) - 1); - res = removexattr(path, new_name, XATTR_NOFOLLOW); + res = removexattr(real_path, new_name, XATTR_NOFOLLOW); } else { - res = removexattr(path, name, XATTR_NOFOLLOW); + res = removexattr(real_path, name, XATTR_NOFOLLOW); } #elif defined(HAVE_LREMOVEXATTR) - res = lremovexattr(path, name); + res = lremovexattr(real_path, name); #else - res = removexattr(path, name, XATTR_NOFOLLOW); + res = removexattr(real_path, name, XATTR_NOFOLLOW); #endif + free(real_path); if (res == -1) return -errno; return 0; @@ -1106,6 +1325,8 @@ " --ctime-from-mtime Read file properties' change time\n" " from file content modification time.\n" " --hide-hard-links Always report a hard link count of 1.\n" + " --resolve-symlinks Resolve symbolic links.\n" + " --resolved-symlink-deletion=... Decide how to delete resolved symlinks.\n" " --multithreaded Enable multithreaded mode. See man page\n" " for security issue with current implementation.\n" "\n" @@ -1144,6 +1365,7 @@ OPTKEY_REALISTIC_PERMISSIONS, OPTKEY_CTIME_FROM_MTIME, OPTKEY_HIDE_HARD_LINKS, + OPTKEY_RESOLVE_SYMLINKS, OPTKEY_MULTITHREADED }; @@ -1225,13 +1447,27 @@ case OPTKEY_HIDE_HARD_LINKS: settings.hide_hard_links = 1; return 0; + case OPTKEY_RESOLVE_SYMLINKS: + settings.resolve_symlinks = 1; + return 0; case OPTKEY_NONOPTION: if (!settings.mntsrc) { - settings.mntsrc = arg; + settings.mntsrc = realpath(arg, NULL); + if (settings.mntsrc == NULL) { + fprintf(stderr, "Failed to resolve source directory `%s': ", arg); + perror(NULL); + return -1; + } return 0; } else if (!settings.mntdest) { - settings.mntdest = arg; + settings.mntdest = realpath(arg, NULL); + if (settings.mntdest == NULL) { + fprintf(stderr, "Failed to resolve mount point `%s': ", arg); + perror(NULL); + return -1; + } + settings.mntdest_len = strlen(settings.mntdest); return 1; /* leave this argument for fuse_main */ } else { fprintf(stderr, "Too many arguments given\n"); @@ -1250,7 +1486,7 @@ char *p, *tmpstr; settings.num_mirrored_users = count_chars(mirror, ',') + - count_chars(mirror, ':') + 1; + count_chars(mirror, ':') + 1; settings.num_mirrored_members = ((*mirror == '@') ? 1 : 0) + count_substrs(mirror, ",@") + count_substrs(mirror, ":@"); @@ -1432,6 +1668,8 @@ static void atexit_func() { + free(settings.mntsrc); + free(settings.mntdest); free(settings.original_working_dir); settings.original_working_dir = NULL; if (settings.read_limiter) { @@ -1480,6 +1718,7 @@ char *create_for_group; char *create_with_perms; char *chmod_filter; + char *resolved_symlink_deletion; int no_allow_other; int multithreaded; } od; @@ -1537,9 +1776,12 @@ OPT2("--xattr-ro", "xattr-ro", OPTKEY_XATTR_READ_ONLY), OPT2("--xattr-rw", "xattr-rw", OPTKEY_XATTR_READ_WRITE), + OPT2("--hide-hard-links", "hide-hard-links", OPTKEY_HIDE_HARD_LINKS), + OPT2("--resolve-symlinks", "resolve-symlinks", OPTKEY_RESOLVE_SYMLINKS), + OPT_OFFSET2("--resolved-symlink-deletion=%s", "resolved-symlink-deletion=%s", resolved_symlink_deletion, -1), + OPT2("--realistic-permissions", "realistic-permissions", OPTKEY_REALISTIC_PERMISSIONS), OPT2("--ctime-from-mtime", "ctime-from-mtime", OPTKEY_CTIME_FROM_MTIME), - OPT2("--hide-hard-links", "hide-hard-links", OPTKEY_HIDE_HARD_LINKS), OPT_OFFSET2("--multithreaded", "multithreaded", multithreaded, -1), FUSE_OPT_END }; @@ -1561,6 +1803,7 @@ settings.create_for_gid = -1; settings.mntsrc = NULL; settings.mntdest = NULL; + settings.mntdest_len = 0; settings.original_working_dir = get_working_dir(); settings.create_policy = (getuid() == 0) ? CREATE_AS_USER : CREATE_AS_MOUNTER; settings.create_permchain = permchain_create(); @@ -1575,9 +1818,11 @@ settings.num_mirrored_users = 0; settings.mirrored_members = NULL; settings.num_mirrored_members = 0; + settings.hide_hard_links = 0; + settings.resolve_symlinks = 0; + settings.resolved_symlink_deletion_policy = RESOLVED_SYMLINK_DELETION_SYMLINK_ONLY; settings.realistic_permissions = 0; settings.ctime_from_mtime = 0; - settings.hide_hard_links = 0; atexit(&atexit_func); /* Parse options */ @@ -1710,6 +1955,23 @@ return 1; } } + + + /* Parse resolved_symlink_deletion */ + if (od.resolved_symlink_deletion) { + if (strcmp(od.resolved_symlink_deletion, "deny") == 0) { + settings.resolved_symlink_deletion_policy = RESOLVED_SYMLINK_DELETION_DENY; + } else if (strcmp(od.resolved_symlink_deletion, "symlink-only") == 0) { + settings.resolved_symlink_deletion_policy = RESOLVED_SYMLINK_DELETION_SYMLINK_ONLY; + } else if (strcmp(od.resolved_symlink_deletion, "symlink-first") == 0) { + settings.resolved_symlink_deletion_policy = RESOLVED_SYMLINK_DELETION_SYMLINK_FIRST; + } else if (strcmp(od.resolved_symlink_deletion, "target-first") == 0) { + settings.resolved_symlink_deletion_policy = RESOLVED_SYMLINK_DELETION_TARGET_FIRST; + } else { + fprintf(stderr, "Invalid setting for --resolved-symlink-deletion: '%s'\n", od.resolved_symlink_deletion); + return 1; + } + } /* Single-threaded mode by default */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/tests/common.rb new/bindfs-1.13.0/tests/common.rb --- old/bindfs-1.12.7/tests/common.rb 2015-09-09 11:40:21.000000000 +0200 +++ new/bindfs-1.13.0/tests/common.rb 2015-09-27 00:11:40.000000000 +0200 @@ -179,6 +179,18 @@ end end +# Like testenv but skips the test if not running as non-root. +# TODO: make all tests runnable as root +def nonroot_testenv(bindfs_args, options = {}, &block) + if Process.uid != 0 + testenv(bindfs_args, options, &block) + else + puts "--- #{bindfs_args} ---" + puts "[ #{bindfs_args} ]" + puts "SKIP (requires running as non-root)" + end +end + def umount_cmd if `which fusermount`.strip.empty? then 'umount' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bindfs-1.12.7/tests/test_bindfs.rb new/bindfs-1.13.0/tests/test_bindfs.rb --- old/bindfs-1.12.7/tests/test_bindfs.rb 2015-09-09 11:40:21.000000000 +0200 +++ new/bindfs-1.13.0/tests/test_bindfs.rb 2015-09-27 00:11:40.000000000 +0200 @@ -373,7 +373,7 @@ testenv("", :title => "utimens on symlinks") do touch('mnt/file') Dir.chdir "mnt" do - system('ln -sf file link') + symlink('file', 'link') end system("#{$tests_dir}/utimens_nofollow mnt/link 12 34 56 78") @@ -385,6 +385,179 @@ assert { File.lstat('mnt/file').mtime.to_i > 100 } end +testenv("--resolve-symlinks", :title => "resolving symlinks") do + mkdir('src/dir') + File.write('src/dir/file', 'hello') + Dir.chdir 'src' do + symlink('dir', 'dirlink') + symlink('dir/file', 'filelink') + symlink('dirlink/file', 'filelink2') + end + + assert { !File.lstat('mnt/dirlink').symlink? } + assert { File.lstat('mnt/dirlink').directory? } + assert { !File.lstat('mnt/dirlink/file').symlink? } + assert { File.lstat('mnt/dirlink/file').file? } + assert { File.lstat('mnt/filelink').file? } + assert { File.read('mnt/filelink') == 'hello' } +end + +testenv("--resolve-symlinks", :title => "attributes of resolved symlinks") do + Dir.chdir 'src' do + touch('file') + symlink('file', 'link') + chmod(0654, 'file') + end + + assert { File.lstat('mnt/link').mode & 0777 == 0654 } +end + +testenv("--resolve-symlinks", :title => "writing through resolved symlinks") do + Dir.chdir 'src' do + File.write('file', 'initial_content') + symlink('file', 'link') + end + + File.write('mnt/link', 'new_content') + assert { File.read('src/file') == 'new_content' } + assert { File.read('src/link') == 'new_content' } + assert { File.symlink?('src/link') } +end + +testenv("--resolve-symlinks", :title => "moving over resolved symlinks") do + Dir.chdir 'src' do + File.write('file', 'initial_content') + File.write('newfile', 'new_content') + symlink('file', 'link') + end + + Dir.chdir 'mnt' do + system("mv newfile link") + end + assert { File.symlink?('src/link') } + assert { File.read('src/file') == 'new_content' } + assert { !File.exist?('src/newfile') } +end + +testenv("--resolve-symlinks", :title => "moving resolved symlinks") do + Dir.chdir 'src' do + touch('file') + symlink('file', 'link') + end + + Dir.chdir 'mnt' do + system("mv link lonk") + end + assert { !File.symlink?('src/link') } + assert { File.lstat('src/lonk').symlink? } + assert { File.readlink('src/lonk') == 'file' } +end + +testenv("--resolve-symlinks", :title => "--resolve-symlinks disallows new symlinks") do + touch('mnt/file') + Dir.chdir "mnt" do + begin + File.symlink("file", "link") + rescue Errno::EPERM => exception + end + assert { exception != nil } + end +end + +testenv("--resolve-symlinks", :title => "deleting a resolved symlink deletes the underlying symlink only by default") do + Dir.chdir 'src' do + touch('file') + symlink('file', 'link') + symlink('broken', 'broken_link') + end + + File.unlink('mnt/link') + assert { !File.symlink?('src/link') } + assert { File.exist?('src/file') } + + File.unlink('mnt/broken_link') + assert { !File.symlink?('src/broken_link') } +end + +testenv("--resolve-symlinks --resolved-symlink-deletion=deny") do + Dir.chdir 'src' do + touch('file') + symlink('file', 'link') + end + + begin + File.unlink('mnt/link') + rescue Errno::EPERM => exception + end + assert { exception != nil } + assert { File.symlink?('src/link') } + assert { File.exist?('src/file') } +end + +# TODO: make all tests runnable as root. This is nonroot because we can't +# easily prevent a bindfs running as root from deleting a file. +nonroot_testenv("--resolve-symlinks --resolved-symlink-deletion=symlink-first") do + begin + Dir.chdir 'src' do + mkdir('dir') + touch('deletable_file') + touch('dir/undeletable_file') + chmod(0555, 'dir') + symlink('deletable_file', 'link1') + symlink('dir/undeletable_file', 'link2') + symlink('broken', 'link3') + end + + File.unlink('mnt/link1') + assert { !File.symlink?('src/link1') } + assert { !File.exist?('src/dir/deletable_file') } + + File.unlink('mnt/link2') + assert { !File.symlink?('src/link2') } + assert { File.exist?('src/dir/undeletable_file') } + + File.unlink('mnt/link3') + assert { !File.symlink?('src/link3') } + ensure + chmod(0777, 'src/dir') # So the cleanup code can delete dir/* + end +end + +# TODO: make all tests runnable as root. This is nonroot because we can't +# easily prevent a bindfs running as root from deleting a file. +nonroot_testenv("--resolve-symlinks --resolved-symlink-deletion=target-first -p a+w") do + begin + Dir.chdir 'src' do + mkdir('dir') + touch('file1') + touch('file2') + symlink('file1', 'deletable_link') + Dir.chdir('dir') do + symlink('../file2', 'undeletable_link') + end + chmod(0555, 'dir') + symlink('broken', 'broken_link') + end + + File.unlink('mnt/deletable_link') + assert { !File.symlink?('src/deletable_link') } + assert { !File.exist?('src/file1') } + + begin + File.unlink('mnt/dir/undeletable_link') + rescue Errno::EACCES => exception + end + assert { exception != nil } + assert { File.symlink?('src/dir/undeletable_link') } + assert { !File.exist?('src/file2') } + + File.unlink('mnt/broken_link') + assert { !File.symlink?('src/broken_link') } + ensure + chmod(0777, 'src/dir') # So the cleanup code can delete dir/* + end +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