Skip to content

Commit

Permalink
add -o lbwrite (balance write to all rw disks judge by free space)
Browse files Browse the repository at this point in the history
  • Loading branch information
yogoloth committed May 30, 2016
1 parent 1650977 commit 4784bec
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 6 deletions.
1 change: 1 addition & 0 deletions examples/fstab
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions mount.unionfs
Original file line number Diff line number Diff line change
@@ -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=*}

39 changes: 37 additions & 2 deletions src/findbranch.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/vfs.h>

#include "unionfs.h"
#include "opts.h"
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions src/findbranch.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/opts.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/opts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/unionfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
98 changes: 98 additions & 0 deletions test
Original file line number Diff line number Diff line change
@@ -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"
90 changes: 89 additions & 1 deletion test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 4784bec

Please sign in to comment.