summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRadek Podgorny <radek@podgorny.cz>2022-04-09 00:20:16 +0200
committerRadek Podgorny <radek@podgorny.cz>2022-04-09 00:20:16 +0200
commitf02afdc9fc67335ed848fc2b546b497d473a3637 (patch)
treed3bc7d491cac5a5bfda2c0ae5b73f056b05447d5
parent9becf9437f344df4891073994d79ae91a58343fb (diff)
parenta64d7963ad176192312b40b479cf436643b01bc0 (diff)
downloadunionfs-fuse-git-f02afdc9fc67335ed848fc2b546b497d473a3637.tar.gz
Merge remote-tracking branch 'brian/bribri' into brian
-rw-r--r--CMakeLists.txt6
-rw-r--r--src/cow.c83
-rw-r--r--src/cow.h4
-rw-r--r--src/findbranch.c44
-rw-r--r--src/findbranch.h1
-rw-r--r--src/fuse_ops.c24
-rw-r--r--src/general.c95
-rw-r--r--src/general.h2
-rw-r--r--src/opts.c6
-rw-r--r--src/opts.h2
-rw-r--r--src/unionfs.c1
-rw-r--r--src/version.h2
-rwxr-xr-xtest_all.py148
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)
diff --git a/src/cow.c b/src/cow.c
index c4b7f8f..dcee6f9 100644
--- a/src/cow.c
+++ b/src/cow.c
@@ -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);
}
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 <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
diff --git a/src/opts.c b/src/opts.c
index f6c381d..6b444f6 100644
--- a/src/opts.c
+++ b/src/opts.c
@@ -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;
diff --git a/src/opts.h b/src/opts.h
index e1ce16c..de96845 100644
--- a/src/opts.h
+++ b/src/opts.h
@@ -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()