From a1fd187e7aba6447dae71730e24bc84341def1d4 Mon Sep 17 00:00:00 2001 From: Brian Kendall Date: Fri, 1 Apr 2022 12:28:38 -0400 Subject: Create directories as needed when renaming with preserve_branch --- src/cow.c | 83 +++++-------------------------------------------- src/cow.h | 4 +-- src/findbranch.c | 4 +-- src/fuse_ops.c | 13 ++++---- src/general.c | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/general.h | 2 ++ test_all.py | 21 ++++++++++--- 7 files changed, 131 insertions(+), 91 deletions(-) diff --git a/src/cow.c b/src/cow.c index c4b7f8f..dcee6f9 100644 --- a/src/cow.c +++ b/src/cow.c @@ -27,96 +27,29 @@ #include "usyslog.h" -/** - * Actually create the directory here. - */ -static int do_create(const char *path, int nbranch_ro, int nbranch_rw) { - DBG("%s\n", path); - - char dirp[PATHLEN_MAX]; // dir path to create - sprintf(dirp, "%s%s", uopt.branches[nbranch_rw].path, path); - - struct stat buf; - int res = stat(dirp, &buf); - if (res != -1) RETURN(0); // already exists - - if (nbranch_ro == nbranch_rw) { - // special case nbranch_ro = nbranch_rw, this is if we a create - // unionfs meta directories, so not directly on cow operations - buf.st_mode = S_IRWXU | S_IRWXG; - } else { - // data from the ro-branch - char o_dirp[PATHLEN_MAX]; // the pathname we want to copy - sprintf(o_dirp, "%s%s", uopt.branches[nbranch_ro].path, path); - res = stat(o_dirp, &buf); - if (res == -1) RETURN(1); // lower level branch removed in the mean time? - } - - res = mkdir(dirp, buf.st_mode); - if (res == -1) { - USYSLOG(LOG_DAEMON, "Creating %s failed: \n", dirp); - RETURN(1); - } - - if (nbranch_ro == nbranch_rw) RETURN(0); // the special case again - - if (setfile(dirp, &buf)) RETURN(1); // directory already removed by another process? - - // TODO: time, but its values are modified by the next dir/file creation steps? - - RETURN(0); -} - /** * l_nbranch (lower nbranch than nbranch) is write protected, create the dir path on * nbranch for an other COW operation. */ -int path_create(const char *path, int nbranch_ro, int nbranch_rw) { +int path_create_cow(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); if (!uopt.cow_enabled) RETURN(0); - char p[PATHLEN_MAX]; - if (BUILD_PATH(p, uopt.branches[nbranch_rw].path, path)) RETURN(-ENAMETOOLONG); - - struct stat st; - if (!stat(p, &st)) { - // path does already exists, no need to create it - RETURN(0); - } - - char *walk = (char *)path; - - // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ - while (*walk == '/') walk++; - - do { - // walk over the directory name, walk will now be /dir2 - while (*walk != '\0' && *walk != '/') walk++; - - // +1 due to \0, which gets added automatically - snprintf(p, (walk - path) + 1, "%s", path); // walk - path = strlen(/dir1) - int res = do_create(p, nbranch_ro, nbranch_rw); - if (res) RETURN(res); // creating the directory failed - - // as above the do loop, walk over the next slashes, walk = dir2/ - while (*walk == '/') walk++; - } while (*walk != '\0'); - - RETURN(0); + return path_create(path, nbranch_ro, nbranch_rw); } /** - * Same as path_create(), but ignore the last segment in path, + * Same as path_create_cow(), but ignore the last segment in path, * i.e. it might be a filename. */ -int path_create_cutlast(const char *path, int nbranch_ro, int nbranch_rw) { +int path_create_cutlast_cow(const char *path, int nbranch_ro, int nbranch_rw) { DBG("%s\n", path); char *dname = u_dirname(path); if (dname == NULL) RETURN(-ENOMEM); - int ret = path_create(dname, nbranch_ro, nbranch_rw); + int ret = path_create_cow(dname, nbranch_ro, nbranch_rw); free(dname); RETURN(ret); @@ -129,7 +62,7 @@ int cow_cp(const char *path, int branch_ro, int branch_rw, bool copy_dir) { DBG("%s\n", path); // create the path to the file - path_create_cutlast(path, branch_ro, branch_rw); + path_create_cutlast_cow(path, branch_ro, branch_rw); char from[PATHLEN_MAX], to[PATHLEN_MAX]; if (BUILD_PATH(from, uopt.branches[branch_ro].path, path)) @@ -163,7 +96,7 @@ int cow_cp(const char *path, int branch_ro, int branch_rw, bool copy_dir) { if (copy_dir) { res = copy_directory(path, branch_ro, branch_rw); } else { - res = path_create(path, branch_ro, branch_rw); + res = path_create_cow(path, branch_ro, branch_rw); } break; case S_IFBLK: @@ -190,7 +123,7 @@ int copy_directory(const char *path, int branch_ro, int branch_rw) { DBG("%s\n", path); /* create the directory on the destination branch */ - int res = path_create(path, branch_ro, branch_rw); + int res = path_create_cow(path, branch_ro, branch_rw); if (res != 0) { RETURN(res); } diff --git a/src/cow.h b/src/cow.h index 04d6dfb..389224a 100644 --- a/src/cow.h +++ b/src/cow.h @@ -10,8 +10,8 @@ #include int cow_cp(const char *path, int branch_ro, int branch_rw, bool copy_dir); -int path_create(const char *path, int nbranch_ro, int nbranch_rw); -int path_create_cutlast(const char *path, int nbranch_ro, int nbranch_rw); +int path_create_cow(const char *path, int nbranch_ro, int nbranch_rw); +int path_create_cutlast_cow(const char *path, int nbranch_ro, int nbranch_rw); int copy_directory(const char *path, int branch_ro, int branch_rw); #endif diff --git a/src/findbranch.c b/src/findbranch.c index af526ea..f61668d 100644 --- a/src/findbranch.c +++ b/src/findbranch.c @@ -205,7 +205,7 @@ int __find_rw_branch_cutlast(const char *path, int rw_hint) { goto out; } - if (path_create(dname, branch, branch_rw) == 0) branch = branch_rw; // path successfully copied + if (path_create_cow(dname, branch, branch_rw) == 0) branch = branch_rw; // path successfully copied out: free(dname); @@ -230,7 +230,7 @@ int find_rw_branch_cow(const char *path) { * copy-on-write * Find path in a union branch and if this branch is read-only, * copy the file to a read-write branch. - * NOTE: Don't call this to copy directories. Use path_create() for that! + * NOTE: Don't call this to copy directories. Use path_create_cow() for that! * It will definitely fail, when a ro-branch is on top of a rw-branch * and a directory is to be copied from ro- to rw-branch. */ diff --git a/src/fuse_ops.c b/src/fuse_ops.c index 394c81b..adec4e5 100644 --- a/src/fuse_ops.c +++ b/src/fuse_ops.c @@ -482,6 +482,7 @@ static int unionfs_rename(const char *from, const char *to, unsigned int flags) DBG("from %s to %s\n", from, to); + int res; bool is_dir = false; // is 'from' a file or directory int j = find_rw_branch_cutlast(to); @@ -492,15 +493,14 @@ static int unionfs_rename(const char *from, const char *to, unsigned int flags) if (uopt.preserve_branch && uopt.branches[i].rw) { if (branch_contains_file_or_parent_dir(i, to)) { - DBG("...from branch contains target directory, no need to change branches\n"); + DBG("preserving branch\n"); j = i; } else { - DBG("...creating target directory in from branch, no need to change branches\n"); - - if (path_create_cutlast(to, j, i) == 0) { + DBG("preserving branch and creating directories to do so\n"); + res = path_create_cutlast(to, j, i); + + if (res == 0) { j = i; - } else { - DBG("...failed to create path in from branch\n"); } } } @@ -526,7 +526,6 @@ static int unionfs_rename(const char *from, const char *to, unsigned int flags) else if (ftype == IS_DIR) is_dir = true; - int res; if (!uopt.branches[i].rw) { // since original file is on a read-only branch, we copied the from file to a writable branch, // but since we will rename from, we also need to hide the from file on the read-only branch diff --git a/src/general.c b/src/general.c index 779bde5..47ab062 100644 --- a/src/general.c +++ b/src/general.c @@ -27,6 +27,7 @@ #include "opts.h" #include "string.h" #include "cow.h" +#include "cow_utils.h" #include "findbranch.h" #include "general.h" #include "debug.h" @@ -143,7 +144,7 @@ static int do_create_whiteout(const char *path, int branch_rw, enum whiteout mod // p MUST be without path to branch prefix here! 2 x branch_rw is correct here! // this creates e.g. branch/.unionfs/some_directory - path_create_cutlast(metapath, branch_rw, branch_rw); + path_create_cutlast_cow(metapath, branch_rw, branch_rw); char p[PATHLEN_MAX]; if (BUILD_PATH(p, uopt.branches[branch_rw].path, metapath)) RETURN(-1); @@ -213,3 +214,95 @@ int set_owner(const char *path) { } RETURN(0); } + +/** + * Actually create the directory here. + */ +static int do_create(const char *path, int nbranch_ro, int nbranch_rw) { + DBG("%s\n", path); + + char dirp[PATHLEN_MAX]; // dir path to create + sprintf(dirp, "%s%s", uopt.branches[nbranch_rw].path, path); + + struct stat buf; + int res = stat(dirp, &buf); + if (res != -1) RETURN(0); // already exists + + if (nbranch_ro == nbranch_rw) { + // special case nbranch_ro = nbranch_rw, this is if we a create + // unionfs meta directories, so not directly on cow operations + buf.st_mode = S_IRWXU | S_IRWXG; + } else { + // data from the ro-branch + char o_dirp[PATHLEN_MAX]; // the pathname we want to copy + sprintf(o_dirp, "%s%s", uopt.branches[nbranch_ro].path, path); + res = stat(o_dirp, &buf); + if (res == -1) RETURN(1); // lower level branch removed in the mean time? + } + + res = mkdir(dirp, buf.st_mode); + if (res == -1) { + USYSLOG(LOG_DAEMON, "Creating %s failed: \n", dirp); + RETURN(1); + } + + if (nbranch_ro == nbranch_rw) RETURN(0); // the special case again + + if (setfile(dirp, &buf)) RETURN(1); // directory already removed by another process? + + // TODO: time, but its values are modified by the next dir/file creation steps? + + RETURN(0); +} + +/** + * create the dir path on nbranch_rw matching same path on nbranch_ro + */ +int path_create(const char *path, int nbranch_ro, int nbranch_rw) { + DBG("%s\n", path); + + char p[PATHLEN_MAX]; + if (BUILD_PATH(p, uopt.branches[nbranch_rw].path, path)) RETURN(-ENAMETOOLONG); + + struct stat st; + if (!stat(p, &st)) { + // path does already exists, no need to create it + RETURN(0); + } + + char *walk = (char *)path; + + // first slashes, e.g. we have path = /dir1/dir2/, will set walk = dir1/dir2/ + while (*walk == '/') walk++; + + do { + // walk over the directory name, walk will now be /dir2 + while (*walk != '\0' && *walk != '/') walk++; + + // +1 due to \0, which gets added automatically + snprintf(p, (walk - path) + 1, "%s", path); // walk - path = strlen(/dir1) + int res = do_create(p, nbranch_ro, nbranch_rw); + if (res) RETURN(res); // creating the directory failed + + // as above the do loop, walk over the next slashes, walk = dir2/ + while (*walk == '/') walk++; + } while (*walk != '\0'); + + RETURN(0); +} + +/** + * Same as path_create(), but ignore the last segment in path, + * i.e. it might be a filename. + */ +int path_create_cutlast(const char *path, int nbranch_ro, int nbranch_rw) { + DBG("%s\n", path); + + char *dname = u_dirname(path); + if (dname == NULL) + RETURN(-ENOMEM); + int ret = path_create(dname, nbranch_ro, nbranch_rw); + free(dname); + + RETURN(ret); +} \ No newline at end of file diff --git a/src/general.h b/src/general.h index 33a67c6..996a168 100644 --- a/src/general.h +++ b/src/general.h @@ -27,6 +27,8 @@ int hide_dir(const char *path, int branch_rw); filetype_t path_is_dir (const char *path); int maybe_whiteout(const char *path, int branch_rw, enum whiteout mode); int set_owner(const char *path); +int path_create(const char *path, int nbranch_ro, int nbranch_rw); +int path_create_cutlast(const char *path, int nbranch_ro, int nbranch_rw); #endif diff --git a/test_all.py b/test_all.py index 742e423..8999ea8 100755 --- a/test_all.py +++ b/test_all.py @@ -516,8 +516,8 @@ class UnionFS_RW_RW_PreserveBranch_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() self.mount('%s -o preserve_branch rw1=rw:rw2=rw union' % self.unionfs_path) - - def test_move_from_branch2_to_common(self): + + def test_move_from_branch_to_common(self): write_to_file('rw2/rw2_dir/rw2_file2', 'something') self.assertTrue(os.access('union/rw2_dir/rw2_file2', os.F_OK)) self.assertFalse(os.access('union/common_dir/rw2_file2', os.F_OK)) @@ -527,16 +527,29 @@ class UnionFS_RW_RW_PreserveBranch_TestCase(Common, unittest.TestCase): self.assertFalse(os.access('union/rw2_dir/rw2_file2', os.F_OK)) self.assertTrue(os.access('rw2/common_dir/rw2_file2', os.F_OK)) self.assertTrue(os.access('union/common_dir/rw2_file2', os.F_OK)) - + def test_move_from_branch1_to_branch2(self): self.assertTrue(os.access('rw1/rw1_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('union/rw1_dir/rw1_file', os.F_OK)) self.assertFalse(os.access('rw2/rw2_dir/rw1_file', os.F_OK)) + self.assertFalse(os.access('rw1/rw2_dir/rw1_file', os.F_OK)) self.assertFalse(os.access('union/rw2_dir/rw1_file', os.F_OK)) os.rename('union/rw1_dir/rw1_file', 'union/rw2_dir/rw1_file') self.assertFalse(os.access('rw1/rw1_dir/rw1_file', os.F_OK)) - self.assertTrue(os.access('rw2/rw2_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('rw1/rw2_dir/rw1_file', os.F_OK)) self.assertTrue(os.access('union/rw2_dir/rw1_file', os.F_OK)) + def test_move_from_branch2_to_branch1(self): + self.assertTrue(os.access('rw2/rw2_dir/rw2_file', os.F_OK)) + self.assertTrue(os.access('union/rw2_dir/rw2_file', os.F_OK)) + self.assertFalse(os.access('rw1/rw1_dir/rw2_file', os.F_OK)) + self.assertFalse(os.access('rw2/rw1_dir/rw2_file', os.F_OK)) + self.assertFalse(os.access('union/rw1_dir/rw2_file', os.F_OK)) + os.rename('union/rw2_dir/rw2_file', 'union/rw1_dir/rw2_file') + self.assertFalse(os.access('rw2/rw2_dir/rw2_file', os.F_OK)) + self.assertTrue(os.access('rw2/rw1_dir/rw2_file', os.F_OK)) + self.assertTrue(os.access('union/rw1_dir/rw2_file', os.F_OK)) + if __name__ == '__main__': unittest.main() -- cgit v1.2.1