From 4784beca2eb9903b9ab614f0947eb9cb1b9bc8eb Mon Sep 17 00:00:00 2001 From: wangjl <2627812260@qq.com> Date: Mon, 30 May 2016 08:12:59 +0000 Subject: [PATCH] add -o lbwrite (balance write to all rw disks judge by free space) --- examples/fstab | 1 + mount.unionfs | 6 +-- src/findbranch.c | 39 ++++++++++++++++++- src/findbranch.h | 1 + src/opts.c | 4 ++ src/opts.h | 2 + src/unionfs.c | 1 + test | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ test.py | 90 +++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 examples/fstab create mode 100755 test diff --git a/examples/fstab b/examples/fstab new file mode 100644 index 00000000..f85e96bc --- /dev/null +++ b/examples/fstab @@ -0,0 +1 @@ +/data/backup1=RW:/data/backup2=RW /data/backup unionfs rw,cow,max_files=32768,allow_other,use_ino,suid,dev,nonempty,lbwrite 0 0 diff --git a/mount.unionfs b/mount.unionfs index 8df47091..4b0d54dd 100755 --- a/mount.unionfs +++ b/mount.unionfs @@ -1,9 +1,9 @@ #!/bin/bash - -label=$1 +export PATH=/sbin:/usr/local/bin:/bin:/usr/bin +src=$1 mpoint=$2 shift 3 dirs=${@##*,dirs=} dirs=${dirs/,/:} -mount.fuse "unionfs#$dirs" "$mpoint" -o ${@%%,dirs=*} +mount.fuse "unionfs#$src" "$mpoint" -o ${@%%,dirs=*} diff --git a/src/findbranch.c b/src/findbranch.c index 5c085635..676644bd 100644 --- a/src/findbranch.c +++ b/src/findbranch.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "unionfs.h" #include "opts.h" @@ -137,9 +138,10 @@ int __find_rw_branch_cutlast(const char *path, int rw_hint) { // Reminder rw_hint == -1 -> autodetect, we do not care which branch it is if (uopt.branches[branch].rw + && uopt.lbwrite == false && (rw_hint == -1 || branch == rw_hint)) goto out; - if (!uopt.cow_enabled) { + if (!uopt.cow_enabled && !uopt.lbwrite) { // So path exists, but is not writable. branch = -1; errno = EACCES; @@ -149,7 +151,12 @@ int __find_rw_branch_cutlast(const char *path, int rw_hint) { int branch_rw; // since it is a directory, any rw-branch is fine if (rw_hint == -1) - branch_rw = find_lowest_rw_branch(uopt.nbranches); + if (uopt.lbwrite == true){ + branch_rw = find_loadlowest_rw_branch(uopt.nbranches); + } + else{ + branch_rw = find_lowest_rw_branch(uopt.nbranches); + } else branch_rw = rw_hint; @@ -236,3 +243,31 @@ int find_lowest_rw_branch(int branch_ro) { RETURN(-1); } + +/** + * Find load lowest possible writable branch but only lower than branch_ro. + */ +int find_loadlowest_rw_branch(int branch_ro) { + DBG_IN(); + struct statfs stb; + int res, retVal = -1; + unsigned long long free_space,max_free_space = 0; + + int i = 0; + for (i = 0; i < branch_ro; i++) { + res = statfs(uopt.branches[i].path, &stb); + if (res == -1) { + retVal = -errno; + break; + } + free_space=stb.f_bsize*stb.f_bfree; + DBG("branch %s free space %llu\n",uopt.branches[i].path,free_space); + if ( free_space > max_free_space ){ + retVal = i; + max_free_space = free_space; + } + } + + if (retVal >= 0) RETURN(retVal); // found it it. + RETURN(-1); +} diff --git a/src/findbranch.h b/src/findbranch.h index 06362854..e0fd7b99 100644 --- a/src/findbranch.h +++ b/src/findbranch.h @@ -14,6 +14,7 @@ typedef enum searchflag { int find_rorw_branch(const char *path); int find_lowest_rw_branch(int branch_ro); +int find_loadlowest_rw_branch(int branch_ro); int find_rw_branch_cutlast(const char *path); int __find_rw_branch_cutlast(const char *path, int rw_hint); int find_rw_branch_cow(const char *path); diff --git a/src/opts.c b/src/opts.c index a7fd2cce..cb222498 100644 --- a/src/opts.c +++ b/src/opts.c @@ -296,6 +296,7 @@ static void print_help(const char *progname) { " -o relaxed_permissions Disable permissions checks, but only if\n" " running neither as UID=0 or GID=0\n" " -o statfs_omit_ro do not count blocks of ro-branches\n" + " -o lbwrite write to each disks load balancely\n" "\n", progname); } @@ -395,6 +396,9 @@ int unionfs_opt_proc(void *data, const char *arg, int key, struct fuse_args *out case KEY_RELAXED_PERMISSIONS: uopt.relaxed_permissions = true; return 0; + case KEY_LBWRITE: + uopt.lbwrite = true; + return 0; case KEY_VERSION: printf("unionfs-fuse version: "VERSION"\n"); #ifdef HAVE_XATTR diff --git a/src/opts.h b/src/opts.h index 78ac1f8c..55a34933 100644 --- a/src/opts.h +++ b/src/opts.h @@ -30,10 +30,12 @@ typedef struct { pthread_rwlock_t dbgpath_lock; // locks dbgpath bool hide_meta_files; bool relaxed_permissions; + bool lbwrite; } uopt_t; enum { + KEY_LBWRITE, KEY_CHROOT, KEY_COW, KEY_DEBUG_FILE, diff --git a/src/unionfs.c b/src/unionfs.c index 54cdcae0..a4c4fb4d 100644 --- a/src/unionfs.c +++ b/src/unionfs.c @@ -68,6 +68,7 @@ #endif static struct fuse_opt unionfs_opts[] = { + FUSE_OPT_KEY("lbwrite", KEY_LBWRITE), FUSE_OPT_KEY("chroot=%s,", KEY_CHROOT), FUSE_OPT_KEY("cow", KEY_COW), FUSE_OPT_KEY("debug_file=%s", KEY_DEBUG_FILE), diff --git a/test b/test new file mode 100755 index 00000000..bbe78b13 --- /dev/null +++ b/test @@ -0,0 +1,98 @@ +#!/bin/bash +# this is a legacy version (will be removed in the future). please use test.py. + +set -v +set -e + +rm -rf original union working-copy +mkdir original union working-copy original/play-dir original/del-dir +echo v1 > original/file +echo v1 > original/play-with-me +echo v1 > original/delete-me + +cleanup() { + if [ -e "union" ]; then fusermount -u -q union; fi + rm -rf union original working-copy +} +trap cleanup EXIT + +src/unionfs -d -o cow working-copy=rw:original=ro union >unionfs.log 2>&1 & + +sleep 1 + +[ "$(cat union/file)" = "v1" ] + +echo "v2" > union/file +[ "$(cat union/file)" = "v2" ] + +echo "v2" > union/play-with-me +[ "$(cat union/play-with-me)" = "v2" ] + +[ -f union/play-with-me ] +rm union/play-with-me +[ ! -f union/play-with-me ] + +[ -f union/delete-me ] +rm union/delete-me +[ ! -f union/delete-me ] + +[ "$(ls union/play-dir)" = "" ] +echo "fool" > union/play-dir/foo +[ "$(ls union/play-dir)" = "foo" ] +rm union/play-dir/foo +[ "$(ls union/play-dir)" = "" ] + +[ -d union/play-dir ] +rmdir union/play-dir +[ ! -d union/play-dir ] + +[ -d union/del-dir ] +rmdir union/del-dir +[ ! -d union/del-dir ] + +! echo v1 > union/del-dir/foo + +[ ! -d union/del-dir ] +mkdir union/del-dir +[ ! -f union/del-dir/foo ] +echo v1 > union/del-dir/foo +[ -f union/del-dir/foo ] +rm union/del-dir/foo +[ -d union/del-dir ] +rmdir union/del-dir +[ ! -d union/del-dir ] + +# rmdir() test +set +e +set +v +rc=0 +mkdir original/testdir +touch original/testdir/testfile +mkdir working-copy/testdir +rmdir union/testdir 2>/dev/null +if [ $? -eq 0 ]; then + echo "rmdir succeeded, although it must not" + rc=$(($rc + $?)) +fi +rm union/testdir/testfile +rc=$(($rc + $?)) +rmdir union/testdir/ +rc=$(($rc + $?)) +if [ $rc -ne 0 ]; then + echo "rmdir test failed" + exit 1 +else + echo "rmdir test passed" +fi +set -e + +fusermount -u union + +[ "$(cat original/file)" = "v1" ] +[ "$(cat original/play-with-me)" = "v1" ] +[ "$(cat original/delete-me)" = "v1" ] +[ -d original/play-dir ] +[ -d original/del-dir ] +[ "$(cat working-copy/file)" = "v2" ] + +echo "ALL TEST PASSED" diff --git a/test.py b/test.py index de1ddee6..8e7a4dbf 100755 --- a/test.py +++ b/test.py @@ -25,7 +25,6 @@ def read_from_file(fn): def get_dir_contents(directory): return [dirs for (_, dirs, _) in os.walk(directory)] - class Common: def setUp(self): self.unionfs_path = os.path.abspath('src/unionfs') @@ -69,6 +68,57 @@ def tearDown(self): shutil.rmtree(self.tmpdir) +class CommonWithImg: + def setUp(self): + self.unionfs_path = os.path.abspath('src/unionfs') + self.unionfsctl_path = os.path.abspath('src/unionfsctl') + + self.tmpdir = tempfile.mkdtemp() + #self.tmpdir = "/tmp/123" + self.original_cwd = os.getcwd() + os.chdir(self.tmpdir) + + self._dirs = ['ro1', 'ro2', 'rw1', 'rw2'] + + for d in self._dirs: + #call("dd if=/dev/zero of=%s/%s.img bs=4M count=25" % self.tmpdir,d) + call('dd if=/dev/zero of=%s/%s.img bs=4M count=5 2>/dev/null' % (str(self.tmpdir),d)) + call('mkfs.ext4 %s/%s.img 2>/dev/null' % (self.tmpdir,d)) + os.mkdir(d) + call('mount -o loop %s/%s.img %s/%s' % (self.tmpdir,d, self.tmpdir,d)) + call('rm -rf %s/%s/*' % (self.tmpdir,d)) + write_to_file('%s/%s_file' % (d, d), d) + write_to_file('%s/common_file' % d, d) + + write_to_file('ro1/ro_common_file', 'ro1') + write_to_file('ro2/ro_common_file', 'ro2') + write_to_file('rw1/rw_common_file', 'rw1') + write_to_file('rw2/rw_common_file', 'rw2') + + os.mkdir('union') + + def tearDown(self): + # In User Mode Linux, fusermount -u union fails with a permission error + # when trying to lock the fuse lock file. + + if os.environ.get('RUNNING_ON_TRAVIS_CI'): + # TODO: investigate the following + # the sleep seems to be needed for some users or else the umount fails + # anyway, everything works fine on my system, so why wait? ;-) + # if it fails for someone, let's find the race and fix it! + # actually had to re-enable it because travis-ci is one of the bad cases + time.sleep(1) + + call('umount union') + else: + call('fusermount -u union') + + os.chdir(self.original_cwd) + for d in self._dirs: + call("umount %s/%s" % (self.tmpdir,d)) + + shutil.rmtree(self.tmpdir) + class UnionFS_RO_RO_TestCase(Common, unittest.TestCase): def setUp(self): @@ -274,6 +324,44 @@ def test_posix_operations(self): op(union) self.assertNotEqual(get_dir_contents(union), get_dir_contents(cow_path)) +class UnionFS_LBWRITE_TestCase(CommonWithImg, unittest.TestCase): + def setUp(self): + super().setUp() + call('%s -o lbwrite rw1=rw:rw2=rw union' % self.unionfs_path) + + def test_listing(self): + lst = ['rw1_file', 'rw2_file', 'rw_common_file', 'common_file'] + self.assertEqual(set(lst), set(os.listdir('union'))) + + def test_delete(self): + os.remove('union/rw1_file') + + self.assertNotIn('rw1_file', os.listdir('union')) + self.assertNotIn('rw1_file', os.listdir('rw1')) + + def test_write(self): + write_to_file('union/rw1_file', 'something') + + self.assertEqual(read_from_file('union/rw1_file'), 'something') + self.assertEqual(read_from_file('rw1/rw1_file'), 'something') + + def test_write_new(self): + write_to_file('union/new_file1', 'something1') + write_to_file('union/new_file2', 'something2') + self.assertEqual(read_from_file('union/new_file1'), 'something1') + self.assertEqual(read_from_file('union/new_file2'), 'something2') + self.assertEqual(read_from_file('rw1/new_file1'), 'something1') + self.assertEqual(read_from_file('rw2/new_file2'), 'something2') + self.assertNotIn('new_file1', os.listdir('rw2')) + self.assertNotIn('new_file2', os.listdir('rw1')) + + def test_rename(self): + os.rename('union/rw1_file', 'union/rw1_file_renamed') + self.assertEqual(read_from_file('union/rw1_file_renamed'), 'rw1') + + def test_copystat(self): + shutil.copystat('union/rw1_file', 'union/rw2_file') + class UnionFS_RO_RW_TestCase(Common, unittest.TestCase): def setUp(self):