diff options
author | Radek Podgorny <radek@podgorny.cz> | 2022-04-09 00:20:16 +0200 |
---|---|---|
committer | Radek Podgorny <radek@podgorny.cz> | 2022-04-09 00:20:16 +0200 |
commit | f02afdc9fc67335ed848fc2b546b497d473a3637 (patch) | |
tree | d3bc7d491cac5a5bfda2c0ae5b73f056b05447d5 | |
parent | 9becf9437f344df4891073994d79ae91a58343fb (diff) | |
parent | a64d7963ad176192312b40b479cf436643b01bc0 (diff) | |
download | unionfs-fuse-git-f02afdc9fc67335ed848fc2b546b497d473a3637.tar.gz |
Merge remote-tracking branch 'brian/bribri' into brian
-rw-r--r-- | CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/cow.c | 83 | ||||
-rw-r--r-- | src/cow.h | 4 | ||||
-rw-r--r-- | src/findbranch.c | 44 | ||||
-rw-r--r-- | src/findbranch.h | 1 | ||||
-rw-r--r-- | src/fuse_ops.c | 24 | ||||
-rw-r--r-- | src/general.c | 95 | ||||
-rw-r--r-- | src/general.h | 2 | ||||
-rw-r--r-- | src/opts.c | 6 | ||||
-rw-r--r-- | src/opts.h | 2 | ||||
-rw-r--r-- | src/unionfs.c | 1 | ||||
-rw-r--r-- | src/version.h | 2 | ||||
-rwxr-xr-x | test_all.py | 148 |
13 files changed, 324 insertions, 94 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 4726324..b513a0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,12 @@ ELSE (WITH_XATTR) add_definitions(-DDISABLE_XATTR) ENDIF (WITH_XATTR) +if (UNIX AND APPLE) + find_library(MACFUSE_PATH fuse HINTS /usr/local/lib) + get_filename_component(MACFUSE_DIRECTORY ${MACFUSE_PATH} DIRECTORY) + link_directories(${MACFUSE_DIRECTORY}) +endif() + INSTALL(PROGRAMS mount.unionfs DESTINATION sbin) add_subdirectory(src) @@ -28,95 +28,28 @@ /** - * 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); } @@ -10,8 +10,8 @@ #include <sys/stat.h> 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 843c44f..f61668d 100644 --- a/src/findbranch.c +++ b/src/findbranch.c @@ -51,6 +51,46 @@ #include "debug.h" #include "usyslog.h" +static bool branch_contains_path(int branch, const char *path, bool *is_dir) { + if (branch < 0 || branch >= uopt.nbranches) + RETURN(false); + + char p[PATHLEN_MAX]; + if (BUILD_PATH(p, uopt.branches[branch].path, path)) { + errno = ENAMETOOLONG; + RETURN(false); + } + + printf("***** p: %s\n", p); + struct stat stbuf; + int res = lstat(p, &stbuf); + + if (res == 0) { + (*is_dir) = S_ISDIR(stbuf.st_mode); + RETURN(true); + } else + RETURN(false); +} + +bool branch_contains_file_or_parent_dir(int branch, const char *path) { + bool is_dir = false; + bool found = branch_contains_path(branch, path, &is_dir); + + if (found) + RETURN(true); + + char *dname = u_dirname(path); + if (dname == NULL) { + errno = ENOMEM; + RETURN(false); + } + + found = branch_contains_path(branch, dname, &is_dir); + + free(dname); + RETURN(found && is_dir); +} + /** * Find a branch that has "path". Return the branch number. */ @@ -165,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); @@ -190,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/findbranch.h b/src/findbranch.h index 0636285..8393602 100644 --- a/src/findbranch.h +++ b/src/findbranch.h @@ -12,6 +12,7 @@ typedef enum searchflag { RWONLY } searchflag_t; +bool branch_contains_file_or_parent_dir(int branch, const char *path); int find_rorw_branch(const char *path); int find_lowest_rw_branch(int branch_ro); int find_rw_branch_cutlast(const char *path); diff --git a/src/fuse_ops.c b/src/fuse_ops.c index 915ceaa..d378ace 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); @@ -490,6 +491,28 @@ static int unionfs_rename(const char *from, const char *to, unsigned int flags) int i = find_rorw_branch(from); if (i == -1) RETURN(-errno); + if (uopt.preserve_branch && uopt.branches[i].rw) { + int existing = find_rorw_branch(to); + + if (existing != -1 && existing != i) { + USYSLOG(LOG_ERR, "%s: from %s would overwrite to on a different branch, which" + "is not supported.\n", __func__, from); + RETURN(-EXDEV); + } + + if (branch_contains_file_or_parent_dir(i, to)) { + DBG("preserving branch\n"); + j = i; + } else { + DBG("preserving branch and creating directories to do so\n"); + res = path_create_cutlast(to, j, i); + + if (res == 0) { + j = i; + } + } + } + if (!uopt.branches[i].rw) { i = find_rw_branch_cow_common(from, true); if (i == -1) RETURN(-errno); @@ -511,7 +534,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 @@ -280,7 +280,8 @@ static void print_help(const char *progname) { " -o chroot=path chroot into this path. Use this if you \n" " want to have a union of \"/\" \n" " -o cow enable copy-on-write\n" - " mountpoint\n" + " -o preserve_branch Preserve branch when moving files, creating\n" + " directories as needed\n" " -o debug_file=<fn> file to write debug information into\n" " -o dirs=branch[=RO/RW][:branch...]\n" " alternate way to specify directories to merge\n" @@ -365,6 +366,9 @@ int unionfs_opt_proc(void *data, const char *arg, int key, struct fuse_args *out case KEY_COW: uopt.cow_enabled = true; return 0; + case KEY_PRESERVE_BRANCH: + uopt.preserve_branch = true; + return 0; case KEY_DEBUG_FILE: uopt.dbgpath = get_opt_str(arg, "debug_file"); uopt.debug = true; @@ -21,6 +21,7 @@ typedef struct { branch_entry_t *branches; bool cow_enabled; + bool preserve_branch; bool statfs_omit_ro; int doexit; int retval; @@ -36,6 +37,7 @@ typedef struct { enum { KEY_CHROOT, KEY_COW, + KEY_PRESERVE_BRANCH, KEY_DEBUG_FILE, KEY_DIRS, KEY_HELP, diff --git a/src/unionfs.c b/src/unionfs.c index 51684a7..9dd0586 100644 --- a/src/unionfs.c +++ b/src/unionfs.c @@ -24,6 +24,7 @@ static struct fuse_opt unionfs_opts[] = { FUSE_OPT_KEY("chroot=%s,", KEY_CHROOT), FUSE_OPT_KEY("cow", KEY_COW), + FUSE_OPT_KEY("preserve_branch", KEY_PRESERVE_BRANCH), FUSE_OPT_KEY("debug_file=%s", KEY_DEBUG_FILE), FUSE_OPT_KEY("dirs=%s", KEY_DIRS), FUSE_OPT_KEY("--help", KEY_HELP), diff --git a/src/version.h b/src/version.h index 4d23795..32f9c14 100644 --- a/src/version.h +++ b/src/version.h @@ -4,5 +4,5 @@ */ #ifndef _VERSION_H -#define VERSION "2.3" +#define VERSION "2.3-Bri.1" #endif diff --git a/test_all.py b/test_all.py index 97bcc44..b4dc744 100755 --- a/test_all.py +++ b/test_all.py @@ -11,7 +11,7 @@ import platform def call(cmd): - return subprocess.check_output(cmd, shell=True) + return subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) def write_to_file(fn, data): @@ -90,11 +90,11 @@ class Common: if platform.system() == 'Darwin': # Need to get the unionfs device name so that we can unmount it later: prev_mounts = get_osxfuse_unionfs_mounts() - call(cmd) + call('%s -o nobrowse %s' % (self.unionfs_path, cmd)) cur_mounts = get_osxfuse_unionfs_mounts() self.mount_device = list(set(cur_mounts)-set(prev_mounts))[0] else: - call(cmd) + call('%s %s' % (self.unionfs_path, cmd)) self.mounted = True @@ -114,7 +114,7 @@ class UnionFS_Version(Common, unittest.TestCase): class UnionFS_Sync(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s ro1=ro:ro2=ro union' % self.unionfs_path) + self.mount('ro1=ro:ro2=ro union') def test_sync(self): call('sync union') @@ -123,7 +123,7 @@ class UnionFS_Sync(Common, unittest.TestCase): class UnionFS_RO_RO_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s -o cow ro1=ro:ro2=ro union' % self.unionfs_path) + self.mount('-o cow ro1=ro:ro2=ro union') def test_listing(self): lst = ['ro1_file', 'ro2_file', 'ro_common_file', 'common_file', 'ro1_dir', 'ro2_dir', 'common_dir', 'common_empty_dir', ] @@ -165,7 +165,7 @@ class UnionFS_RO_RO_TestCase(Common, unittest.TestCase): class UnionFS_RW_RO_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s rw1=rw:ro1=ro union' % self.unionfs_path) + self.mount('rw1=rw:ro1=ro union') def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file', 'ro1_dir', 'rw1_dir', 'common_dir', 'common_empty_dir', ] @@ -235,7 +235,7 @@ class UnionFS_RW_RO_TestCase(Common, unittest.TestCase): class UnionFS_RW_RO_COW_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s -o cow rw1=rw:ro1=ro union' % self.unionfs_path) + self.mount('-o cow rw1=rw:ro1=ro union') def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file', 'ro1_dir', 'rw1_dir', 'common_dir', 'common_empty_dir', ] @@ -367,7 +367,7 @@ class UnionFS_RW_RO_COW_TestCase(Common, unittest.TestCase): class UnionFS_RO_RW_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s ro1=ro:rw1=rw union' % self.unionfs_path) + self.mount('ro1=ro:rw1=rw union') def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file', 'ro1_dir', 'rw1_dir', 'common_dir', 'common_empty_dir', ] @@ -419,7 +419,7 @@ class UnionFS_RO_RW_TestCase(Common, unittest.TestCase): class UnionFS_RO_RW_COW_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s -o cow ro1=ro:rw1=rw union' % self.unionfs_path) + self.mount('-o cow ro1=ro:rw1=rw union') def test_listing(self): lst = ['ro1_file', 'rw1_file', 'ro_common_file', 'rw_common_file', 'common_file', 'ro1_dir', 'rw1_dir', 'common_dir', 'common_empty_dir', ] @@ -470,7 +470,7 @@ class UnionFS_RO_RW_COW_TestCase(Common, unittest.TestCase): class IOCTL_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s rw1=rw:ro1=ro union' % self.unionfs_path) + self.mount('rw1=rw:ro1=ro union') def test_debug(self): debug_fn = '%s/debug.log' % self.tmpdir @@ -497,7 +497,7 @@ class IOCTL_TestCase(Common, unittest.TestCase): class UnionFS_RW_RO_COW_RelaxedPermissions_TestCase(Common, unittest.TestCase): def setUp(self): super().setUp() - self.mount('%s -o cow,relaxed_permissions rw1=rw:ro1=ro union' % self.unionfs_path) + self.mount('-o cow,relaxed_permissions rw1=rw:ro1=ro union') def test_access(self): self.assertFalse(os.access('union/file', os.F_OK)) @@ -512,5 +512,131 @@ class UnionFS_RW_RO_COW_RelaxedPermissions_TestCase(Common, unittest.TestCase): self.assertFalse(os.access('union/file', os.X_OK)) +class UnionFS_RW_RW_PreserveBranch_TestCase(Common, unittest.TestCase): + def setUp(self): + super().setUp() + self.mount('-o preserve_branch rw1=rw:rw2=rw union') + + def test_file_move_from_low_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)) + os.rename('union/rw2_dir/rw2_file2', 'union/common_dir/rw2_file2') + self.assertFalse(os.access('rw2/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_file_move_from_high_branch_to_low_branch(self): + self.assertTrue(os.access('union/rw1_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('rw1/rw2_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('union/rw2_dir/rw1_file', os.F_OK)) + + def test_file_move_from_low_branch_to_high_branch(self): + self.assertTrue(os.access('union/rw2_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)) + + def test_file_move_to_nonexistent_path(self): + self.assertTrue(os.access('union/rw1_dir/rw1_file', os.F_OK)) + self.assertFalse(os.access('union/common_dir/new_dir', os.F_OK)) + with self.assertRaises(OSError): + os.rename('union/rw1_dir/rw1_file', 'union/common_dir/new_dir/rw1_file') + 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/common_dir/new_dir/rw1_file', os.F_OK)) + + def test_file_move_replace_in_single_branch(self): + write_to_file('rw1/rw1_file', 'rw1b') + self.assertTrue(os.access('union/rw1_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('union/rw1_file', os.F_OK)) + os.rename('union/rw1_dir/rw1_file', 'union/rw1_file') + self.assertFalse(os.access('rw1/rw1_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('rw1/rw1_file', os.F_OK)) + self.assertTrue(os.access('union/rw1_file', os.F_OK)) + self.assertEqual(read_from_file('union/rw1_file'), 'rw1') + + def test_file_move_replace_between_branches(self): + self.assertTrue(os.access('rw1/rw1_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('rw2/rw2_dir/rw2_file', os.F_OK)) + with self.assertRaises(OSError): + os.rename('union/rw2_dir/rw2_file', 'union/rw1_dir/rw1_file') + self.assertTrue(os.access('rw1/rw1_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('rw2/rw2_dir/rw2_file', os.F_OK)) + self.assertEqual(read_from_file('union/rw1_dir/rw1_file'), 'rw1') + + def test_folder_move_from_low_branch_to_common(self): + self.assertTrue(os.access('union/rw2_dir', os.F_OK)) + self.assertFalse(os.access('union/common_dir/rw2_dir', os.F_OK)) + os.rename('union/rw2_dir', 'union/common_dir/rw2_dir') + self.assertFalse(os.access('rw1/common_dir/rw2_dir', os.F_OK)) + self.assertFalse(os.access('rw2/rw2_dir', os.F_OK)) + self.assertTrue(os.access('rw2/common_dir/rw2_dir', os.F_OK)) + self.assertTrue(os.access('union/common_dir/rw2_dir', os.F_OK)) + + def test_folder_move_from_low_branch_to_high_branch(self): + self.assertTrue(os.access('union/rw2_dir', os.F_OK)) + self.assertFalse(os.access('union/rw1_dir/rw2_dir', os.F_OK)) + os.rename('union/rw2_dir', 'union/rw1_dir/rw2_dir') + self.assertFalse(os.access('rw1/rw1_dir/rw2_dir', os.F_OK)) + self.assertFalse(os.access('rw2/rw2_dir', os.F_OK)) + self.assertTrue(os.access('rw2/rw1_dir/rw2_dir', os.F_OK)) + self.assertTrue(os.access('union/rw1_dir/rw2_dir', os.F_OK)) + + def test_folder_move_from_high_branch_to_low_branch(self): + self.assertTrue(os.access('union/rw1_dir', os.F_OK)) + self.assertFalse(os.access('union/rw2_dir/rw1_dir', os.F_OK)) + os.rename('union/rw1_dir', 'union/rw2_dir/rw1_dir') + self.assertFalse(os.access('rw2/rw2_dir/rw1_dir', os.F_OK)) + self.assertFalse(os.access('rw1/rw1_dir', os.F_OK)) + self.assertTrue(os.access('rw1/rw2_dir/rw1_dir', os.F_OK)) + self.assertTrue(os.access('union/rw2_dir/rw1_dir', os.F_OK)) + + def test_permissions_after_creating_directories(self): + self.assertTrue(os.access('union/rw2_dir/rw2_file', os.F_OK)) + self.assertFalse(os.access('union/rw1_dir/rw2_file', os.F_OK)) + self.assertNotEqual(oct(os.stat('union/rw1_dir').st_mode)[-3:], '760') + os.chmod('union/rw1_dir', 0o760); + self.assertEqual(oct(os.stat('rw1/rw1_dir').st_mode)[-3:], '760') + self.assertEqual(oct(os.stat('union/rw1_dir').st_mode)[-3:], '760') + os.rename('union/rw2_dir/rw2_file', 'union/rw1_dir/rw2_file') + self.assertEqual(oct(os.stat('rw2/rw1_dir').st_mode)[-3:], '760') + self.assertEqual(oct(os.stat('union/rw1_dir').st_mode)[-3:], '760') + 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)) + + def test_file_move_without_access(self): + self.assertTrue(os.access('union/rw1_dir/rw1_file', os.F_OK)) + os.chmod('union/rw2_dir', 0o500); + try: + self.assertFalse(os.access('union/rw2_dir/rw1_file', os.F_OK)) + with self.assertRaises(PermissionError): + os.rename('union/rw1_dir/rw1_file', 'union/rw2_dir/rw1_file') + self.assertTrue(os.access('union/rw1_dir/rw1_file', os.F_OK)) + self.assertFalse(os.access('union/rw2_dir/rw1_file', os.F_OK)) + finally: + # Ensure teardown can delete the files it needs to: + os.chmod('union/rw2_dir', 0o700); + + def test_file_move_with_access(self): + os.mkdir('rw1/rw2_dir') + os.chmod('rw2/rw2_dir', 0o500); + try: + self.assertTrue(os.access('union/rw1_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('rw1/rw2_dir/rw1_file', os.F_OK)) + self.assertTrue(os.access('union/rw2_dir/rw1_file', os.F_OK)) + finally: + # Ensure teardown can delete the files it needs to: + os.chmod('rw2/rw2_dir', 0o700); + + if __name__ == '__main__': unittest.main() |