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.
This commit is contained in:
Bill Zissimooulos
2018-05-12 18:51:44 -07:00
committed by Nikolaus Rath
parent 88da32d665
commit d105fafabb
2 changed files with 275 additions and 3 deletions
+59 -3
View 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;
+216
View 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);