rename: perform user mode dir loop check when not done in kernel
authorBill Zissimopoulos <billziss@navimatics.com>
Tue, 5 Jun 2018 22:29:57 +0000 (15:29 -0700)
committerNikolaus Rath <Nikolaus@rath.org>
Thu, 7 Jun 2018 09:17:12 +0000 (10:17 +0100)
    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.

ChangeLog
lib/fuse.c
test/test.c

index 8371584e8a77535b0aea405bdbbae2fa0be5e9b6..5a7e4c89db254c0a66f239b9a4151136f84b5a0d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+FUSE 2.9.8
+==========
+
+* Fixed rename deadlock on FreeBSD.
+
 FUSE 2.9.7 (2016-06-20)
 =======================
 
index fa0a8149bcdaf74df2d076e9b0e0e1097d990a21..5f7ed5c5dcb451b4c07913503d03b64d5fb95c21 100644 (file)
@@ -1201,6 +1201,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
+#endif
+
+#if defined(CHECK_DIR_LOOP)
+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;
+}
+#endif
+
 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,
@@ -1230,6 +1273,17 @@ static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
        int err;
 
        pthread_mutex_lock(&f->lock);
+
+#if defined(CHECK_DIR_LOOP)
+       if (name1)
+       {
+               // called during rename; perform dir loop check
+               err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+               if (err)
+                       goto out_unlock;
+       }
+#endif
+
        err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
                            path1, path2, wnode1, wnode2);
        if (err == -EAGAIN) {
@@ -1250,6 +1304,10 @@ 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);
        }
+
+#if defined(CHECK_DIR_LOOP)
+out_unlock:
+#endif
        pthread_mutex_unlock(&f->lock);
 
        return err;
index c421cda923414b65e7ced933e1ccfbffc0a308ba..2d3ec72296f10b294a5f39e516cd5592c2c3c888 100644 (file)
@@ -1270,6 +1270,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
+}
+
 static int test_mkfifo(void)
 {
        int res;
@@ -1425,6 +1640,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);