diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/bindfs.1 | 24 | ||||
-rw-r--r-- | src/bindfs.c | 75 | ||||
-rw-r--r-- | src/misc.c | 24 | ||||
-rw-r--r-- | src/misc.h | 4 | ||||
-rw-r--r-- | src/rate_limiter.c | 108 | ||||
-rw-r--r-- | src/rate_limiter.h | 51 |
7 files changed, 276 insertions, 14 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 60cdbf0..c049cc3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,8 +2,8 @@ bin_PROGRAMS = bindfs -noinst_HEADERS = debug.h permchain.h userinfo.h misc.h usermap.h -bindfs_SOURCES = bindfs.c debug.c permchain.c userinfo.c misc.c usermap.c +noinst_HEADERS = debug.h permchain.h userinfo.h misc.h usermap.h rate_limiter.h +bindfs_SOURCES = bindfs.c debug.c permchain.c userinfo.c misc.c usermap.c rate_limiter.c AM_CPPFLAGS = ${my_CPPFLAGS} ${fuse_CFLAGS} AM_CFLAGS = ${my_CFLAGS} diff --git a/src/bindfs.1 b/src/bindfs.1 index b183a78..8ed5d2f 100644 --- a/src/bindfs.1 +++ b/src/bindfs.1 @@ -204,6 +204,23 @@ The read/write permissions are checked against the (possibly modified) file permissions inside the mount. +.SH RATE LIMITS +Reads and writes through the mount point can be throttled. Throttling works +by sleeping the required amount of time on each read or write request. +Throttling imposes one global limit on all readers/writers as opposed to a +per-process or per-user limit. + +Currently, the implementation is not entirely fair. See \fB\%BUGS\fP below. + +.TP +.B \-\-read\-rate=\fIN\fP, \-o read\-rate=\fIN\fP +Allow at most \fIN\fP bytes per second to be read. \fIN\fP may have one of the +following (1024-based) suffixes: \fBk\fP, \fBM\fP, \fBG\fP, \fBT\fP. + +.TP +.B \-\-write\-rate=\fIN\fP, \-o write\-rate=\fIN\fP +Same as above, but for writes. + .SH MISCELLANEOUS OPTIONS .TP @@ -375,6 +392,13 @@ This may constitute a security risk if you rely on bindfs to reduce permissions on new files. For this reason, as of version 1.11 bindfs runs in single-threaded mode by default. +Rate limiting favors the process with the larger block size. +If two processes compete for read/write access, the one whose read()/write() +calls specify the larger block size gets to read/write faster. +The total rate limit is maintained though, and clients with equal block sizes +and a similar rate of requests are treated fairly as long as the kernel orders +their requests fairly. + Please report bugs and/or send pull requests to \fBhttps://github.com/mpartel/bindfs/issues\fP. diff --git a/src/bindfs.c b/src/bindfs.c index e94402d..1ca24ed 100644 --- a/src/bindfs.c +++ b/src/bindfs.c @@ -68,10 +68,11 @@ #include <fuse_opt.h> #include "debug.h" +#include "misc.h" #include "permchain.h" +#include "rate_limiter.h" #include "userinfo.h" #include "usermap.h" -#include "misc.h" /* SETTINGS */ static struct Settings { @@ -91,6 +92,9 @@ static struct Settings { UserMap* usermap; /* From the --map option. */ UserMap* usermap_reverse; + RateLimiter* read_limiter; + RateLimiter* write_limiter; + enum CreatePolicy { CREATE_AS_USER, CREATE_AS_MOUNTER @@ -766,6 +770,10 @@ static int bindfs_read(const char *path, char *buf, size_t size, off_t offset, int res; (void) path; + if (settings.read_limiter) { + rate_limiter_wait(settings.read_limiter, size); + } + res = pread(fi->fh, buf, size, offset); if (res == -1) res = -errno; @@ -779,6 +787,10 @@ static int bindfs_write(const char *path, const char *buf, size_t size, int res; (void) path; + if (settings.write_limiter) { + rate_limiter_wait(settings.write_limiter, size); + } + res = pwrite(fi->fh, buf, size, offset); if (res == -1) res = -errno; @@ -956,24 +968,24 @@ static void print_usage(const char *progname) " -V --version Print version number and exit.\n" "\n" "File ownership:\n" - " -u --force-user Set file owner.\n" - " -g --force-group Set file group.\n" - " -m --mirror Comma-separated list of users who will see\n" + " -u --force-user=... Set file owner.\n" + " -g --force-group=... Set file group.\n" + " -m --mirror=... Comma-separated list of users who will see\n" " themselves as the owners of all files.\n" - " -M --mirror-only Like --mirror but disallow access for\n" + " -M --mirror-only=... Like --mirror but disallow access for\n" " all other users.\n" " --map=user1/user2:... Let user2 see files of user1 as his own.\n" "\n" "Permission bits:\n" - " -p --perms Specify permissions, similar to chmod\n" + " -p --perms=... Specify permissions, similar to chmod\n" " e.g. og-x,og+rD,u=rwX,g+rw or 0644,a+X\n" "\n" "File creation policy:\n" " --create-as-user New files owned by creator (default for root). *\n" " --create-as-mounter New files owned by fs mounter (default for users).\n" - " --create-for-user New files owned by specified user. *\n" - " --create-for-group New files owned by specified group. *\n" - " --create-with-perms Alter permissions of new files.\n" + " --create-for-user=... New files owned by specified user. *\n" + " --create-for-group=... New files owned by specified group. *\n" + " --create-with-perms=... Alter permissions of new files.\n" "\n" "Chown policy:\n" " --chown-normal Try to chown the original files (the default).\n" @@ -989,7 +1001,7 @@ static void print_usage(const char *progname) " --chmod-normal Try to chmod the original files (the default).\n" " --chmod-ignore Have all chmods fail silently.\n" " --chmod-deny Have all chmods fail with 'permission denied'.\n" - " --chmod-filter Change permissions of chmod requests.\n" + " --chmod-filter=... Change permissions of chmod requests.\n" " --chmod-allow-x Allow changing file execute bits in any case.\n" "\n" "Extended attribute policy:\n" @@ -997,6 +1009,10 @@ static void print_usage(const char *progname) " --xattr-ro Read-only xattr operations.\n" " --xattr-rw Read-write xattr operations (the default).\n" "\n" + "Rate limits:\n" + " --read-rate=... Limit to bytes/sec that can be read.\n" + " --write-rate=... Limit to bytes/sec that can be written.\n" + "\n" "Miscellaneous:\n" " -n --no-allow-other Do not add -o allow_other to fuse options.\n" " --realistic-permissions Hide permission bits for actions mounter can't do.\n" @@ -1331,6 +1347,16 @@ static void atexit_func() { free(settings.original_working_dir); settings.original_working_dir = NULL; + if (settings.read_limiter) { + rate_limiter_destroy(settings.read_limiter); + free(settings.read_limiter); + settings.read_limiter = NULL; + } + if (settings.write_limiter) { + rate_limiter_destroy(settings.write_limiter); + free(settings.write_limiter); + settings.write_limiter = NULL; + } usermap_destroy(settings.usermap); settings.usermap = NULL; usermap_destroy(settings.usermap_reverse); @@ -1361,6 +1387,8 @@ int main(int argc, char *argv[]) char *mirror; char *mirror_only; char *map; + char *read_rate; + char *write_rate; char *create_for_user; char *create_for_group; char *create_with_perms; @@ -1395,6 +1423,9 @@ int main(int argc, char *argv[]) OPT_OFFSET2("--map=%s", "map=%s", map, -1), OPT_OFFSET3("-n", "--no-allow-other", "no-allow-other", no_allow_other, -1), + OPT_OFFSET2("--read-rate=%s", "read-rate=%s", read_rate, -1), + OPT_OFFSET2("--write-rate=%s", "write-rate=%s", write_rate, -1), + OPT2("--create-as-user", "create-as-user", OPTKEY_CREATE_AS_USER), OPT2("--create-as-mounter", "create-as-mounter", OPTKEY_CREATE_AS_MOUNTER), OPT_OFFSET2("--create-for-user=%s", "create-for-user=%s", create_for_user, -1), @@ -1435,6 +1466,8 @@ int main(int argc, char *argv[]) settings.permchain = permchain_create(); settings.usermap = usermap_create(); settings.usermap_reverse = usermap_create(); + settings.read_limiter = NULL; + settings.write_limiter = NULL; settings.new_uid = -1; settings.new_gid = -1; settings.create_for_uid = -1; @@ -1500,6 +1533,28 @@ int main(int argc, char *argv[]) } } + /* Parse rate limits */ + if (od.read_rate) { + double rate; + if (parse_byte_count(od.read_rate, &rate) && rate > 0) { + settings.read_limiter = malloc(sizeof(RateLimiter)); + rate_limiter_init(settings.read_limiter, rate, &gettimeofday_clock); + } else { + fprintf(stderr, "Error: Invalid --read-rate.\n"); + return 1; + } + } + if (od.write_rate) { + double rate; + if (parse_byte_count(od.write_rate, &rate) && rate > 0) { + settings.write_limiter = malloc(sizeof(RateLimiter)); + rate_limiter_init(settings.write_limiter, rate, &gettimeofday_clock); + } else { + fprintf(stderr, "Error: Invalid --write-rate.\n"); + return 1; + } + } + /* Parse usermap */ if (od.map) { if (getuid() != 0) { @@ -108,7 +108,7 @@ const char *my_dirname(char *path) } } -void grow_array_impl(void **array, int* capacity, int member_size) +void grow_array_impl(void **array, int *capacity, int member_size) { int new_cap = *capacity; if (new_cap == 0) { @@ -121,6 +121,28 @@ void grow_array_impl(void **array, int* capacity, int member_size) *capacity = new_cap; } +int parse_byte_count(const char *str, double *result) +{ + char* end; + double base = strtod(str, &end); + long mul = 1; + if (*end == '\0') { + mul = 1L; + } else if (strcmp(end, "k") == 0) { + mul = 1024L; + } else if (strcmp(end, "M") == 0) { + mul = 1024L * 1024L; + } else if (strcmp(end, "G") == 0) { + mul = 1024L * 1024L * 1024L; + } else if (strcmp(end, "T") == 0) { + mul = 1024L * 1024L * 1024L * 1024L; + } else { + return 0; + } + *result = base * mul; + return 1; +} + void init_arena(struct arena *a, int initial_capacity) { a->size = 0; @@ -50,8 +50,10 @@ const char *my_dirname(char *path); than `*capacity` (may be 0) and stores the new capacity in `*capacity`. */ #define grow_array(array, capacity, member_size) grow_array_impl((void**)(array), (capacity), (member_size)) -void grow_array_impl(void **array, int* capacity, int member_size); +void grow_array_impl(void **array, int *capacity, int member_size); +/* Returns 1 on success, 0 on syntax error. */ +int parse_byte_count(const char *str, double *result); /* Simple arena allocation for when it's convenient to grow multiple times and deallocate all at once. */ diff --git a/src/rate_limiter.c b/src/rate_limiter.c new file mode 100644 index 0000000..d1c701a --- /dev/null +++ b/src/rate_limiter.c @@ -0,0 +1,108 @@ +/* + Copyright 2014 Martin Pärtel <martin.partel@gmail.com> + + This file is part of bindfs. + + bindfs is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + bindfs is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with bindfs. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define _POSIX_C_SOURCE 199309L /* for nanosleep() */ + +#include "rate_limiter.h" +#include <assert.h> +#include <errno.h> +#include <pthread.h> +#include <time.h> +#include <sys/time.h> + +const double rate_limiter_idle_credit = -0.2; + +double gettimeofday_clock() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec * 0.000001; +} + +static void sleep_seconds(double s) +{ + struct timespec ts; + ts.tv_sec = (time_t)s; + ts.tv_nsec = (long)((s - ts.tv_sec) * 1000000000L); + /* Guard against float imprecision */ + if (ts.tv_nsec > 999999999L) { + ts.tv_nsec = 999999999L; + } else if (ts.tv_nsec < 0L) { + ts.tv_nsec = 0L; + } + + nanosleep(&ts, NULL); +} + +void rate_limiter_init(RateLimiter *limiter, double rate, double (*clock)()) +{ + limiter->rate = rate; + limiter->clock = clock; + limiter->last_modified = limiter->clock(); + limiter->accumulated_sleep_time = rate_limiter_idle_credit; + + pthread_mutexattr_t attr; + int status = pthread_mutexattr_init(&attr); + assert(status == 0); + status = pthread_mutex_init(&limiter->mutex, &attr); + assert(status == 0); + status = pthread_mutexattr_destroy(&attr); + assert(status == 0); +} + +void rate_limiter_wait(RateLimiter* limiter, size_t size) +{ + double time_to_sleep = rate_limiter_wait_nosleep(limiter, size); + sleep_seconds(time_to_sleep); +} + +double rate_limiter_wait_nosleep(RateLimiter* limiter, size_t size) +{ + int status = pthread_mutex_lock(&limiter->mutex); + assert(status == 0); + + double time_to_add = size / limiter->rate; + + double now = limiter->clock(); + double elapsed = now - limiter->last_modified; + if (elapsed < 0) { + elapsed = 0; + } + + double time_to_sleep = limiter->accumulated_sleep_time; + time_to_sleep -= elapsed; + if (time_to_sleep < rate_limiter_idle_credit) { + time_to_sleep = rate_limiter_idle_credit; + } + time_to_sleep += time_to_add; + limiter->accumulated_sleep_time = time_to_sleep; + + limiter->last_modified = now; + + status = pthread_mutex_unlock(&limiter->mutex); + assert(status == 0); + + return time_to_sleep; +} + +void rate_limiter_destroy(RateLimiter *limiter) +{ + int status = pthread_mutex_destroy(&limiter->mutex); + assert(status == 0); +} diff --git a/src/rate_limiter.h b/src/rate_limiter.h new file mode 100644 index 0000000..a332294 --- /dev/null +++ b/src/rate_limiter.h @@ -0,0 +1,51 @@ +/* + Copyright 2014 Martin Pärtel <martin.partel@gmail.com> + + This file is part of bindfs. + + bindfs is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + bindfs is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with bindfs. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INC_BINDFS_RATE_LIMITER_H +#define INC_BINDFS_RATE_LIMITER_H + +#include <string.h> +#include <pthread.h> + +/* When we are idle, we allow some time to be "credited" to the next writer. + * Otherwise, the short pause between requests would "go to waste", lowering + * the throughput when there is only one requester. */ +const double rate_limiter_idle_credit; + +typedef struct RateLimiter { + double rate; /* bytes / second */ + double (*clock)(); + double last_modified; + double accumulated_sleep_time; + pthread_mutex_t mutex; +} RateLimiter; + +double gettimeofday_clock(); + +/* 0 on success, error number on error. */ +void rate_limiter_init(RateLimiter* limiter, double rate, double (*clock)()); +/* Blocks until the rate limiter clears `size` units. */ +void rate_limiter_wait(RateLimiter* limiter, size_t size); +/* Updates the rate limiter like `rate_limiter_wait` but does not actually + * sleep. Returns the time that the caller is expected to sleep. */ +double rate_limiter_wait_nosleep(RateLimiter* limiter, size_t size); +/* Destroys the rate limiter. No wait_for_permit calls may be active. */ +void rate_limiter_destroy(RateLimiter* limiter); + +#endif |