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 - jengelh@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 .
#
@@ -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.partel@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.partel@gmail.com])
+AC_INIT([bindfs],[1.13.0],[martin.partel@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
#include
#include
+#include
#include
#include
#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