aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Bill Zissimooulos <billziss@navimatics.com>2018-05-12 18:51:44 -0700
committerGravatar Nikolaus Rath <Nikolaus@rath.org>2018-05-18 12:52:10 +0100
commitd105fafabbd01d39dd98d4e7d24e41da5cb2680e (patch)
treedfd561c3891d3a90856d6a7ec360ea922fe545f1
parent88da32d665eb3372f590675a5642c52f11c3ca03 (diff)
rename: perform user mode dir loop check when not done in kernel
Linux performs the dir loop check (rename(a, a/b/c) or rename(a/b/c, a), etc.) in kernel. Unfortunately other systems do not perform this check (e.g. FreeBSD). This results in a deadlock in get_path2, because libfuse did not expect to handle such cases. We add a check_dir_loop function that performs the dir loop check in user mode and enable it on systems that need it.
-rw-r--r--lib/fuse.c62
-rw-r--r--test/test_syscalls.c216
2 files changed, 275 insertions, 3 deletions
diff --git a/lib/fuse.c b/lib/fuse.c
index 2fb942c..7072fb0 100644
--- a/lib/fuse.c
+++ b/lib/fuse.c
@@ -1248,6 +1248,49 @@ static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
return get_path_common(f, nodeid, name, path, wnode);
}
+#if defined(__FreeBSD__)
+#define CHECK_DIR_LOOP true
+#else
+#define CHECK_DIR_LOOP false
+#endif
+
+static int check_dir_loop(struct fuse *f,
+ fuse_ino_t nodeid1, const char *name1,
+ fuse_ino_t nodeid2, const char *name2)
+{
+ struct node *node, *node1, *node2;
+ fuse_ino_t id1, id2;
+
+ node1 = lookup_node(f, nodeid1, name1);
+ id1 = node1 ? node1->nodeid : nodeid1;
+
+ node2 = lookup_node(f, nodeid2, name2);
+ id2 = node2 ? node2->nodeid : nodeid2;
+
+ for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+ node = node->parent) {
+ if (node->name == NULL || node->parent == NULL)
+ break;
+
+ if (node->nodeid != id2 && node->nodeid == id1)
+ return -EINVAL;
+ }
+
+ if (node2)
+ {
+ for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+ node = node->parent) {
+ if (node->name == NULL || node->parent == NULL)
+ break;
+
+ if (node->nodeid != id1 && node->nodeid == id2)
+ return -ENOTEMPTY;
+ }
+ }
+
+ return 0;
+}
+
static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
fuse_ino_t nodeid2, const char *name2,
char **path1, char **path2,
@@ -1272,11 +1315,20 @@ static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
fuse_ino_t nodeid2, const char *name2,
char **path1, char **path2,
- struct node **wnode1, struct node **wnode2)
+ struct node **wnode1, struct node **wnode2,
+ bool dir_loop)
{
int err;
pthread_mutex_lock(&f->lock);
+
+ if (dir_loop)
+ {
+ err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+ if (err)
+ goto out_unlock;
+ }
+
err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
path1, path2, wnode1, wnode2);
if (err == -EAGAIN) {
@@ -1297,6 +1349,8 @@ static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
}
+
+out_unlock:
pthread_mutex_unlock(&f->lock);
return err;
@@ -2965,7 +3019,8 @@ static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
int err;
err = get_path2(f, olddir, oldname, newdir, newname,
- &oldpath, &newpath, &wnode1, &wnode2);
+ &oldpath, &newpath, &wnode1, &wnode2,
+ CHECK_DIR_LOOP);
if (!err) {
struct fuse_intr_data d;
err = 0;
@@ -3001,7 +3056,8 @@ static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
int err;
err = get_path2(f, ino, NULL, newparent, newname,
- &oldpath, &newpath, NULL, NULL);
+ &oldpath, &newpath, NULL, NULL,
+ false);
if (!err) {
struct fuse_intr_data d;
diff --git a/test/test_syscalls.c b/test/test_syscalls.c
index 392a210..38a37a1 100644
--- a/test/test_syscalls.c
+++ b/test/test_syscalls.c
@@ -1282,6 +1282,221 @@ static int test_rename_dir(void)
return 0;
}
+static int test_rename_dir_loop(void)
+{
+#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
+#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
+
+ char path[1024], path2[1024];
+ int err = 0;
+ int res;
+
+ start_test("rename dir loop");
+
+ res = create_dir(testdir, testdir_files);
+ if (res == -1)
+ return -1;
+
+ res = mkdir(PATH("a"), 0755);
+ if (res == -1) {
+ PERROR("mkdir");
+ goto fail;
+ }
+
+ res = rename(PATH("a"), PATH2("a"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ errno = 0;
+ res = rename(PATH("a"), PATH2("a/b"));
+ if (res == 0 || errno != EINVAL) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = mkdir(PATH("a/b"), 0755);
+ if (res == -1) {
+ PERROR("mkdir");
+ goto fail;
+ }
+
+ res = mkdir(PATH("a/b/c"), 0755);
+ if (res == -1) {
+ PERROR("mkdir");
+ goto fail;
+ }
+
+ errno = 0;
+ res = rename(PATH("a"), PATH2("a/b/c"));
+ if (res == 0 || errno != EINVAL) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ errno = 0;
+ res = rename(PATH("a"), PATH2("a/b/c/a"));
+ if (res == 0 || errno != EINVAL) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ errno = 0;
+ res = rename(PATH("a/b/c"), PATH2("a"));
+ if (res == 0 || errno != ENOTEMPTY) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = open(PATH("a/foo"), O_CREAT, 0644);
+ if (res == -1) {
+ PERROR("open");
+ goto fail;
+ }
+ close(res);
+
+ res = rename(PATH("a/foo"), PATH2("a/bar"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/bar"), PATH2("a/foo"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/foo"), PATH2("a/b/bar"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/b/bar"), PATH2("a/foo"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = open(PATH("a/bar"), O_CREAT, 0644);
+ if (res == -1) {
+ PERROR("open");
+ goto fail;
+ }
+ close(res);
+
+ res = rename(PATH("a/foo"), PATH2("a/bar"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ unlink(PATH("a/bar"));
+
+ res = rename(PATH("a/b"), PATH2("a/d"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/d"), PATH2("a/b"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = mkdir(PATH("a/d"), 0755);
+ if (res == -1) {
+ PERROR("mkdir");
+ goto fail;
+ }
+
+ res = rename(PATH("a/b"), PATH2("a/d"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = rename(PATH("a/d"), PATH2("a/b"));
+ if (res == -1) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ res = mkdir(PATH("a/d"), 0755);
+ if (res == -1) {
+ PERROR("mkdir");
+ goto fail;
+ }
+
+ res = mkdir(PATH("a/d/e"), 0755);
+ if (res == -1) {
+ PERROR("mkdir");
+ goto fail;
+ }
+
+ errno = 0;
+ res = rename(PATH("a/b"), PATH2("a/d"));
+ if (res == 0 || errno != ENOTEMPTY) {
+ PERROR("rename");
+ goto fail;
+ }
+
+ rmdir(PATH("a/d/e"));
+ rmdir(PATH("a/d"));
+
+ rmdir(PATH("a/b/c"));
+ rmdir(PATH("a/b"));
+ rmdir(PATH("a"));
+
+ err += cleanup_dir(testdir, testdir_files, 0);
+ res = rmdir(testdir);
+ if (res == -1) {
+ PERROR("rmdir");
+ goto fail;
+ }
+ res = check_nonexist(testdir);
+ if (res == -1)
+ return -1;
+ if (err)
+ return -1;
+
+ success();
+ return 0;
+
+fail:
+ unlink(PATH("a/bar"));
+
+ rmdir(PATH("a/d/e"));
+ rmdir(PATH("a/d"));
+
+ rmdir(PATH("a/b/c"));
+ rmdir(PATH("a/b"));
+ rmdir(PATH("a"));
+
+ cleanup_dir(testdir, testdir_files, 1);
+ rmdir(testdir);
+
+ return -1;
+
+#undef PATH2
+#undef PATH
+}
+
#ifndef __FreeBSD__
static int test_mkfifo(void)
{
@@ -1461,6 +1676,7 @@ int main(int argc, char *argv[])
err += test_mkdir();
err += test_rename_file();
err += test_rename_dir();
+ err += test_rename_dir_loop();
err += test_utime();
err += test_truncate(0);
err += test_truncate(testdatalen / 2);