diff --git a/src/debug.h b/src/debug.h index b912bf91..88ea4929 100644 --- a/src/debug.h +++ b/src/debug.h @@ -8,7 +8,9 @@ #define DEBUG_H #include +#include #include "opts.h" +#include "general.h" #define DBG_IN() DBG("\n"); @@ -17,8 +19,12 @@ if (!uopt.debug) break; \ int _errno = errno; \ FILE* dbgfile = get_dbgfile(); \ - fprintf(stderr, "%s(): %d: ", __func__, __LINE__); \ - fprintf(dbgfile, "%s(): %d: ", __func__, __LINE__); \ + struct timeval _tv_dbg; \ + gettimeofday(&_tv_dbg, NULL); \ + print_iso8601(stderr, _tv_dbg); \ + print_iso8601(dbgfile, _tv_dbg); \ + fprintf(stderr, " %s(): %d: ", __func__, __LINE__); \ + fprintf(dbgfile, " %s(): %d: ", __func__, __LINE__); \ fprintf(stderr, format, ##__VA_ARGS__); \ fprintf(dbgfile, format, ##__VA_ARGS__); \ fflush(stderr); \ diff --git a/src/findbranch.c b/src/findbranch.c index 843c44fc..ac55c8bf 100644 --- a/src/findbranch.c +++ b/src/findbranch.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -139,10 +140,10 @@ int __find_rw_branch_cutlast(const char *path, int rw_hint) { if (branch < 0) goto out; // Reminder rw_hint == -1 -> autodetect, we do not care which branch it is - if (uopt.branches[branch].rw + if (uopt.branches[branch].rw && !uopt.even && (rw_hint == -1 || branch == rw_hint)) goto out; - if (!uopt.cow_enabled) { + if (!(uopt.cow_enabled || uopt.even)) { // So path exists, but is not writable. branch = -1; errno = EACCES; @@ -152,7 +153,7 @@ 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); + branch_rw = uopt.even ? find_evenly_rw_branch() : find_lowest_rw_branch(uopt.nbranches); else branch_rw = rw_hint; @@ -239,3 +240,48 @@ int find_lowest_rw_branch(int branch_ro) { RETURN(-1); } + +int find_evenly_rw_branch() { + DBG_IN(); + + int branch = -1; + uint64_t *spaces; + uint64_t choice; + uint64_t sum = 0; + + spaces = malloc(uopt.nbranches * sizeof(*spaces)); + if (!spaces) + goto out; + + int i = 0; + for (i = 0; i < uopt.nbranches; i++) { + struct statvfs stb; + uint64_t free; + int res = statvfs_local(uopt.branches[i].path, &stb); + if (res == -1) + goto out; + + free = uopt.branches[i].rw ? stb.f_frsize * stb.f_bavail : 0; + sum += free; + spaces[i] = sum; + DBG("branch = %d free = %"PRIu64" sum = %"PRIu64"\n", i, free, + sum); + } + if (sum == 0) + goto out; + + choice = randupto64(sum - 1); + DBG("choice = %"PRIu64"\n", choice); + + for (i = 0; i < uopt.nbranches; i++) { + if (spaces[i] > choice) { + branch = i; + break; + } + } + +out: + + free(spaces); + RETURN(branch); +} diff --git a/src/findbranch.h b/src/findbranch.h index 06362854..3d77e316 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_evenly_rw_branch(); 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/fuse_ops.c b/src/fuse_ops.c index 0e74ad08..ee072696 100644 --- a/src/fuse_ops.c +++ b/src/fuse_ops.c @@ -33,12 +33,6 @@ #include #include -#ifdef linux - #include -#else - #include -#endif - #ifdef HAVE_XATTR #include #endif @@ -511,52 +505,6 @@ static int unionfs_rename(const char *from, const char *to) { RETURN(0); } -/** - * Wrapper function to convert the result of statfs() to statvfs() - * libfuse uses statvfs, since it conforms to POSIX. Unfortunately, - * glibc's statvfs parses /proc/mounts, which then results in reading - * the filesystem itself again - which would result in a deadlock. - * TODO: BSD/MacOSX - */ -static int statvfs_local(const char *path, struct statvfs *stbuf) { -#ifdef linux - /* glibc's statvfs walks /proc/mounts and stats entries found there - * in order to extract their mount flags, which may deadlock if they - * are mounted under the unionfs. As a result, we have to do this - * ourselves. - */ - struct statfs stfs; - int res = statfs(path, &stfs); - if (res == -1) RETURN(res); - - memset(stbuf, 0, sizeof(*stbuf)); - stbuf->f_bsize = stfs.f_bsize; - if (stfs.f_frsize) { - stbuf->f_frsize = stfs.f_frsize; - } else { - stbuf->f_frsize = stfs.f_bsize; - } - stbuf->f_blocks = stfs.f_blocks; - stbuf->f_bfree = stfs.f_bfree; - stbuf->f_bavail = stfs.f_bavail; - stbuf->f_files = stfs.f_files; - stbuf->f_ffree = stfs.f_ffree; - stbuf->f_favail = stfs.f_ffree; /* nobody knows */ - - /* We don't worry about flags, exactly because this would - * require reading /proc/mounts, and avoiding that and the - * resulting deadlocks is exactly what we're trying to avoid - * by doing this rather than using statvfs. - */ - stbuf->f_flag = 0; - stbuf->f_namemax = stfs.f_namelen; - - RETURN(0); -#else - RETURN(statvfs(path, stbuf)); -#endif -} - /** * statvs implementation * diff --git a/src/general.c b/src/general.c index 2a8fdece..6017a11b 100644 --- a/src/general.c +++ b/src/general.c @@ -22,6 +22,13 @@ #include #include #include +#include + +#ifdef linux + #include +#else + #include +#endif #include "unionfs.h" #include "opts.h" @@ -213,3 +220,65 @@ int set_owner(const char *path) { } RETURN(0); } + +void print_iso8601(FILE *file, struct timeval tv) +{ + char timestampbuf[100]; + struct tm tm; + + localtime_r(&tv.tv_sec, &tm); + strftime(timestampbuf, sizeof("YYYY-MM-ddTHH:mm:ss.SSS+0000"), "%Y-%m-%dT%H:%M:%S.000%z", &tm); + sprintf(timestampbuf + 20, "%03ld%s", tv.tv_usec / 1000, timestampbuf + 23); + fprintf(file, timestampbuf); +} + +uint64_t randupto64(uint64_t max) +{ + return (uint64_t) (rand() / (double) ((uint64_t) RAND_MAX + 1) * (max + 1)); +} + +/** + * Wrapper function to convert the result of statfs() to statvfs() + * libfuse uses statvfs, since it conforms to POSIX. Unfortunately, + * glibc's statvfs parses /proc/mounts, which then results in reading + * the filesystem itself again - which would result in a deadlock. + * TODO: BSD/MacOSX + */ +int statvfs_local(const char *path, struct statvfs *stbuf) { +#ifdef linux + /* glibc's statvfs walks /proc/mounts and stats entries found there + * in order to extract their mount flags, which may deadlock if they + * are mounted under the unionfs. As a result, we have to do this + * ourselves. + */ + struct statfs stfs; + int res = statfs(path, &stfs); + if (res == -1) RETURN(res); + + memset(stbuf, 0, sizeof(*stbuf)); + stbuf->f_bsize = stfs.f_bsize; + if (stfs.f_frsize) { + stbuf->f_frsize = stfs.f_frsize; + } else { + stbuf->f_frsize = stfs.f_bsize; + } + stbuf->f_blocks = stfs.f_blocks; + stbuf->f_bfree = stfs.f_bfree; + stbuf->f_bavail = stfs.f_bavail; + stbuf->f_files = stfs.f_files; + stbuf->f_ffree = stfs.f_ffree; + stbuf->f_favail = stfs.f_ffree; /* nobody knows */ + + /* We don't worry about flags, exactly because this would + * require reading /proc/mounts, and avoiding that and the + * resulting deadlocks is exactly what we're trying to avoid + * by doing this rather than using statvfs. + */ + stbuf->f_flag = 0; + stbuf->f_namemax = stfs.f_namelen; + + RETURN(0); +#else + RETURN(statvfs(path, stbuf)); +#endif +} diff --git a/src/general.h b/src/general.h index 33a67c65..db7d5fc6 100644 --- a/src/general.h +++ b/src/general.h @@ -27,6 +27,9 @@ 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); +void print_iso8601(FILE *file, struct timeval tv); +uint64_t randupto64(uint64_t max); +int statvfs_local(const char *path, struct statvfs *stbuf); #endif diff --git a/src/opts.c b/src/opts.c index 8fcaed71..b57323d1 100644 --- a/src/opts.c +++ b/src/opts.c @@ -286,6 +286,9 @@ static void print_help(const char *progname) { " want to have a union of \"/\" \n" " -o cow enable copy-on-write\n" " mountpoint\n" + " -o even distribute file allocation evenly among\n" + " writable branches\n" + " the most free place\n" " -o debug_file file to write debug information into\n" " -o dirs=branch[=RO/RW][:branch...]\n" " alternate way to specify directories to merge\n" @@ -395,6 +398,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_EVEN_PLACEMENT: + uopt.even = 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 e1ce16c5..6accfaa5 100644 --- a/src/opts.h +++ b/src/opts.h @@ -30,6 +30,7 @@ typedef struct { pthread_rwlock_t dbgpath_lock; // locks dbgpath bool hide_meta_files; bool relaxed_permissions; + bool even; } uopt_t; @@ -45,6 +46,7 @@ enum { KEY_NOINITGROUPS, KEY_RELAXED_PERMISSIONS, KEY_STATFS_OMIT_RO, + KEY_EVEN_PLACEMENT, KEY_VERSION }; diff --git a/src/unionfs.c b/src/unionfs.c index 51684a76..da0fbf44 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("even", KEY_EVEN_PLACEMENT), FUSE_OPT_KEY("debug_file=%s", KEY_DEBUG_FILE), FUSE_OPT_KEY("dirs=%s", KEY_DIRS), FUSE_OPT_KEY("--help", KEY_HELP), @@ -88,6 +89,7 @@ int main(int argc, char *argv[]) { #endif umask(0); + srand(time(NULL)); int res = fuse_main(args.argc, args.argv, &unionfs_oper, NULL); RETURN(uopt.doexit ? uopt.retval : res); }