| |
@@ -0,0 +1,819 @@
|
| |
+ From dbcfb88ddd55fef9cb955718c92d58082e980985 Mon Sep 17 00:00:00 2001
|
| |
+ From: Kevin Vigor <kvigor@gmail.com>
|
| |
+ Date: Thu, 6 Feb 2020 12:20:19 -0800
|
| |
+ Subject: [PATCH] Implement multi-threaded squashfuse_ll, allowing parallel
|
| |
+ decompression.
|
| |
+
|
| |
+ A simple thread-safe cache implementation is added and squashfuse_ll init
|
| |
+ is altered to use fuse_session_loop_mt().
|
| |
+
|
| |
+ Multithreading must be explicitly enabled at configure time with the
|
| |
+ --enable-multithreading option. If enabled, the resulting squashfuse_ll
|
| |
+ will be multithreaded by default, but this may be disabled at runtime
|
| |
+ with the '-s' FUSE commandline option.
|
| |
+ ---
|
| |
+ Makefile.am | 8 +-
|
| |
+ cache.c | 4 +
|
| |
+ cache_mt.c | 169 +++++++++++++++++++++++++++++++
|
| |
+ configure.ac | 11 +-
|
| |
+ fs.c | 9 +-
|
| |
+ ll.c | 77 ++++++++++----
|
| |
+ ll_main.c | 20 +++-
|
| |
+ m4/squashfuse_c.m4 | 33 +-----
|
| |
+ squashfs_fs.h | 6 +-
|
| |
+ tests/cachetest.c | 1 +
|
| |
+ tests/ll-smoke-singlethreaded.sh | 10 ++
|
| |
+ tests/ll-smoke.sh | 141 ++++++++++++++++++++++++++
|
| |
+ tests/ll-smoke.sh.in | 6 +-
|
| |
+ 13 files changed, 432 insertions(+), 63 deletions(-)
|
| |
+ create mode 100644 cache_mt.c
|
| |
+ create mode 100755 tests/ll-smoke-singlethreaded.sh
|
| |
+ create mode 100755 tests/ll-smoke.sh
|
| |
+
|
| |
+ diff --git a/Makefile.am b/Makefile.am
|
| |
+ index eaf7ac97..17b01be4 100644
|
| |
+ --- a/Makefile.am
|
| |
+ +++ b/Makefile.am
|
| |
+ @@ -26,7 +26,7 @@ pkgconfig_DATA = squashfuse.pc
|
| |
+ noinst_LTLIBRARIES += libsquashfuse_convenience.la
|
| |
+ libsquashfuse_convenience_la_SOURCES = swap.c cache.c table.c dir.c file.c fs.c \
|
| |
+ decompress.c xattr.c hash.c stack.c traverse.c util.c \
|
| |
+ - nonstd-pread.c nonstd-stat.c \
|
| |
+ + nonstd-pread.c nonstd-stat.c cache_mt.c \
|
| |
+ squashfs_fs.h common.h nonstd-internal.h nonstd.h swap.h cache.h table.h \
|
| |
+ dir.h file.h decompress.h xattr.h squashfuse.h hash.h stack.h traverse.h \
|
| |
+ util.h fs.h
|
| |
+ @@ -105,6 +105,12 @@ endif
|
| |
+ TESTS =
|
| |
+ if SQ_FUSE_TESTS
|
| |
+ TESTS += tests/ll-smoke.sh
|
| |
+ +if MULTITHREADED
|
| |
+ +# I know this test looks backwards, but the default smoke test is multithreaded
|
| |
+ +# when threading is enabled. So we additionally run a singlethreaded test in
|
| |
+ +# that case.
|
| |
+ +TESTS += tests/ll-smoke-singlethreaded.sh
|
| |
+ +endif
|
| |
+ check_PROGRAMS = cachetest endiantest
|
| |
+ cachetest_SOURCES=tests/cachetest.c
|
| |
+ cachetest_LDADD=libsquashfuse.la $(COMPRESSION_LIBS)
|
| |
+ diff --git a/cache.c b/cache.c
|
| |
+ index 36d02234..45408f24 100644
|
| |
+ --- a/cache.c
|
| |
+ +++ b/cache.c
|
| |
+ @@ -24,6 +24,9 @@
|
| |
+ */
|
| |
+
|
| |
+ #include "config.h"
|
| |
+ +
|
| |
+ +#ifndef SQFS_MULTITHREADED
|
| |
+ +
|
| |
+ #include "cache.h"
|
| |
+
|
| |
+ #include "fs.h"
|
| |
+ @@ -140,3 +143,4 @@ void sqfs_cache_entry_mark_valid(sqfs_cache *cache, void *e) {
|
| |
+ void sqfs_cache_put(const sqfs_cache *cache, const void *e) {
|
| |
+ // nada, we have no locking in single-threaded implementation.
|
| |
+ }
|
| |
+ +#endif /* SQFS_MULTITHREADED */
|
| |
+ diff --git a/cache_mt.c b/cache_mt.c
|
| |
+ new file mode 100644
|
| |
+ index 00000000..1b17fa5a
|
| |
+ --- /dev/null
|
| |
+ +++ b/cache_mt.c
|
| |
+ @@ -0,0 +1,169 @@
|
| |
+ +#include "config.h"
|
| |
+ +
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ +
|
| |
+ +/* Thread-safe cache implementation.
|
| |
+ + *
|
| |
+ + * Simple implementation: basic hash table, each individual entry is
|
| |
+ + * protected by a mutex, any collision is handled by eviction.
|
| |
+ + */
|
| |
+ +
|
| |
+ +#include "cache.h"
|
| |
+ +#include "fs.h"
|
| |
+ +
|
| |
+ +#include <assert.h>
|
| |
+ +#include <pthread.h>
|
| |
+ +#include <stdlib.h>
|
| |
+ +
|
| |
+ +typedef struct sqfs_cache_internal {
|
| |
+ + uint8_t *buf;
|
| |
+ + sqfs_cache_dispose dispose;
|
| |
+ + size_t entry_size, count;
|
| |
+ +} sqfs_cache_internal;
|
| |
+ +
|
| |
+ +typedef struct {
|
| |
+ + enum { EMPTY, FULL } state;
|
| |
+ + sqfs_cache_idx idx;
|
| |
+ + pthread_mutex_t lock;
|
| |
+ +} sqfs_cache_entry_hdr;
|
| |
+ +
|
| |
+ +// MurmurHash64A performance-optimized for hash of uint64_t keys
|
| |
+ +const static uint64_t kMurmur2Seed = 4193360111ul;
|
| |
+ +static uint64_t MurmurRehash64A(uint64_t key) {
|
| |
+ + const uint64_t m = 0xc6a4a7935bd1e995;
|
| |
+ + const int r = 47;
|
| |
+ +
|
| |
+ + uint64_t h = (uint64_t)kMurmur2Seed ^ (sizeof(uint64_t) * m);
|
| |
+ +
|
| |
+ + key *= m;
|
| |
+ + key ^= key >> r;
|
| |
+ + key *= m;
|
| |
+ +
|
| |
+ + h ^= key;
|
| |
+ + h *= m;
|
| |
+ +
|
| |
+ + h ^= h >> r;
|
| |
+ + h *= m;
|
| |
+ + h ^= h >> r;
|
| |
+ +
|
| |
+ + return h;
|
| |
+ +}
|
| |
+ +
|
| |
+ +static sqfs_cache_entry_hdr *sqfs_cache_entry_header(
|
| |
+ + sqfs_cache_internal* cache,
|
| |
+ + size_t i) {
|
| |
+ + assert(i < cache->count);
|
| |
+ + return (sqfs_cache_entry_hdr *)(cache->buf + i * cache->entry_size);
|
| |
+ +}
|
| |
+ +
|
| |
+ +sqfs_err sqfs_cache_init(sqfs_cache *cache, size_t entry_size, size_t count,
|
| |
+ + sqfs_cache_dispose dispose) {
|
| |
+ + size_t i;
|
| |
+ + pthread_mutexattr_t attr;
|
| |
+ + sqfs_cache_internal *c = malloc(sizeof(sqfs_cache_internal));
|
| |
+ +
|
| |
+ + if (!c) {
|
| |
+ + return SQFS_ERR;
|
| |
+ + }
|
| |
+ +
|
| |
+ + c->entry_size = entry_size + sizeof(sqfs_cache_entry_hdr);
|
| |
+ + c->count = count;
|
| |
+ + c->dispose = dispose;
|
| |
+ +
|
| |
+ + pthread_mutexattr_init(&attr);
|
| |
+ +#if defined(_GNU_SOURCE) && !defined(NDEBUG)
|
| |
+ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
| |
+ +#endif
|
| |
+ +
|
| |
+ + c->buf = calloc(c->count, c->entry_size);
|
| |
+ + if (!c->buf) {
|
| |
+ + goto err_out;
|
| |
+ + }
|
| |
+ +
|
| |
+ + for (i = 0; i < c->count; ++i) {
|
| |
+ + sqfs_cache_entry_hdr *hdr = sqfs_cache_entry_header(c, i);
|
| |
+ + hdr->state = EMPTY;
|
| |
+ + if (pthread_mutex_init(&hdr->lock, &attr)) {
|
| |
+ + goto err_out;
|
| |
+ + }
|
| |
+ + }
|
| |
+ +
|
| |
+ + pthread_mutexattr_destroy(&attr);
|
| |
+ +
|
| |
+ + *cache = c;
|
| |
+ + return SQFS_OK;
|
| |
+ +
|
| |
+ +err_out:
|
| |
+ + sqfs_cache_destroy(&c);
|
| |
+ + return SQFS_ERR;
|
| |
+ +}
|
| |
+ +
|
| |
+ +void sqfs_cache_destroy(sqfs_cache *cache) {
|
| |
+ + if (cache && *cache) {
|
| |
+ + sqfs_cache_internal *c = *cache;
|
| |
+ + if (c->buf) {
|
| |
+ + size_t i;
|
| |
+ + for (i = 0; i < c->count; ++i) {
|
| |
+ + sqfs_cache_entry_hdr *hdr =
|
| |
+ + sqfs_cache_entry_header(c, i);
|
| |
+ + if (hdr->state == FULL) {
|
| |
+ + c->dispose((void *)(hdr + 1));
|
| |
+ + }
|
| |
+ + if (pthread_mutex_destroy(&hdr->lock)) {
|
| |
+ + assert(0);
|
| |
+ + }
|
| |
+ + }
|
| |
+ + }
|
| |
+ + free(c->buf);
|
| |
+ + free(c);
|
| |
+ + *cache = NULL;
|
| |
+ + }
|
| |
+ +}
|
| |
+ +
|
| |
+ +void *sqfs_cache_get(sqfs_cache *cache, sqfs_cache_idx idx) {
|
| |
+ + sqfs_cache_internal *c = *cache;
|
| |
+ + sqfs_cache_entry_hdr *hdr;
|
| |
+ + void *entry;
|
| |
+ +
|
| |
+ + uint64_t key = MurmurRehash64A(idx) % c->count;
|
| |
+ +
|
| |
+ + hdr = sqfs_cache_entry_header(c, key);
|
| |
+ + if (pthread_mutex_lock(&hdr->lock)) { assert(0); }
|
| |
+ + /* matching unlock is in sqfs_cache_put() */
|
| |
+ + entry = (void *)(hdr + 1);
|
| |
+ +
|
| |
+ + if (hdr->state == EMPTY) {
|
| |
+ + hdr->idx = idx;
|
| |
+ + return entry;
|
| |
+ + }
|
| |
+ +
|
| |
+ + /* There's a valid entry: it's either a cache hit or a collision. */
|
| |
+ + assert(hdr->state == FULL);
|
| |
+ + if (hdr->idx == idx) {
|
| |
+ + return entry;
|
| |
+ + }
|
| |
+ +
|
| |
+ + /* Collision. */
|
| |
+ + c->dispose((void *)(hdr + 1));
|
| |
+ + hdr->state = EMPTY;
|
| |
+ + hdr->idx = idx;
|
| |
+ + return entry;
|
| |
+ +}
|
| |
+ +
|
| |
+ +int sqfs_cache_entry_valid(const sqfs_cache *cache, const void *e) {
|
| |
+ + sqfs_cache_entry_hdr *hdr = ((sqfs_cache_entry_hdr *)e) - 1;
|
| |
+ + return hdr->state == FULL;
|
| |
+ +}
|
| |
+ +
|
| |
+ +void sqfs_cache_entry_mark_valid(sqfs_cache *cache, void *e) {
|
| |
+ + sqfs_cache_entry_hdr *hdr = ((sqfs_cache_entry_hdr *)e) - 1;
|
| |
+ + assert(hdr->state == EMPTY);
|
| |
+ + hdr->state = FULL;
|
| |
+ +}
|
| |
+ +
|
| |
+ +void sqfs_cache_put(const sqfs_cache *cache, const void *e) {
|
| |
+ + sqfs_cache_entry_hdr *hdr = ((sqfs_cache_entry_hdr *)e) - 1;
|
| |
+ + if (pthread_mutex_unlock(&hdr->lock)) { assert(0); }
|
| |
+ +}
|
| |
+ +
|
| |
+ +#endif /* SQFS_MULTITHREADED */
|
| |
+ diff --git a/configure.ac b/configure.ac
|
| |
+ index 762766e9..3869075a 100644
|
| |
+ --- a/configure.ac
|
| |
+ +++ b/configure.ac
|
| |
+ @@ -10,6 +10,7 @@ AH_BOTTOM([#endif])
|
| |
+ AC_CANONICAL_BUILD
|
| |
+ AC_CANONICAL_TARGET
|
| |
+ AM_INIT_AUTOMAKE([foreign -Wall subdir-objects])
|
| |
+ +AC_USE_SYSTEM_EXTENSIONS
|
| |
+ AM_SILENT_RULES(yes)
|
| |
+ AM_PROG_AR
|
| |
+ LT_INIT
|
| |
+ @@ -23,10 +24,8 @@ AC_PROG_SED
|
| |
+ AC_PROG_CPP
|
| |
+ AC_SYS_LARGEFILE
|
| |
+ AM_PROG_CC_C_O
|
| |
+ -SQ_PROG_CPP_POSIX_2001
|
| |
+ SQ_PROG_CC_WALL
|
| |
+
|
| |
+ -AC_DEFINE([_POSIX_C_SOURCE], [200112L], [POSIX 2001 compatibility])
|
| |
+
|
| |
+ # Non-POSIX declarations
|
| |
+ SQ_CHECK_DECL_MAKEDEV
|
| |
+ @@ -97,6 +96,14 @@ AC_CONFIG_FILES([tests/ll-smoke.sh],[chmod +x tests/ll-smoke.sh])
|
| |
+ AS_IF([test "x$sq_high_level$sq_low_level$sq_demo" = xnonono],
|
| |
+ AC_MSG_FAILURE([Nothing left to build]))
|
| |
+
|
| |
+ +AC_ARG_ENABLE([multithreading],
|
| |
+ + AS_HELP_STRING([--enable-multithreading], [enable multi-threaded low-level FUSE driver]),
|
| |
+ + [
|
| |
+ + AC_CHECK_LIB([pthread], [pthread_mutex_lock], [], AC_MSG_ERROR([libpthread is required for multithreaded build]))
|
| |
+ + AC_DEFINE(SQFS_MULTITHREADED, 1, [Enable multi-threaded low-level FUSE driver])
|
| |
+ + ])
|
| |
+ +AM_CONDITIONAL([MULTITHREADED], [test x$enable_multithreading = xyes])
|
| |
+ +
|
| |
+ AC_SUBST([sq_decompressors])
|
| |
+ AC_SUBST([sq_high_level])
|
| |
+ AC_SUBST([sq_low_level])
|
| |
+ diff --git a/fs.c b/fs.c
|
| |
+ index 1838c5ca..ab854b0f 100644
|
| |
+ --- a/fs.c
|
| |
+ +++ b/fs.c
|
| |
+ @@ -34,8 +34,13 @@
|
| |
+ #include <sys/stat.h>
|
| |
+
|
| |
+
|
| |
+ -#define DATA_CACHED_BLKS 1
|
| |
+ -#define FRAG_CACHED_BLKS 3
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ +# define DATA_CACHED_BLKS 48
|
| |
+ +# define FRAG_CACHED_BLKS 48
|
| |
+ +#else
|
| |
+ +# define DATA_CACHED_BLKS 1
|
| |
+ +# define FRAG_CACHED_BLKS 3
|
| |
+ +#endif
|
| |
+
|
| |
+ void sqfs_version_supported(int *min_major, int *min_minor, int *max_major,
|
| |
+ int *max_minor) {
|
| |
+ diff --git a/ll.c b/ll.c
|
| |
+ index 4d17ba5b..596c8bf1 100644
|
| |
+ --- a/ll.c
|
| |
+ +++ b/ll.c
|
| |
+ @@ -52,11 +52,49 @@ static sig_atomic_t open_refcount = 0;
|
| |
+ /* same as lib/fuse_signals.c */
|
| |
+ static struct fuse_session *fuse_instance = NULL;
|
| |
+
|
| |
+ +static void update_access_time(void) {
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ + /* We only need to track access time if we have an idle timeout,
|
| |
+ + * don't bother with expensive operations if idle_timeout is 0.
|
| |
+ + */
|
| |
+ + if (idle_timeout_secs) {
|
| |
+ + time_t now = time(NULL);
|
| |
+ + __atomic_store_n(&last_access, now, __ATOMIC_RELEASE);
|
| |
+ + }
|
| |
+ +#else
|
| |
+ + last_access = time(NULL);
|
| |
+ +#endif
|
| |
+ +}
|
| |
+ +
|
| |
+ +static void update_open_refcount(int delta) {
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ + __atomic_fetch_add(&open_refcount, delta, __ATOMIC_RELEASE);
|
| |
+ +#else
|
| |
+ + open_refcount += delta;
|
| |
+ +#endif
|
| |
+ +}
|
| |
+ +
|
| |
+ +static inline time_t get_access_time(void) {
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ + return __atomic_load_n(&last_access, __ATOMIC_ACQUIRE);
|
| |
+ +#else
|
| |
+ + return last_access;
|
| |
+ +#endif
|
| |
+ +}
|
| |
+ +
|
| |
+ +static inline sig_atomic_t get_open_refcount(void) {
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ + return __atomic_load_n(&open_refcount, __ATOMIC_ACQUIRE);
|
| |
+ +#else
|
| |
+ + return open_refcount;
|
| |
+ +#endif
|
| |
+ +}
|
| |
+ +
|
| |
+ void sqfs_ll_op_getattr(fuse_req_t req, fuse_ino_t ino,
|
| |
+ struct fuse_file_info *fi) {
|
| |
+ sqfs_ll_i lli;
|
| |
+ struct stat st;
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (sqfs_ll_iget(req, &lli, ino))
|
| |
+ return;
|
| |
+
|
| |
+ @@ -71,7 +109,7 @@ void sqfs_ll_op_getattr(fuse_req_t req, fuse_ino_t ino,
|
| |
+ void sqfs_ll_op_opendir(fuse_req_t req, fuse_ino_t ino,
|
| |
+ struct fuse_file_info *fi) {
|
| |
+ sqfs_ll_i *lli;
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+
|
| |
+ fi->fh = (intptr_t)NULL;
|
| |
+
|
| |
+ @@ -86,7 +124,7 @@ void sqfs_ll_op_opendir(fuse_req_t req, fuse_ino_t ino,
|
| |
+ fuse_reply_err(req, ENOTDIR);
|
| |
+ } else {
|
| |
+ fi->fh = (intptr_t)lli;
|
| |
+ - ++open_refcount;
|
| |
+ + update_open_refcount(1);
|
| |
+ fuse_reply_open(req, fi);
|
| |
+ return;
|
| |
+ }
|
| |
+ @@ -96,14 +134,14 @@ void sqfs_ll_op_opendir(fuse_req_t req, fuse_ino_t ino,
|
| |
+
|
| |
+ void sqfs_ll_op_create(fuse_req_t req, fuse_ino_t parent, const char *name,
|
| |
+ mode_t mode, struct fuse_file_info *fi) {
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ fuse_reply_err(req, EROFS);
|
| |
+ }
|
| |
+
|
| |
+ void sqfs_ll_op_releasedir(fuse_req_t req, fuse_ino_t ino,
|
| |
+ struct fuse_file_info *fi) {
|
| |
+ - last_access = time(NULL);
|
| |
+ - --open_refcount;
|
| |
+ + update_access_time();
|
| |
+ + update_open_refcount(-1);
|
| |
+ free((sqfs_ll_i*)(intptr_t)fi->fh);
|
| |
+ fuse_reply_err(req, 0); /* yes, this is necessary */
|
| |
+ }
|
| |
+ @@ -132,7 +170,7 @@ void sqfs_ll_op_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
|
| |
+ sqfs_ll_i *lli = (sqfs_ll_i*)(intptr_t)fi->fh;
|
| |
+ int err = 0;
|
| |
+
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (sqfs_dir_open(&lli->ll->fs, &lli->inode, &dir, off))
|
| |
+ err = EINVAL;
|
| |
+ if (!err && !(bufpos = buf = malloc(size)))
|
| |
+ @@ -173,7 +211,7 @@ void sqfs_ll_op_lookup(fuse_req_t req, fuse_ino_t parent,
|
| |
+ bool found;
|
| |
+ sqfs_inode inode;
|
| |
+
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (sqfs_ll_iget(req, &lli, parent))
|
| |
+ return;
|
| |
+
|
| |
+ @@ -223,7 +261,7 @@ void sqfs_ll_op_open(fuse_req_t req, fuse_ino_t ino,
|
| |
+ sqfs_inode *inode;
|
| |
+ sqfs_ll *ll;
|
| |
+
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (fi->flags & (O_WRONLY | O_RDWR)) {
|
| |
+ fuse_reply_err(req, EROFS);
|
| |
+ return;
|
| |
+ @@ -243,7 +281,7 @@ void sqfs_ll_op_open(fuse_req_t req, fuse_ino_t ino,
|
| |
+ } else {
|
| |
+ fi->fh = (intptr_t)inode;
|
| |
+ fi->keep_cache = 1;
|
| |
+ - ++open_refcount;
|
| |
+ + update_open_refcount(1);
|
| |
+ fuse_reply_open(req, fi);
|
| |
+ return;
|
| |
+ }
|
| |
+ @@ -254,8 +292,8 @@ void sqfs_ll_op_release(fuse_req_t req, fuse_ino_t ino,
|
| |
+ struct fuse_file_info *fi) {
|
| |
+ free((sqfs_inode*)(intptr_t)fi->fh);
|
| |
+ fi->fh = 0;
|
| |
+ - last_access = time(NULL);
|
| |
+ - --open_refcount;
|
| |
+ + update_access_time();
|
| |
+ + update_open_refcount(-1);
|
| |
+ fuse_reply_err(req, 0);
|
| |
+ }
|
| |
+
|
| |
+ @@ -272,7 +310,7 @@ void sqfs_ll_op_read(fuse_req_t req, fuse_ino_t ino,
|
| |
+ return;
|
| |
+ }
|
| |
+
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ osize = size;
|
| |
+ err = sqfs_read_range(&ll->fs, inode, off, &osize, buf);
|
| |
+ if (err) {
|
| |
+ @@ -289,7 +327,7 @@ void sqfs_ll_op_readlink(fuse_req_t req, fuse_ino_t ino) {
|
| |
+ char *dst;
|
| |
+ size_t size;
|
| |
+ sqfs_ll_i lli;
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (sqfs_ll_iget(req, &lli, ino))
|
| |
+ return;
|
| |
+
|
| |
+ @@ -313,7 +351,7 @@ void sqfs_ll_op_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size) {
|
| |
+ char *buf;
|
| |
+ int ferr;
|
| |
+
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (sqfs_ll_iget(req, &lli, ino))
|
| |
+ return;
|
| |
+
|
| |
+ @@ -351,7 +389,7 @@ void sqfs_ll_op_getxattr(fuse_req_t req, fuse_ino_t ino,
|
| |
+ }
|
| |
+ #endif
|
| |
+
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ if (sqfs_ll_iget(req, &lli, ino))
|
| |
+ return;
|
| |
+
|
| |
+ @@ -373,7 +411,7 @@ void sqfs_ll_op_getxattr(fuse_req_t req, fuse_ino_t ino,
|
| |
+ void sqfs_ll_op_forget(fuse_req_t req, fuse_ino_t ino,
|
| |
+ unsigned long nlookup) {
|
| |
+ sqfs_ll_i lli;
|
| |
+ - last_access = time(NULL);
|
| |
+ + update_access_time();
|
| |
+ sqfs_ll_iget(req, &lli, SQFS_FUSE_INODE_NONE);
|
| |
+ lli.ll->ino_forget(lli.ll, ino, nlookup);
|
| |
+ fuse_reply_none(req);
|
| |
+ @@ -489,7 +527,8 @@ void alarm_tick(int sig) {
|
| |
+ return;
|
| |
+ }
|
| |
+
|
| |
+ - if (open_refcount == 0 && time(NULL) - last_access > idle_timeout_secs) {
|
| |
+ + if (get_open_refcount() == 0 &&
|
| |
+ + time(NULL) - get_access_time() > idle_timeout_secs) {
|
| |
+ /* Safely shutting down fuse in a cross-platform way is a dark art!
|
| |
+ But just about any platform should stop on SIGINT, so do that */
|
| |
+ kill(getpid(), SIGINT);
|
| |
+ @@ -499,8 +538,8 @@ void alarm_tick(int sig) {
|
| |
+ }
|
| |
+
|
| |
+ void setup_idle_timeout(struct fuse_session *se, unsigned int timeout_secs) {
|
| |
+ - last_access = time(NULL);
|
| |
+ idle_timeout_secs = timeout_secs;
|
| |
+ + update_access_time();
|
| |
+
|
| |
+ struct sigaction sa;
|
| |
+ memset(&sa, 0, sizeof(struct sigaction));
|
| |
+ diff --git a/ll_main.c b/ll_main.c
|
| |
+ index aca76935..22302085 100644
|
| |
+ --- a/ll_main.c
|
| |
+ +++ b/ll_main.c
|
| |
+ @@ -142,8 +142,22 @@ int main(int argc, char *argv[]) {
|
| |
+ if (opts.idle_timeout_secs) {
|
| |
+ setup_idle_timeout(ch.session, opts.idle_timeout_secs);
|
| |
+ }
|
| |
+ - /* FIXME: multithreading */
|
| |
+ - err = fuse_session_loop(ch.session);
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ +# if FUSE_USE_VERSION >= 30
|
| |
+ + if (!fuse_cmdline_opts.singlethread) {
|
| |
+ + struct fuse_loop_config config;
|
| |
+ + config.clone_fd = 1;
|
| |
+ + config.max_idle_threads = 10;
|
| |
+ + err = fuse_session_loop_mt(ch.session, &config);
|
| |
+ + }
|
| |
+ +# else /* FUSE_USE_VERSION < 30 */
|
| |
+ + if (fuse_cmdline_opts.mt) {
|
| |
+ + err = fuse_session_loop_mt(ch.session);
|
| |
+ + }
|
| |
+ +# endif /* FUSE_USE_VERSION */
|
| |
+ + else
|
| |
+ +#endif
|
| |
+ + err = fuse_session_loop(ch.session);
|
| |
+ teardown_idle_timeout();
|
| |
+ fuse_remove_signal_handlers(ch.session);
|
| |
+ }
|
| |
+ @@ -157,4 +171,4 @@ int main(int argc, char *argv[]) {
|
| |
+ free(fuse_cmdline_opts.mountpoint);
|
| |
+
|
| |
+ return -err;
|
| |
+ -}
|
| |
+ \ No newline at end of file
|
| |
+ +}
|
| |
+ diff --git a/m4/squashfuse_c.m4 b/m4/squashfuse_c.m4
|
| |
+ index f29a90b1..c4039c42 100644
|
| |
+ --- a/m4/squashfuse_c.m4
|
| |
+ +++ b/m4/squashfuse_c.m4
|
| |
+ @@ -21,37 +21,6 @@
|
| |
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
| |
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| |
+
|
| |
+ -# SQ_PROG_CPP_POSIX_2001
|
| |
+ -#
|
| |
+ -# Check if a preprocessor flag is needed for POSIX-2001 headers.
|
| |
+ -# Needed at least on Solaris and derivatives.
|
| |
+ -AC_DEFUN([SQ_PROG_CPP_POSIX_2001],[
|
| |
+ -AC_CACHE_CHECK([for option for POSIX-2001 preprocessor],
|
| |
+ - [sq_cv_prog_cpp_posix2001],
|
| |
+ -[
|
| |
+ - sq_cv_prog_cpp_posix2001=unknown
|
| |
+ - sq_save_CPPFLAGS=$CPPFLAGS
|
| |
+ - for sq_flags in none -std=gnu99 -xc99=all
|
| |
+ - do
|
| |
+ - AS_IF([test "x$sq_flags" = xnone],,
|
| |
+ - [CPPFLAGS="$save_CPPFLAGS $sq_flags"])
|
| |
+ - AC_PREPROC_IFELSE([AC_LANG_PROGRAM([
|
| |
+ - #define _POSIX_C_SOURCE 200112L
|
| |
+ - #include <sys/types.h>
|
| |
+ - ])],[
|
| |
+ - sq_cv_prog_cpp_posix2001=$sq_flags
|
| |
+ - break
|
| |
+ - ])
|
| |
+ - done
|
| |
+ - CPPFLAGS=$sq_save_CPPFLAGS
|
| |
+ -])
|
| |
+ -AS_IF([test "x$sq_cv_prog_cpp_posix2001" = xunknown],
|
| |
+ - [AC_MSG_FAILURE([can't preprocess for POSIX-2001])],
|
| |
+ - [AS_IF([test "x$sq_cv_prog_cpp_posix2001" = xnone],,
|
| |
+ - CPPFLAGS="$CPPFLAGS $sq_cv_prog_cpp_posix2001")
|
| |
+ -])
|
| |
+ -])
|
| |
+ -
|
| |
+ # SQ_PROG_CC_WALL
|
| |
+ #
|
| |
+ # Check if -Wall is supported
|
| |
+ @@ -67,4 +36,4 @@ AC_CACHE_CHECK([how to enable all compiler warnings],
|
| |
+ ])
|
| |
+ AS_IF([test "x$sq_cv_prog_cc_wall" = xunknown],,
|
| |
+ [AC_SUBST([AM_CFLAGS],["$AM_CFLAGS $sq_cv_prog_cc_wall"])])
|
| |
+ -])
|
| |
+ \ No newline at end of file
|
| |
+ +])
|
| |
+ diff --git a/squashfs_fs.h b/squashfs_fs.h
|
| |
+ index e0ab1f4e..a85b7606 100644
|
| |
+ --- a/squashfs_fs.h
|
| |
+ +++ b/squashfs_fs.h
|
| |
+ @@ -105,7 +105,11 @@
|
| |
+
|
| |
+
|
| |
+ /* cached data constants for filesystem */
|
| |
+ -#define SQUASHFS_CACHED_BLKS 8
|
| |
+ +#ifdef SQFS_MULTITHREADED
|
| |
+ +# define SQUASHFS_CACHED_BLKS 128
|
| |
+ +#else
|
| |
+ +# define SQUASHFS_CACHED_BLKS 8
|
| |
+ +#endif
|
| |
+
|
| |
+ #define SQUASHFS_MAX_FILE_SIZE_LOG 64
|
| |
+
|
| |
+ diff --git a/tests/cachetest.c b/tests/cachetest.c
|
| |
+ index 8a2c2363..c515fcdf 100644
|
| |
+ --- a/tests/cachetest.c
|
| |
+ +++ b/tests/cachetest.c
|
| |
+ @@ -36,6 +36,7 @@ int test_cache_miss(void) {
|
| |
+ TestStructDispose), SQFS_OK);
|
| |
+ entry = (TestStruct *)sqfs_cache_get(&cache, 1);
|
| |
+ EXPECT_EQ(sqfs_cache_entry_valid(&cache, entry), 0);
|
| |
+ + sqfs_cache_put(&cache, entry);
|
| |
+ sqfs_cache_destroy(&cache);
|
| |
+
|
| |
+ return errors == 0;
|
| |
+ diff --git a/tests/ll-smoke-singlethreaded.sh b/tests/ll-smoke-singlethreaded.sh
|
| |
+ new file mode 100755
|
| |
+ index 00000000..c7cbfc38
|
| |
+ --- /dev/null
|
| |
+ +++ b/tests/ll-smoke-singlethreaded.sh
|
| |
+ @@ -0,0 +1,10 @@
|
| |
+ +!/bin/bash
|
| |
+ +
|
| |
+ +# Singlethreaded ll-smoke test.
|
| |
+ +#
|
| |
+ +# When multithreading is enabled at build time, it is the default
|
| |
+ +# behavior of squashfuse_ll, but can be disabled at runtime with
|
| |
+ +# the FUSE '-s' commandline option.
|
| |
+ +#
|
| |
+ +# So we just re-run the normal ll-smoke test with the '-s' option.
|
| |
+ +SFLL_EXTRA_ARGS="-s" $(dirname -- $0)/ll-smoke.sh
|
| |
+ diff --git a/tests/ll-smoke.sh b/tests/ll-smoke.sh
|
| |
+ new file mode 100755
|
| |
+ index 00000000..6b9f4641
|
| |
+ --- /dev/null
|
| |
+ +++ b/tests/ll-smoke.sh
|
| |
+ @@ -0,0 +1,141 @@
|
| |
+ +#!/bin/sh
|
| |
+ +
|
| |
+ +. "tests/lib.sh"
|
| |
+ +
|
| |
+ +# Very simple smoke test for squashfuse_ll. Make some random files.
|
| |
+ +# assemble a squashfs image, mount it, compare the files.
|
| |
+ +
|
| |
+ +SFLL=${1:-./squashfuse_ll} # The squashfuse_ll binary.
|
| |
+ +
|
| |
+ +IDLE_TIMEOUT=5
|
| |
+ +
|
| |
+ +trap cleanup EXIT
|
| |
+ +set -e
|
| |
+ +
|
| |
+ +WORKDIR=$(mktemp -d)
|
| |
+ +
|
| |
+ +sq_umount() {
|
| |
+ + case linux-gnu in
|
| |
+ + linux*)
|
| |
+ + fusermount3 -u $1
|
| |
+ + ;;
|
| |
+ + *)
|
| |
+ + umount $1
|
| |
+ + ;;
|
| |
+ + esac
|
| |
+ +}
|
| |
+ +
|
| |
+ +sq_is_mountpoint() {
|
| |
+ + mount | grep -q "$1"
|
| |
+ +}
|
| |
+ +
|
| |
+ +cleanup() {
|
| |
+ + set +e # Don't care about errors here.
|
| |
+ + if [ -n "$WORKDIR" ]; then
|
| |
+ + if [ -n "$SQ_SAVE_LOGS" ]; then
|
| |
+ + cp "$WORKDIR/squashfs_ll.log" "$SQ_SAVE_LOGS" || true
|
| |
+ + fi
|
| |
+ + if sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ + sq_umount "$WORKDIR/mount"
|
| |
+ + fi
|
| |
+ + rm -rf "$WORKDIR"
|
| |
+ + fi
|
| |
+ +}
|
| |
+ +
|
| |
+ +find_compressors
|
| |
+ +
|
| |
+ +echo "Generating random test files..."
|
| |
+ +mkdir -p "$WORKDIR/source"
|
| |
+ +head -c 64000000 /dev/urandom >"$WORKDIR/source/rand1"
|
| |
+ +head -c 17000 /dev/urandom >"$WORKDIR/source/rand2"
|
| |
+ +head -c 100000000 /dev/urandom >"$WORKDIR/source/rand3"
|
| |
+ +head -c 87 /dev/zero >"$WORKDIR/source/z1 with spaces"
|
| |
+ +
|
| |
+ +for comp in $compressors; do
|
| |
+ + echo "Building $comp squashfs image..."
|
| |
+ + mksquashfs "$WORKDIR/source" "$WORKDIR/squashfs.image" -comp $comp -no-progress
|
| |
+ +
|
| |
+ + mkdir -p "$WORKDIR/mount"
|
| |
+ +
|
| |
+ + echo "Mounting squashfs image..."
|
| |
+ + $SFLL -f $SFLL_EXTRA_ARGS "$WORKDIR/squashfs.image" "$WORKDIR/mount" >"$WORKDIR/squashfs_ll.log" 2>&1 &
|
| |
+ + # Wait up to 5 seconds to be mounted. TSAN builds can take some time to mount.
|
| |
+ + for _ in $(seq 5); do
|
| |
+ + if sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ + break
|
| |
+ + fi
|
| |
+ + sleep 1
|
| |
+ + done
|
| |
+ +
|
| |
+ + if ! sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ + echo "Image did not mount after 5 seconds."
|
| |
+ + cp "$WORKDIR/squashfs_ll.log" /tmp/squashfs_ll.smoke.log
|
| |
+ + echo "There may be clues in /tmp/squashfs_ll.smoke.log"
|
| |
+ + exit 1
|
| |
+ + fi
|
| |
+ +
|
| |
+ + if command -v fio >/dev/null; then
|
| |
+ + echo "FIO tests..."
|
| |
+ + fio --filename="$WORKDIR/mount/rand1" --direct=1 --rw=randread --ioengine=libaio --bs=512 --iodepth=16 --numjobs=4 --name=j1 --minimal --output=/dev/null --runtime 30
|
| |
+ + fio --filename="$WORKDIR/mount/rand2" --rw=randread --ioengine=libaio --bs=4k --iodepth=16 --numjobs=4 --name=j2 --minimal --output=/dev/null --runtime 30
|
| |
+ + fio --filename="$WORKDIR/mount/rand3" --rw=randread --ioengine=psync --bs=128k --name=j3 --minimal --output=/dev/null --runtime 30
|
| |
+ + else
|
| |
+ + echo "Consider installing fio for better test coverage."
|
| |
+ + fi
|
| |
+ +
|
| |
+ + echo "Comparing files..."
|
| |
+ + cmp "$WORKDIR/source/rand1" "$WORKDIR/mount/rand1"
|
| |
+ + cmp "$WORKDIR/source/rand2" "$WORKDIR/mount/rand2"
|
| |
+ + cmp "$WORKDIR/source/rand3" "$WORKDIR/mount/rand3"
|
| |
+ + cmp "$WORKDIR/source/z1 with spaces" "$WORKDIR/mount/z1 with spaces"
|
| |
+ +
|
| |
+ + echo "Parallel md5sum..."
|
| |
+ + md5sum "$WORKDIR"/mount/* >"$WORKDIR/md5sums"
|
| |
+ + split -l1 "$WORKDIR/md5sums" "$WORKDIR/sumpiece"
|
| |
+ + echo "$WORKDIR"/sumpiece* | xargs -P4 -n1 md5sum -c
|
| |
+ +
|
| |
+ + echo "Lookup tests..."
|
| |
+ + # Look for non-existent files to exercise failed lookup path.
|
| |
+ + if [ -e "$WORKDIR/mount/bogus" ]; then
|
| |
+ + echo "Bogus existence test"
|
| |
+ + exit 1
|
| |
+ + fi
|
| |
+ + # Twice so we hit cache path.
|
| |
+ + if [ -e "$WORKDIR/mount/bogus" ]; then
|
| |
+ + echo "Bogus existence test #2"
|
| |
+ + exit 1
|
| |
+ + fi
|
| |
+ +
|
| |
+ + SRCSZ=$(wc -c < "$WORKDIR/source/rand1")
|
| |
+ + MNTSZ=$(wc -c < "$WORKDIR/mount/rand1")
|
| |
+ + if [ "$SRCSZ" != "$MNTSZ" ]; then
|
| |
+ + echo "Bogus size $MNTSZ != $SRCSZ"
|
| |
+ + exit 1
|
| |
+ + fi
|
| |
+ +
|
| |
+ + echo "Unmounting..."
|
| |
+ + sq_umount "$WORKDIR/mount"
|
| |
+ +
|
| |
+ + # Only test timeouts once, it takes a long time
|
| |
+ + if [ -z "$did_timeout" ]; then
|
| |
+ + echo "Remounting with idle unmount option..."
|
| |
+ + $SFLL $SFLL_EXTRA_ARGS -otimeout=$IDLE_TIMEOUT "$WORKDIR/squashfs.image" "$WORKDIR/mount"
|
| |
+ + if ! sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ + echo "Not mounted?"
|
| |
+ + exit 1
|
| |
+ + fi
|
| |
+ + echo "Waiting up to $(( IDLE_TIMEOUT + 10 )) seconds for idle unmount..."
|
| |
+ + sleep $(( IDLE_TIMEOUT + 10 ))
|
| |
+ + if sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ + echo "FS did not idle unmount in timely way."
|
| |
+ + exit 1
|
| |
+ + fi
|
| |
+ +
|
| |
+ + did_timeout=yes
|
| |
+ + fi
|
| |
+ +
|
| |
+ + rm -f "$WORKDIR/squashfs.image"
|
| |
+ +done
|
| |
+ +
|
| |
+ +echo "Success."
|
| |
+ +exit 0
|
| |
+ diff --git a/tests/ll-smoke.sh.in b/tests/ll-smoke.sh.in
|
| |
+ index 84256267..d7ddd8a1 100755
|
| |
+ --- a/tests/ll-smoke.sh.in
|
| |
+ +++ b/tests/ll-smoke.sh.in
|
| |
+ @@ -52,13 +52,13 @@ head -c 100000000 /dev/urandom >"$WORKDIR/source/rand3"
|
| |
+ head -c 87 /dev/zero >"$WORKDIR/source/z1 with spaces"
|
| |
+
|
| |
+ for comp in $compressors; do
|
| |
+ - echo "Building $comp squashfs image,.,"
|
| |
+ + echo "Building $comp squashfs image..."
|
| |
+ mksquashfs "$WORKDIR/source" "$WORKDIR/squashfs.image" -comp $comp -no-progress
|
| |
+
|
| |
+ mkdir -p "$WORKDIR/mount"
|
| |
+
|
| |
+ echo "Mounting squashfs image..."
|
| |
+ - $SFLL -f "$WORKDIR/squashfs.image" "$WORKDIR/mount" >"$WORKDIR/squashfs_ll.log" 2>&1 &
|
| |
+ + $SFLL -f $SFLL_EXTRA_ARGS "$WORKDIR/squashfs.image" "$WORKDIR/mount" >"$WORKDIR/squashfs_ll.log" 2>&1 &
|
| |
+ # Wait up to 5 seconds to be mounted. TSAN builds can take some time to mount.
|
| |
+ for _ in $(seq 5); do
|
| |
+ if sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ @@ -119,7 +119,7 @@ for comp in $compressors; do
|
| |
+ # Only test timeouts once, it takes a long time
|
| |
+ if [ -z "$did_timeout" ]; then
|
| |
+ echo "Remounting with idle unmount option..."
|
| |
+ - $SFLL -otimeout=$IDLE_TIMEOUT "$WORKDIR/squashfs.image" "$WORKDIR/mount"
|
| |
+ + $SFLL $SFLL_EXTRA_ARGS -otimeout=$IDLE_TIMEOUT "$WORKDIR/squashfs.image" "$WORKDIR/mount"
|
| |
+ if ! sq_is_mountpoint "$WORKDIR/mount"; then
|
| |
+ echo "Not mounted?"
|
| |
+ exit 1
|
| |
Signed-off-by: Michel Alexandre Salim salimma@fedoraproject.org