rename: perform user mode dir loop check when not done in kernel
authorBill Zissimooulos <billziss@navimatics.com>
Sun, 13 May 2018 01:51:44 +0000 (18:51 -0700)
committerNikolaus Rath <Nikolaus@rath.org>
Fri, 18 May 2018 11:52:10 +0000 (12:52 +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.

lib/fuse.c
test/test_syscalls.c

index 2fb942ceb6759748dcd4a74a389df02922c035ea..7072fb067eb9b0997ca0fa8a042c70ca13c8526b 100644 (file)
@@ -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;
 
index 392a21071a4bd66786e53436e3af3bc8324a8694..38a37a1b37cc3f7ad49520e2c40aea524c0cdab2 100644 (file)
@@ -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);