diff --git a/70.patch b/70.patch new file mode 100644 index 0000000..9d8660d --- /dev/null +++ b/70.patch @@ -0,0 +1,1784 @@ +From 2ff837b814e84eedc25574014da9ed24ee44b7b4 Mon Sep 17 00:00:00 2001 +From: Kevin Vigor +Date: Thu, 6 Feb 2020 09:59:56 -0800 +Subject: [PATCH 1/3] Add locking to cache API in anticipation of multithreaded + decompression. + +The existing cache API has effectively no locking and relies on the single- +threaded nature of the code to prevent contention on cache entries. +As a first step to multithreading the driver, refactor the cache API to allow +alternative implementations. In this changeset the API is changed but the +internal cache implementation is not make thread-safe, so it still suitable +only for single-threaded usage. + +Specific changes: + +* sqfs_cache type is made opaque. +* previously, newly allocated cache entries were assumed valid. This meant that + on any failure path following a cache entry allocation, one had to be careful + to call sqfs_cache_invalidate(). The assumption is now reversed, cache + entries are invalid until explicitly marked valid with + sqfs_cache_entry_valid(). This simplifies error handling. +* block cache code was intermixed with generic cache code, relocate to fs.c +* cache eviction led to object destruction in block cache. The only thing + preventing cache eviction while block object in use is single-threaded + nature of code. Instead use a refcounting mechanism on the block entries + so that we can independently manage block lifetime. +--- + Makefile.am | 6 +- + cache.c | 142 +++++++++++++++++++++++++++++----------------- + cache.h | 40 ++++++------- + common.h | 26 ++++++++- + file.c | 23 ++++---- + file.h | 4 -- + fs.c | 61 ++++++++++++++++---- + fs.h | 5 ++ + table.c | 2 +- + tests/cachetest.c | 107 ++++++++++++++++++++++++++++++++++ + 10 files changed, 314 insertions(+), 102 deletions(-) + create mode 100644 tests/cachetest.c + +diff --git a/Makefile.am b/Makefile.am +index 5659cd22..eaf7ac97 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -105,9 +105,11 @@ endif + TESTS = + if SQ_FUSE_TESTS + TESTS += tests/ll-smoke.sh +-check_PROGRAMS = endiantest ++check_PROGRAMS = cachetest endiantest ++cachetest_SOURCES=tests/cachetest.c ++cachetest_LDADD=libsquashfuse.la $(COMPRESSION_LIBS) + endiantest_SOURCES = tests/endiantest.c +-TESTS += endiantest ++TESTS += cachetest endiantest + endif + if SQ_DEMO_TESTS + TESTS += tests/ls.sh +diff --git a/cache.c b/cache.c +index 0deacfca..36d02234 100644 +--- a/cache.c ++++ b/cache.c +@@ -22,85 +22,121 @@ + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ ++ ++#include "config.h" + #include "cache.h" + + #include "fs.h" + ++#include + #include + ++typedef struct sqfs_cache_internal { ++ uint8_t *buf; ++ ++ sqfs_cache_dispose dispose; ++ ++ size_t size, count; ++ size_t next; /* next block to evict */ ++} sqfs_cache_internal; ++ ++typedef struct { ++ int valid; ++ sqfs_cache_idx idx; ++} sqfs_cache_entry_hdr; ++ + sqfs_err sqfs_cache_init(sqfs_cache *cache, size_t size, size_t count, +- sqfs_cache_dispose dispose) { +- cache->size = size; +- cache->count = count; +- cache->dispose = dispose; +- cache->next = 0; +- +- cache->idxs = calloc(count, sizeof(sqfs_cache_idx)); +- cache->buf = calloc(count, size); +- if (cache->idxs && cache->buf) ++ sqfs_cache_dispose dispose) { ++ ++ sqfs_cache_internal *c = malloc(sizeof(sqfs_cache_internal)); ++ if (!c) { ++ return SQFS_ERR; ++ } ++ ++ c->size = size + sizeof(sqfs_cache_entry_hdr); ++ c->count = count; ++ c->dispose = dispose; ++ c->next = 0; ++ ++ c->buf = calloc(count, c->size); ++ ++ if (c->buf) { ++ *cache = c; + return SQFS_OK; +- +- sqfs_cache_destroy(cache); ++ } ++ ++ sqfs_cache_destroy(&c); + return SQFS_ERR; + } + +-static void *sqfs_cache_entry(sqfs_cache *cache, size_t i) { +- return cache->buf + i * cache->size; ++static sqfs_cache_entry_hdr *sqfs_cache_entry_header( ++ sqfs_cache_internal* cache, ++ size_t i) { ++ return (sqfs_cache_entry_hdr *)(cache->buf + i * cache->size); ++} ++ ++static void* sqfs_cache_entry(sqfs_cache_internal* cache, size_t i) { ++ return (void *)(sqfs_cache_entry_header(cache, i) + 1); + } + + void sqfs_cache_destroy(sqfs_cache *cache) { +- if (cache->buf && cache->idxs) { +- size_t i; +- for (i = 0; i < cache->count; ++i) { +- if (cache->idxs[i] != SQFS_CACHE_IDX_INVALID) +- cache->dispose(sqfs_cache_entry(cache, i)); ++ 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->valid) { ++ c->dispose((void *)(hdr + 1)); ++ } ++ } + } ++ free(c->buf); ++ free(c); ++ *cache = NULL; + } +- free(cache->buf); +- free(cache->idxs); + } + + void *sqfs_cache_get(sqfs_cache *cache, sqfs_cache_idx idx) { + size_t i; +- for (i = 0; i < cache->count; ++i) { +- if (cache->idxs[i] == idx) +- return sqfs_cache_entry(cache, i); ++ sqfs_cache_internal *c = *cache; ++ sqfs_cache_entry_hdr *hdr; ++ ++ for (i = 0; i < c->count; ++i) { ++ hdr = sqfs_cache_entry_header(c, i); ++ if (hdr->idx == idx) { ++ assert(hdr->valid); ++ return sqfs_cache_entry(c, i); ++ } + } +- return NULL; +-} + +-void *sqfs_cache_add(sqfs_cache *cache, sqfs_cache_idx idx) { +- size_t i = (cache->next++); +- cache->next %= cache->count; +- +- if (cache->idxs[i] != SQFS_CACHE_IDX_INVALID) +- cache->dispose(sqfs_cache_entry(cache, i)); +- +- cache->idxs[i] = idx; +- return sqfs_cache_entry(cache, i); +-} ++ /* No existing entry; free one if necessary, allocate a new one. */ ++ i = (c->next++); ++ c->next %= c->count; + +-/* sqfs_cache_add can be called but the caller can fail to fill it (IO +- * error, etc). sqfs_cache_invalidate invalidates the cache entry. +- * It does not call dispose; it merely marks the entry as reusable +- * since it is never fully initialized. +- */ +-void sqfs_cache_invalidate(sqfs_cache *cache, sqfs_cache_idx idx) { +- size_t i; +- for (i = 0; i < cache->count; ++i) { +- if (cache->idxs[i] == idx) { +- cache->idxs[i] = SQFS_CACHE_IDX_INVALID; +- return; +- } ++ hdr = sqfs_cache_entry_header(c, i); ++ if (hdr->valid) { ++ /* evict */ ++ c->dispose((void *)(hdr + 1)); ++ hdr->valid = 0; + } ++ ++ hdr->idx = idx; ++ return (void *)(hdr + 1); ++} ++ ++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->valid; + } + +-static void sqfs_block_cache_dispose(void *data) { +- sqfs_block_cache_entry *entry = (sqfs_block_cache_entry*)data; +- sqfs_block_dispose(entry->block); ++void sqfs_cache_entry_mark_valid(sqfs_cache *cache, void *e) { ++ sqfs_cache_entry_hdr *hdr = ((sqfs_cache_entry_hdr *)e) - 1; ++ assert(hdr->valid == 0); ++ hdr->valid = 1; + } + +-sqfs_err sqfs_block_cache_init(sqfs_cache *cache, size_t count) { +- return sqfs_cache_init(cache, sizeof(sqfs_block_cache_entry), count, +- &sqfs_block_cache_dispose); ++void sqfs_cache_put(const sqfs_cache *cache, const void *e) { ++ // nada, we have no locking in single-threaded implementation. + } +diff --git a/cache.h b/cache.h +index b78c524d..da471352 100644 +--- a/cache.h ++++ b/cache.h +@@ -33,35 +33,37 @@ + * - No thread safety + * - Misses are caller's responsibility + */ +-#define SQFS_CACHE_IDX_INVALID 0 + + typedef uint64_t sqfs_cache_idx; + typedef void (*sqfs_cache_dispose)(void* data); + +-typedef struct { +- sqfs_cache_idx *idxs; +- uint8_t *buf; +- +- sqfs_cache_dispose dispose; +- +- size_t size, count; +- size_t next; /* next block to evict */ +-} sqfs_cache; ++struct sqfs_cache_internal; ++typedef struct sqfs_cache_internal *sqfs_cache; + + sqfs_err sqfs_cache_init(sqfs_cache *cache, size_t size, size_t count, + sqfs_cache_dispose dispose); + void sqfs_cache_destroy(sqfs_cache *cache); + ++/* Get an entry for the given index. ++ * ++ * This will always succeed (evicting if necessary). The caller must then ++ * call sqfs_cache_entry_valid() to determine if the entry is valid. If not ++ * valid, the entry is newly allocated and the caller is responsible for ++ * initializing it and then calling sqfs_cache_entry_mark_valid(). ++ * ++ * This call may block in multithreaded case. ++ * ++ * In multithreaded case, the cache is locked on return (no entries can ++ * be added or removed). Caller must call sqfs_cache_put() when it is safe ++ * to evict the returned cache entry. ++ */ + void *sqfs_cache_get(sqfs_cache *cache, sqfs_cache_idx idx); +-void *sqfs_cache_add(sqfs_cache *cache, sqfs_cache_idx idx); +-void sqfs_cache_invalidate(sqfs_cache *cache, sqfs_cache_idx idx); +- +- +-typedef struct { +- sqfs_block *block; +- size_t data_size; +-} sqfs_block_cache_entry; ++/* inform cache it is now safe to evict this entry. */ ++void sqfs_cache_put(const sqfs_cache *cache, const void *e); + +-sqfs_err sqfs_block_cache_init(sqfs_cache *cache, size_t count); ++/* Determine if cache entry contains valid contents. */ ++int sqfs_cache_entry_valid(const sqfs_cache *cache, const void *e); ++/* Mark cache entry as containing valid contents. */ ++void sqfs_cache_entry_mark_valid(sqfs_cache *cache, void *e); + + #endif +diff --git a/common.h b/common.h +index aeac5c67..9d50e006 100644 +--- a/common.h ++++ b/common.h +@@ -32,12 +32,23 @@ + #include + + #ifdef _WIN32 +- #include ++# include ++# include ++# define atomic_inc_relaxed(ptr) \ ++ _InterlockedIncrement(ptr) ++# define atomic_dec_acqrel(ptr) \ ++ _InterlockedDecrement(ptr) + #else + typedef mode_t sqfs_mode_t; + typedef uid_t sqfs_id_t; + typedef off_t sqfs_off_t; + typedef int sqfs_fd_t; ++ ++# define atomic_inc_relaxed(ptr) \ ++ __atomic_add_fetch(&block->refcount, 1, __ATOMIC_RELAXED) ++# define atomic_dec_acqrel(ptr) \ ++ __atomic_sub_fetch(&block->refcount, 1, __ATOMIC_ACQ_REL) ++ + #endif + + typedef enum { +@@ -59,6 +70,7 @@ typedef struct sqfs_inode sqfs_inode; + typedef struct { + size_t size; + void *data; ++ long refcount; + } sqfs_block; + + typedef struct { +@@ -66,4 +78,16 @@ typedef struct { + size_t offset; + } sqfs_md_cursor; + ++/* Increment the refcount on the block. */ ++static inline void sqfs_block_ref(sqfs_block *block) { ++ atomic_inc_relaxed(&block->refcount); ++} ++ ++/* decrement the refcount on the block, return non-zero if we held the last ++ * reference. ++ */ ++static inline int sqfs_block_deref(sqfs_block *block) { ++ return atomic_dec_acqrel(&block->refcount) == 0; ++} ++ + #endif +diff --git a/file.c b/file.c +index a4d894eb..d09f2a7d 100644 +--- a/file.c ++++ b/file.c +@@ -177,7 +177,7 @@ sqfs_err sqfs_read_range(sqfs *fs, sqfs_inode *inode, sqfs_off_t start, + take = (size_t)(*size); + if (block) { + memcpy(buf, (char*)block->data + data_off + read_off, take); +- /* BLOCK CACHED, DON'T DISPOSE */ ++ sqfs_block_dispose(block); + } else { + memset(buf, 0, take); + } +@@ -226,7 +226,7 @@ sqfs_err sqfs_blockidx_init(sqfs_cache *cache) { + } + + sqfs_err sqfs_blockidx_add(sqfs *fs, sqfs_inode *inode, +- sqfs_blockidx_entry **out) { ++ sqfs_blockidx_entry **out, sqfs_blockidx_entry **cachep) { + size_t blocks; /* Number of blocks in the file */ + size_t md_size; /* Amount of metadata necessary to hold the blocksizes */ + size_t count; /* Number of block-index entries necessary */ +@@ -234,10 +234,6 @@ sqfs_err sqfs_blockidx_add(sqfs *fs, sqfs_inode *inode, + sqfs_blockidx_entry *blockidx; + sqfs_blocklist bl; + +- /* For the cache */ +- sqfs_cache_idx idx; +- sqfs_blockidx_entry **cachep; +- + size_t i = 0; + bool first = true; + +@@ -270,8 +266,6 @@ sqfs_err sqfs_blockidx_add(sqfs *fs, sqfs_inode *inode, + } + } + +- idx = inode->base.inode_number + 1; /* zero means invalid */ +- cachep = sqfs_cache_add(&fs->blockidx, idx); + *out = *cachep = blockidx; + return SQFS_OK; + } +@@ -299,12 +293,16 @@ sqfs_err sqfs_blockidx_blocklist(sqfs *fs, sqfs_inode *inode, + + /* Get the index, creating it if necessary */ + idx = inode->base.inode_number + 1; /* zero means invalid index */ +- if ((bp = sqfs_cache_get(&fs->blockidx, idx))) { ++ bp = sqfs_cache_get(&fs->blockidx, idx); ++ if (sqfs_cache_entry_valid(&fs->blockidx, bp)) { + blockidx = *bp; + } else { +- sqfs_err err = sqfs_blockidx_add(fs, inode, &blockidx); +- if (err) ++ sqfs_err err = sqfs_blockidx_add(fs, inode, &blockidx, bp); ++ if (err) { ++ sqfs_cache_put(&fs->blockidx, bp); + return err; ++ } ++ sqfs_cache_entry_mark_valid(&fs->blockidx, bp); + } + + skipped = (metablock * SQUASHFS_METADATA_SIZE / sizeof(sqfs_blocklist_entry)) +@@ -316,6 +314,9 @@ sqfs_err sqfs_blockidx_blocklist(sqfs *fs, sqfs_inode *inode, + bl->remain -= skipped; + bl->pos = (uint64_t)skipped * fs->sb.block_size; + bl->block = blockidx->data_block; ++ ++ sqfs_cache_put(&fs->blockidx, bp); ++ + return SQFS_OK; + } + +diff --git a/file.h b/file.h +index 249c6413..e3d2b028 100644 +--- a/file.h ++++ b/file.h +@@ -71,10 +71,6 @@ typedef struct { + + sqfs_err sqfs_blockidx_init(sqfs_cache *cache); + +-/* Fill *out with all the block-index entries for this file */ +-sqfs_err sqfs_blockidx_add(sqfs *fs, sqfs_inode *inode, +- sqfs_blockidx_entry **out); +- + /* Get a blocklist fast-forwarded to the correct location */ + sqfs_err sqfs_blockidx_blocklist(sqfs *fs, sqfs_inode *inode, + sqfs_blocklist *bl, sqfs_off_t start); +diff --git a/fs.c b/fs.c +index d69bb681..1838c5ca 100644 +--- a/fs.c ++++ b/fs.c +@@ -124,6 +124,8 @@ sqfs_err sqfs_block_read(sqfs *fs, sqfs_off_t pos, bool compressed, + sqfs_err err = SQFS_ERR; + if (!(*block = malloc(sizeof(**block)))) + return SQFS_ERR; ++ /* start with refcount one, so dispose on failure path works as expected. */ ++ (*block)->refcount = 1; + if (!((*block)->data = malloc(size))) + goto error; + +@@ -188,44 +190,81 @@ sqfs_err sqfs_data_block_read(sqfs *fs, sqfs_off_t pos, uint32_t hdr, + } + + sqfs_err sqfs_md_cache(sqfs *fs, sqfs_off_t *pos, sqfs_block **block) { +- sqfs_block_cache_entry *entry = sqfs_cache_get( +- &fs->md_cache, *pos); +- if (!entry) { ++ sqfs_block_cache_entry *entry = sqfs_cache_get(&fs->md_cache, *pos); ++ if (!sqfs_cache_entry_valid(&fs->md_cache, entry)) { + sqfs_err err = SQFS_OK; +- entry = sqfs_cache_add(&fs->md_cache, *pos); + /* fprintf(stderr, "MD BLOCK: %12llx\n", (long long)*pos); */ + err = sqfs_md_block_read(fs, *pos, + &entry->data_size, &entry->block); + if (err) { +- sqfs_cache_invalidate(&fs->md_cache, *pos); ++ sqfs_cache_put(&fs->md_cache, entry); + return err; + } ++ sqfs_cache_entry_mark_valid(&fs->md_cache, entry); + } ++ /* block is created with refcount 1, which accounts for presence in the ++ * cache (will be decremented on eviction). ++ * ++ * We increment it here as a convienience for the caller, who will ++ * obviously want one. Therefore all callers must eventually call deref ++ * by means of calling sqfs_block_dispose(). ++ */ + *block = entry->block; + *pos += entry->data_size; ++ ++ sqfs_block_ref(entry->block); ++ /* it is now safe to evict the entry from the cache, we have a ++ * reference to the block so eviction will not destroy it. ++ */ ++ sqfs_cache_put(&fs->md_cache, entry); ++ + return SQFS_OK; + } + + sqfs_err sqfs_data_cache(sqfs *fs, sqfs_cache *cache, sqfs_off_t pos, + uint32_t hdr, sqfs_block **block) { + sqfs_block_cache_entry *entry = sqfs_cache_get(cache, pos); +- if (!entry) { ++ if (!sqfs_cache_entry_valid(cache, entry)) { + sqfs_err err = SQFS_OK; +- entry = sqfs_cache_add(cache, pos); + err = sqfs_data_block_read(fs, pos, hdr, + &entry->block); + if (err) { +- sqfs_cache_invalidate(cache, pos); ++ sqfs_cache_put(cache, entry); + return err; + } ++ sqfs_cache_entry_mark_valid(cache, entry); + } ++ /* block is created with refcount 1, which accounts for presence in the ++ * cache (will be decremented on eviction). ++ * ++ * We increment it here as a convenience for the caller, who will ++ * obviously want one. Therefore all callers must eventually call deref ++ * by means of calling sqfs_block_dispose(). ++ */ + *block = entry->block; ++ sqfs_block_ref(*block); ++ /* it is now safe to evict the entry from the cache, we have a ++ * reference to the block so eviction will not destroy it. ++ */ ++ sqfs_cache_put(cache, entry); + return SQFS_OK; + } + + void sqfs_block_dispose(sqfs_block *block) { +- free(block->data); +- free(block); ++ if (sqfs_block_deref(block)) { ++ free(block->data); ++ free(block); ++ } ++} ++ ++static void sqfs_block_cache_dispose(void *data) { ++ sqfs_block_cache_entry *entry = (sqfs_block_cache_entry*)data; ++ sqfs_block_dispose(entry->block); ++} ++ ++sqfs_err sqfs_block_cache_init(sqfs_cache *cache, size_t count) { ++ return sqfs_cache_init(cache, sizeof(sqfs_block_cache_entry), count, ++ &sqfs_block_cache_dispose); + } + + void sqfs_md_cursor_inode(sqfs_md_cursor *cur, sqfs_inode_id id, sqfs_off_t base) { +@@ -247,7 +286,6 @@ sqfs_err sqfs_md_read(sqfs *fs, sqfs_md_cursor *cur, void *buf, size_t size) { + take = size; + if (buf) + memcpy(buf, (char*)block->data + cur->offset, take); +- /* BLOCK CACHED, DON'T DISPOSE */ + + if (buf) + buf = (char*)buf + take; +@@ -257,6 +295,7 @@ sqfs_err sqfs_md_read(sqfs *fs, sqfs_md_cursor *cur, void *buf, size_t size) { + cur->block = pos; + cur->offset = 0; + } ++ sqfs_block_dispose(block); + } + return SQFS_OK; + } +diff --git a/fs.h b/fs.h +index d300a3bb..1d475ce0 100644 +--- a/fs.h ++++ b/fs.h +@@ -97,6 +97,11 @@ sqfs_compression_type sqfs_compression(sqfs *fs); + void sqfs_md_header(uint16_t hdr, bool *compressed, uint16_t *size); + void sqfs_data_header(uint32_t hdr, bool *compressed, uint32_t *size); + ++typedef struct { ++ sqfs_block *block; ++ size_t data_size; ++} sqfs_block_cache_entry; ++sqfs_err sqfs_block_cache_init(sqfs_cache *cache, size_t count); + sqfs_err sqfs_block_read(sqfs *fs, sqfs_off_t pos, bool compressed, uint32_t size, + size_t outsize, sqfs_block **block); + void sqfs_block_dispose(sqfs_block *block); +diff --git a/table.c b/table.c +index c035398f..02a5442c 100644 +--- a/table.c ++++ b/table.c +@@ -76,6 +76,6 @@ sqfs_err sqfs_table_get(sqfs_table *table, sqfs *fs, size_t idx, void *buf) { + return SQFS_ERR; + + memcpy(buf, (char*)(block->data) + off, table->each); +- /* BLOCK CACHED, DON'T DISPOSE */ ++ sqfs_block_dispose(block); + return SQFS_OK; + } +diff --git a/tests/cachetest.c b/tests/cachetest.c +new file mode 100644 +index 00000000..8a2c2363 +--- /dev/null ++++ b/tests/cachetest.c +@@ -0,0 +1,107 @@ ++#include "cache.h" ++#include ++ ++typedef struct { ++ int x; ++ int y; ++} TestStruct; ++ ++static void TestStructDispose(void *t) { ++ // nada. ++} ++ ++#define EXPECT_EQ(exp1, exp2) \ ++ do { if ((exp1) != (exp2)) { \ ++ printf("Test failure: expected " #exp1 " to equal " #exp2 \ ++ " at " __FILE__ ":%d\n", __LINE__); \ ++ ++errors; \ ++ } \ ++ } while (0) ++ ++#define EXPECT_NE(exp1, exp2) \ ++ do { if ((exp1) == (exp2)) { \ ++ printf("Test failure: expected " #exp1 " to !equal " #exp2 \ ++ " at " __FILE__ ":%d\n", __LINE__); \ ++ ++errors; \ ++ } \ ++ } while (0) ++ ++ ++int test_cache_miss(void) { ++ int errors = 0; ++ sqfs_cache cache; ++ TestStruct *entry; ++ ++ EXPECT_EQ(sqfs_cache_init(&cache, sizeof(TestStruct), 16, ++ TestStructDispose), SQFS_OK); ++ entry = (TestStruct *)sqfs_cache_get(&cache, 1); ++ EXPECT_EQ(sqfs_cache_entry_valid(&cache, entry), 0); ++ sqfs_cache_destroy(&cache); ++ ++ return errors == 0; ++} ++ ++int test_mark_valid_and_lookup(void) { ++ int errors = 0; ++ sqfs_cache cache; ++ TestStruct *entry; ++ ++ EXPECT_EQ(sqfs_cache_init(&cache, sizeof(TestStruct), 16, ++ TestStructDispose), SQFS_OK); ++ entry = (TestStruct *)sqfs_cache_get(&cache, 1); ++ entry->x = 666; ++ entry->y = 777; ++ sqfs_cache_entry_mark_valid(&cache, entry); ++ sqfs_cache_put(&cache, entry); ++ EXPECT_NE(sqfs_cache_entry_valid(&cache, entry), 0); ++ entry = (TestStruct *)sqfs_cache_get(&cache, 1); ++ EXPECT_NE(sqfs_cache_entry_valid(&cache, entry), 0); ++ EXPECT_EQ(entry->x, 666); ++ EXPECT_EQ(entry->y, 777); ++ sqfs_cache_put(&cache, entry); ++ ++ sqfs_cache_destroy(&cache); ++ return errors == 0; ++} ++ ++int test_two_entries(void) { ++ int errors = 0; ++ sqfs_cache cache; ++ TestStruct *entry1, *entry2; ++ ++ EXPECT_EQ(sqfs_cache_init(&cache, sizeof(TestStruct), 16, ++ TestStructDispose), SQFS_OK); ++ ++ entry1 = (TestStruct *)sqfs_cache_get(&cache, 1); ++ entry1->x = 1; ++ entry1->y = 2; ++ sqfs_cache_entry_mark_valid(&cache, entry1); ++ sqfs_cache_put(&cache, entry1); ++ ++ entry2 = (TestStruct *)sqfs_cache_get(&cache, 666); ++ entry2->x = 3; ++ entry2->y = 4; ++ sqfs_cache_entry_mark_valid(&cache, entry2); ++ sqfs_cache_put(&cache, entry2); ++ ++ entry1 = (TestStruct *)sqfs_cache_get(&cache, 1); ++ sqfs_cache_put(&cache, entry1); ++ entry2 = (TestStruct *)sqfs_cache_get(&cache, 666); ++ sqfs_cache_put(&cache, entry2); ++ EXPECT_NE(sqfs_cache_entry_valid(&cache, entry1), 0); ++ EXPECT_NE(sqfs_cache_entry_valid(&cache, entry2), 0); ++ EXPECT_EQ(entry1->x, 1); ++ EXPECT_EQ(entry1->y, 2); ++ EXPECT_EQ(entry2->x, 3); ++ EXPECT_EQ(entry2->y, 4); ++ ++ sqfs_cache_destroy(&cache); ++ ++ return errors == 0; ++} ++ ++int main(void) { ++ return test_cache_miss() && ++ test_mark_valid_and_lookup() && ++ test_two_entries() ? 0 : 1; ++} + +From 379c8507c15ef43b641c1024b43372f2de9fb480 Mon Sep 17 00:00:00 2001 +From: Kevin Vigor +Date: Thu, 6 Feb 2020 12:20:19 -0800 +Subject: [PATCH 2/3] 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 ++#include ++#include ++ ++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 + + +-#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 +- ])],[ +- 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 + +From 069e8f802481af0636fabef46bec75de1992b220 Mon Sep 17 00:00:00 2001 +From: Kevin Vigor +Date: Mon, 23 May 2022 14:55:25 -0600 +Subject: [PATCH 3/3] Enable lazy umount on SIGTERM. + +libfuse sets SIGTERM signal handler to exit immediately. This is very +unfortunate if any other processes are still using the filesystem. + +Teach squashfuse_ll to respond to SIGTERM with lazy umount. We cannot +directly call umount2() API from the signal handler, since it is not +signal safe, but we can fork/exec fusermount3 (yay posix?). This is +also a win because fusermount is suid, enabling non-privileged users +to umount. Note that normal libfuse umount uses same strategy when +running as non-root. + +Note that this must be explicitly enabled at configure time with +--enable-sigterm-handler, and it is only tested on linux. +--- + Makefile.am | 3 ++ + configure.ac | 9 +++++ + ll_main.c | 87 +++++++++++++++++++++++++++++++++++++++++ + tests/umount-test.sh.in | 85 ++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 184 insertions(+) + create mode 100755 tests/umount-test.sh.in + +diff --git a/Makefile.am b/Makefile.am +index 17b01be4..67c17cde 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -111,6 +111,9 @@ if MULTITHREADED + # that case. + TESTS += tests/ll-smoke-singlethreaded.sh + endif ++if SIGTERM_HANDLER ++TESTS += tests/umount-test.sh ++endif + check_PROGRAMS = cachetest endiantest + cachetest_SOURCES=tests/cachetest.c + cachetest_LDADD=libsquashfuse.la $(COMPRESSION_LIBS) +diff --git a/configure.ac b/configure.ac +index 3869075a..7cd4db77 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -91,6 +91,7 @@ AS_IF([test "x$sq_tests" = x], [sq_tests=" none"]) + + AC_SUBST([sq_mksquashfs_compressors]) + AC_CONFIG_FILES([tests/ll-smoke.sh],[chmod +x tests/ll-smoke.sh]) ++AC_CONFIG_FILES([tests/umount-test.sh],[chmod +x tests/umount-test.sh]) + + + AS_IF([test "x$sq_high_level$sq_low_level$sq_demo" = xnonono], +@@ -104,6 +105,14 @@ AC_ARG_ENABLE([multithreading], + ]) + AM_CONDITIONAL([MULTITHREADED], [test x$enable_multithreading = xyes]) + ++AC_ARG_ENABLE([sigterm-handler], ++ AS_HELP_STRING([--enable-sigterm-handler], [enable lazy umount on SIGTERM in low-level FUSE driver]), ++ [ ++ AC_CHECK_HEADER([linux/version.h], , [], AC_MSG_ERROR([linux host required for sigterm-handler.])) ++ AC_DEFINE(SQFS_SIGTERM_HANDLER, 1, [Enable lazy umount on SIGTERM in low-level FUSE driver]) ++ ]) ++AM_CONDITIONAL([SIGTERM_HANDLER], [test x$enable_sigterm_handler = xyes]) ++ + AC_SUBST([sq_decompressors]) + AC_SUBST([sq_high_level]) + AC_SUBST([sq_low_level]) +diff --git a/ll_main.c b/ll_main.c +index 22302085..0f956b5d 100644 +--- a/ll_main.c ++++ b/ll_main.c +@@ -37,6 +37,90 @@ + #include + #include + ++ ++#if defined(SQFS_SIGTERM_HANDLER) ++#include ++#include ++static bool kernel_version_at_least(unsigned required_major, ++ unsigned required_minor, ++ unsigned required_micro) { ++ struct utsname info; ++ ++ if (uname(&info) >= 0) { ++ unsigned major, minor, micro; ++ ++ if (sscanf(info.release, "%u.%u.%u", &major, &minor, µ) == 3) { ++ return KERNEL_VERSION(major, minor, micro) >= ++ KERNEL_VERSION(required_major, required_minor, required_micro); ++ } ++ } ++ return false; ++} ++ ++/* libfuse's default SIGTERM handler (set up in fuse_set_signal_handlers()) ++ * immediately calls fuse_session_exit(), which shuts down the filesystem ++ * even if there are active users. This leads to nastiness if other processes ++ * still depend on the filesystem. ++ * ++ * So: we respond to SIGTERM by starting a lazy unmount. This is done ++ * by exec'ing fusermount3, which works properly for unpriviledged ++ * users (we cannot use umount2() syscall because it is not signal safe; ++ * fork() and exec(), amazingly, are). ++ * ++ * If we fail to start the lazy umount, we signal ourself with SIGINT, ++ * which falls back to the old behavior of exiting ASAP. ++ */ ++static const char *g_mount_point = NULL; ++static void sigterm_handler(int signum) { ++ /* Unfortunately, lazy umount of in-use fuse filesystem triggers ++ * kernel bug on kernels < 5.2, Fixed by kernel commit ++ * e8f3bd773d22f488724dffb886a1618da85c2966 in 5.2. ++ */ ++ if (g_mount_point && kernel_version_at_least(5,2,0)) { ++ int pid = fork(); ++ if (pid == 0) { ++ /* child process: disassociate ourself from parent so ++ * we do not become zombie (as parent does not waitpid()). ++ */ ++ pid_t parent = getppid(); ++ setsid(); ++ execl("/bin/fusermount3", "fusermount3", ++ "-u", "-q", "-z", "--", g_mount_point, NULL); ++ execlp("fusermount3", "fusermount3", ++ "-u", "-q", "-z", "--", g_mount_point, NULL); ++ /* if we get here, we can't run fusermount, ++ * kill the original process with a harshness. ++ */ ++ kill(parent, SIGINT); ++ _exit(0); ++ } else if (pid > 0) { ++ /* parent process: nothing to do, murderous child will do us ++ * in one way or another. ++ */ ++ return; ++ } ++ } ++ /* If we get here, we have failed to lazy unmount for whatever reason, ++ * kill ourself more brutally. ++ */ ++ kill(getpid(), SIGINT); ++} ++ ++static void set_sigterm_handler(const char *mountpoint) { ++ struct sigaction sa; ++ ++ g_mount_point = mountpoint; ++ memset(&sa, 0, sizeof(sa)); ++ sa.sa_handler = sigterm_handler; ++ sigemptyset(&(sa.sa_mask)); ++ sa.sa_flags = SA_RESTART; ++ ++ if (sigaction(SIGTERM, &sa, NULL) == -1) { ++ perror("sigaction(SIGTERM)"); ++ } ++} ++#endif /* SQFS_SIGTERM_HANDLER */ ++ + int main(int argc, char *argv[]) { + struct fuse_args args; + sqfs_opts opts; +@@ -139,6 +223,9 @@ int main(int argc, char *argv[]) { + ll) == SQFS_OK) { + if (sqfs_ll_daemonize(fuse_cmdline_opts.foreground) != -1) { + if (fuse_set_signal_handlers(ch.session) != -1) { ++#if defined(SQFS_SIGTERM_HANDLER) ++ set_sigterm_handler(fuse_cmdline_opts.mountpoint); ++#endif + if (opts.idle_timeout_secs) { + setup_idle_timeout(ch.session, opts.idle_timeout_secs); + } +diff --git a/tests/umount-test.sh.in b/tests/umount-test.sh.in +new file mode 100755 +index 00000000..06fed533 +--- /dev/null ++++ b/tests/umount-test.sh.in +@@ -0,0 +1,85 @@ ++#!/bin/bash ++ ++. "tests/lib.sh" ++ ++SFLL=${1:-./squashfuse_ll} # The squashfuse_ll binary. ++TIMEOUT=20 ++ ++case @build_os@ in ++ linux*) ++ ;; ++ *) ++ echo "This test is only enabled on linux hosts." ++ exit 0 ++ ;; ++esac ++ ++function cleanup { ++ set +e ++ if [[ -n "$TAIL_PID" ]]; then ++ kill "$TAIL_PID" ++ fi ++ @sq_fusermount@ -u "$MNTDIR" >& /dev/null ++ rm -rf "$WORKDIR" ++} ++ ++set -e ++WORKDIR=$(mktemp -d) ++MNTDIR="$WORKDIR/mountpoint" ++mkdir -p "$MNTDIR" ++mkdir -p "$WORKDIR/source" ++trap cleanup EXIT ++ ++# Make a tiny squashfs filesystem. ++echo "Hello world" >"$WORKDIR/source/hello" ++mksquashfs "$WORKDIR/source" "$WORKDIR/squashfs.image" -comp zstd -no-progress >& /dev/null ++ ++# Mount it. ++$SFLL "$WORKDIR/squashfs.image" "$MNTDIR" ++SFPID=$(pgrep -f "squashfuse_ll.*$MNTDIR") ++ ++if ! [[ -d /proc/$SFPID ]]; then ++ echo "squashfuse process missing" ++ exit 1 ++fi ++if ! grep -q "$MNTDIR" /proc/mounts; then ++ echo "mount missing." ++ exit 1 ++fi ++ ++# background a task to hold a file open from the image. ++tail -f "${MNTDIR}/hello" >/dev/null & ++TAIL_PID=$! ++ ++# SIGTERM the squashfuse process. ++kill -15 "$SFPID" ++ ++# Now we expect the mountpoint to disappear due to lazy umount. ++if ! timeout $TIMEOUT bash -c \ ++ "while grep -q $MNTDIR /proc/mounts; do \ ++ sleep 1; ++ done"; then ++ echo "$MNTDIR did not dismount in response to SIGTERM." ++ exit 1 ++fi ++ ++# but the process should remain alive, because of the background task. ++if ! [[ -d /proc/$SFPID ]]; then ++ echo "squashfuse process missing" ++ exit 1 ++fi ++ ++# Now kill the background process. ++kill $TAIL_PID ++TAIL_PID= ++ ++# Now we expect the process to die. ++if ! timeout $TIMEOUT bash -c \ ++ "while [[ -d /proc/$SFPID ]]; do \ ++ sleep 1; ++ done"; then ++ echo "squashfuse process did not die once filesystem was released." ++ exit 1 ++fi ++ ++echo "Success." diff --git a/77.patch b/77.patch new file mode 100644 index 0000000..099332c --- /dev/null +++ b/77.patch @@ -0,0 +1,181 @@ +From b816b986f063f2f7ba5f517669a1d36d1c40ce16 Mon Sep 17 00:00:00 2001 +From: Dave Dykstra <2129743+DrDaveD@users.noreply.github.com> +Date: Thu, 6 Oct 2022 18:21:57 -0500 +Subject: [PATCH] add low-level uid=N and gid=N options, better help + +--- + fs.h | 2 ++ + fuseprivate.c | 12 ++++++++++-- + fuseprivate.h | 4 +++- + hl.c | 4 ++-- + ll_main.c | 12 +++++++++--- + stat.c | 24 ++++++++++++++++-------- + 6 files changed, 42 insertions(+), 16 deletions(-) + +diff --git a/fs.h b/fs.h +index d300a3bb..dc00d54a 100644 +--- a/fs.h ++++ b/fs.h +@@ -36,6 +36,8 @@ + struct sqfs { + sqfs_fd_t fd; + size_t offset; ++ int uid; ++ int gid; + struct squashfs_super_block sb; + sqfs_table id_table; + sqfs_table frag_table; +diff --git a/fuseprivate.c b/fuseprivate.c +index a8e71191..b9a10182 100644 +--- a/fuseprivate.c ++++ b/fuseprivate.c +@@ -59,12 +59,20 @@ int sqfs_listxattr(sqfs *fs, sqfs_inode *inode, char *buf, size_t *size) { + return 0; + } + +-void sqfs_usage(char *progname, bool fuse_usage) { ++void sqfs_usage(char *progname, bool fuse_usage, bool ll_usage) { + fprintf(stderr, "%s (c) 2012 Dave Vasilevsky\n\n", PACKAGE_STRING); + fprintf(stderr, "Usage: %s [options] ARCHIVE MOUNTPOINT\n", + progname ? progname : PACKAGE_NAME); ++ fprintf(stderr, "\n%s options:\n", progname); ++ fprintf(stderr, " -o offset offset into ARCHIVE to mount\n"); ++ fprintf(stderr, " -o timeout idle seconds for automatic unmount\n"); ++ if (ll_usage) { ++ fprintf(stderr, " -o uid=N set file owner\n"); ++ fprintf(stderr, " -o gid=N set file group\n"); ++ } + if (fuse_usage) { + #if FUSE_USE_VERSION >= 30 ++ fprintf(stderr, "\nFUSE options:\n"); + fuse_cmdline_help(); + #else + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); +@@ -92,7 +100,7 @@ int sqfs_opt_proc(void *data, const char *arg, int key, + } + } else if (key == FUSE_OPT_KEY_OPT) { + if (strncmp(arg, "-h", 2) == 0 || strncmp(arg, "--h", 3) == 0) +- sqfs_usage(opts->progname, true); ++ return -1; + } + return 1; /* Keep */ + } +diff --git a/fuseprivate.h b/fuseprivate.h +index c978076c..29974b86 100644 +--- a/fuseprivate.h ++++ b/fuseprivate.h +@@ -40,7 +40,7 @@ + int sqfs_listxattr(sqfs *fs, sqfs_inode *inode, char *buf, size_t *size); + + /* Print a usage string */ +-void sqfs_usage(char *progname, bool fuse_usage); ++void sqfs_usage(char *progname, bool fuse_usage, bool ll_usage); + + /* Parse command-line arguments */ + typedef struct { +@@ -49,6 +49,8 @@ typedef struct { + int mountpoint; + size_t offset; + unsigned int idle_timeout_secs; ++ int uid; ++ int gid; + } sqfs_opts; + int sqfs_opt_proc(void *data, const char *arg, int key, + struct fuse_args *outargs); +diff --git a/hl.c b/hl.c +index 49673913..aa71e4e0 100644 +--- a/hl.c ++++ b/hl.c +@@ -325,9 +325,9 @@ int main(int argc, char *argv[]) { + opts.mountpoint = 0; + opts.offset = 0; + if (fuse_opt_parse(&args, &opts, fuse_opts, sqfs_opt_proc) == -1) +- sqfs_usage(argv[0], true); ++ sqfs_usage(argv[0], true, false); + if (!opts.image) +- sqfs_usage(argv[0], true); ++ sqfs_usage(argv[0], true, false); + + hl = sqfs_hl_open(opts.image, opts.offset); + if (!hl) +diff --git a/ll_main.c b/ll_main.c +index aca76935..5bc57abd 100644 +--- a/ll_main.c ++++ b/ll_main.c +@@ -55,6 +55,8 @@ int main(int argc, char *argv[]) { + struct fuse_opt fuse_opts[] = { + {"offset=%zu", offsetof(sqfs_opts, offset), 0}, + {"timeout=%u", offsetof(sqfs_opts, idle_timeout_secs), 0}, ++ {"uid=%d", offsetof(sqfs_opts, uid), 0}, ++ {"gid=%d", offsetof(sqfs_opts, gid), 0}, + FUSE_OPT_END + }; + +@@ -85,8 +87,10 @@ int main(int argc, char *argv[]) { + opts.mountpoint = 0; + opts.offset = 0; + opts.idle_timeout_secs = 0; ++ opts.uid = 0; ++ opts.gid = 0; + if (fuse_opt_parse(&args, &opts, fuse_opts, sqfs_opt_proc) == -1) +- sqfs_usage(argv[0], true); ++ sqfs_usage(argv[0], true, true); + + #if FUSE_USE_VERSION >= 30 + if (fuse_parse_cmdline(&args, &fuse_cmdline_opts) != 0) +@@ -96,9 +100,9 @@ int main(int argc, char *argv[]) { + &fuse_cmdline_opts.mt, + &fuse_cmdline_opts.foreground) == -1) + #endif +- sqfs_usage(argv[0], true); ++ sqfs_usage(argv[0], true, true); + if (fuse_cmdline_opts.mountpoint == NULL) +- sqfs_usage(argv[0], true); ++ sqfs_usage(argv[0], true, true); + + /* fuse_daemonize() will unconditionally clobber fds 0-2. + * +@@ -128,6 +132,8 @@ int main(int argc, char *argv[]) { + + /* STARTUP FUSE */ + if (!err) { ++ ll->fs.uid = opts.uid; ++ ll->fs.gid = opts.gid; + sqfs_ll_chan ch; + err = -1; + if (sqfs_ll_mount( +diff --git a/stat.c b/stat.c +index 29923f37..b425c0c0 100644 +--- a/stat.c ++++ b/stat.c +@@ -49,14 +49,22 @@ sqfs_err sqfs_stat(sqfs *fs, sqfs_inode *inode, struct stat *st) { + + st->st_blksize = fs->sb.block_size; /* seriously? */ + +- err = sqfs_id_get(fs, inode->base.uid, &id); +- if (err) +- return err; +- st->st_uid = id; +- err = sqfs_id_get(fs, inode->base.guid, &id); +- st->st_gid = id; +- if (err) +- return err; ++ if (fs->uid > 0) { ++ st->st_uid = fs->uid; ++ } else { ++ err = sqfs_id_get(fs, inode->base.uid, &id); ++ if (err) ++ return err; ++ st->st_uid = id; ++ } ++ if (fs->gid > 0) { ++ st->st_gid = fs->gid; ++ } else { ++ err = sqfs_id_get(fs, inode->base.guid, &id); ++ st->st_gid = id; ++ if (err) ++ return err; ++ } + + return SQFS_OK; + } diff --git a/81.patch b/81.patch new file mode 100644 index 0000000..58635a3 --- /dev/null +++ b/81.patch @@ -0,0 +1,29 @@ +From a804a17937f35b009787760034caed43dd4dcb0a Mon Sep 17 00:00:00 2001 +From: Florian Weimer +Date: Wed, 7 Dec 2022 15:20:48 +0100 +Subject: [PATCH] Fix fuse_mount function argument probing + +The test did not check the number of arguments with certain +fuse headers because they declare fuse_mount in , +not . If the compile supports implicit +function declarations, the test always succeeds (regardless +of parameter count). If it does not, it always fails. +--- + m4/squashfuse_fuse.m4 | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/m4/squashfuse_fuse.m4 b/m4/squashfuse_fuse.m4 +index e429168b..88c37a46 100644 +--- a/m4/squashfuse_fuse.m4 ++++ b/m4/squashfuse_fuse.m4 +@@ -260,7 +260,9 @@ AC_DEFUN([SQ_FUSE_API_VERSION],[ + AC_CACHE_CHECK([for two-argument fuse_unmount], + [sq_cv_decl_fuse_unmount_two_arg],[ + AC_LINK_IFELSE( +- [AC_LANG_PROGRAM([#include ], ++ [AC_LANG_PROGRAM([ ++ #include ++ #include ], + [fuse_unmount(0,0)])], + [sq_cv_decl_fuse_unmount_two_arg=yes], + [sq_cv_decl_fuse_unmount_two_arg=no]) diff --git a/apptainer-1.1.6.tar.gz b/apptainer-1.1.6.tar.gz new file mode 100644 index 0000000..63408df Binary files /dev/null and b/apptainer-1.1.6.tar.gz differ diff --git a/apptainer.spec b/apptainer.spec new file mode 100644 index 0000000..6829a72 --- /dev/null +++ b/apptainer.spec @@ -0,0 +1,436 @@ +# +# Copyright (c) Contributors to the Apptainer project, established as +# Apptainer a Series of LF Projects LLC. +# For website terms of use, trademark policy, privacy policy and other +# project policies see https://lfprojects.org/policies +# Copyright (c) 2017-2022, SyLabs, Inc. All rights reserved. +# Copyright (c) 2017, SingularityWare, LLC. All rights reserved. +# +# Copyright (c) 2015-2017, Gregory M. Kurtzer. All rights reserved. +# +# Copyright (c) 2016, The Regents of the University of California, through +# Lawrence Berkeley National Laboratory (subject to receipt of any required +# approvals from the U.S. Dept. of Energy). All rights reserved. +# +# This software is licensed under a customized 3-clause BSD license. Please +# consult LICENSE file distributed with the sources of this project regarding +# your rights to use or distribute this software. +# +# NOTICE. This Software was developed under funding from the U.S. Department of +# Energy and the U.S. Government consequently retains certain rights. As such, +# the U.S. Government has been granted for itself and others acting on its +# behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software +# to reproduce, distribute copies to the public, prepare derivative works, and +# perform publicly and display publicly, and to permit other to do so. +# +# + +# Disable debugsource packages; otherwise it ends up with an empty %%files +# file in debugsourcefiles.list on Fedora +%undefine _debugsource_packages + +# This can be slightly different than %%{version}. +# For example, it has dash instead of tilde for release candidates. +%global package_version 1.1.6 + +# Uncomment this to include a multithreaded version of squashfuse_ll +%global squashfuse_version 0.1.105 + +# The last singularity version number in EPEL/Fedora +%global last_singularity_version 3.8.7-3 + +Summary: Application and environment virtualization formerly known as Singularity +Name: apptainer +Version: 1.1.6 +Release: 1 +# See LICENSE.md for first party code (BSD-3-Clause and LBNL BSD) +# See LICENSE_THIRD_PARTY.md for incorporated code (ASL 2.0) +# See LICENSE_DEPENDENCIES.md for dependencies +# License identifiers taken from: https://fedoraproject.org/wiki/Licensing +License: BSD and LBNL BSD and ASL 2.0 +URL: https://apptainer.org +Source: https://github.com/%{name}/%{name}/releases/download/v%{package_version}/%{name}-%{package_version}.tar.gz + +%if "%{?squashfuse_version}" != "" +Source10: https://github.com/vasi/squashfuse/archive/%{squashfuse_version}/squashfuse-%{squashfuse_version}.tar.gz +Patch10: 70.patch +Patch11: 77.patch +Patch12: 81.patch +%endif + +# This Conflicts is in case someone tries to install the main apptainer +# package when an old singularity package is installed. An Obsoletes is on +# the apptainer-suid subpackage below. If an Obsoletes were here too, it +# would get different behavior with yum and dnf: a "yum install apptainer" +# on EL7 would install only apptainer but a "dnf install apptainer" on EL8 +# or greater would install both apptainer and apptainer-suid. With this +# Conflicts, both yum and dnf consistently install both apptainer and +# apptainer-suid when apptainer is requested while singularity is installed. +Conflicts: singularity <= %{last_singularity_version} + +# In the singularity 2.x series there was a singularity-runtime package +# that could have been installed independently, but starting in 3.x +# there was only one package +Obsoletes: singularity-runtime < 3.0 + +# Multiple packages contain /usr/bin/singularity and /usr/bin/run-singularity, +# which are necessary to run SIF images. Use a pivot provides/conflicts to +# avoid them all needing to conflict with each other. +Provides: sif-runtime +Conflicts: sif-runtime + +Provides: bundled(golang(github.com/AdamKorcz/go_fuzz_headers)) = v0.0.0_20210319161527_f761c2329661 +Provides: bundled(golang(github.com/Azure/go_ansiterm)) = v0.0.0_20210617225240_d185dfc1b5a1 +Provides: bundled(golang(github.com/BurntSushi/toml)) = v1.2.0 +Provides: bundled(golang(github.com/Microsoft/go_winio)) = v0.5.2 +Provides: bundled(golang(github.com/Microsoft/hcsshim)) = v0.9.4 +Provides: bundled(golang(github.com/Netflix/go_expect)) = v0.0.0_20220104043353_73e0943537d2 +Provides: bundled(golang(github.com/ProtonMail/go_crypto)) = v0.0.0_20220824120805_4b6e5c587895 +Provides: bundled(golang(github.com/VividCortex/ewma)) = v1.2.0 +Provides: bundled(golang(github.com/acarl005/stripansi)) = v0.0.0_20180116102854_5a71ef0e047d +Provides: bundled(golang(github.com/adigunhammedolalekan/registry_auth)) = v0.0.0_20200730122110_8cde180a3a60 +Provides: bundled(golang(github.com/alexflint/go_filemutex)) = v1.1.0 +Provides: bundled(golang(github.com/apex/log)) = v1.9.0 +Provides: bundled(golang(github.com/apptainer/container_key_client)) = v0.8.0 +Provides: bundled(golang(github.com/apptainer/container_library_client)) = v1.3.4 +Provides: bundled(golang(github.com/apptainer/sif/v2)) = v2.8.1 +Provides: bundled(golang(github.com/beorn7/perks)) = v1.0.1 +Provides: bundled(golang(github.com/blang/semver)) = v3.5.1+incompatible +Provides: bundled(golang(github.com/blang/semver/v4)) = v4.0.0 +Provides: bundled(golang(github.com/buger/jsonparser)) = v1.1.1 +Provides: bundled(golang(github.com/bugsnag/bugsnag_go)) = v1.5.1 +Provides: bundled(golang(github.com/bugsnag/panicwrap)) = v1.2.0 +Provides: bundled(golang(github.com/cenkalti/backoff/v4)) = v4.1.3 +Provides: bundled(golang(github.com/cespare/xxhash/v2)) = v2.1.2 +Provides: bundled(golang(github.com/cilium/ebpf)) = v0.7.0 +Provides: bundled(golang(github.com/cloudflare/circl)) = v1.1.0 +Provides: bundled(golang(github.com/containerd/cgroups)) = v1.0.3 +Provides: bundled(golang(github.com/containerd/containerd)) = v1.6.8 +Provides: bundled(golang(github.com/containernetworking/cni)) = v1.1.2 +Provides: bundled(golang(github.com/containernetworking/plugins)) = v1.1.1 +Provides: bundled(golang(github.com/containers/image/v5)) = v5.22.0 +Provides: bundled(golang(github.com/containers/libtrust)) = v0.0.0_20200511145503_9c3a6c22cd9a +Provides: bundled(golang(github.com/containers/ocicrypt)) = v1.1.5 +Provides: bundled(golang(github.com/containers/storage)) = v1.42.0 +Provides: bundled(golang(github.com/coreos/go_iptables)) = v0.6.0 +Provides: bundled(golang(github.com/coreos/go_systemd/v22)) = v22.3.2 +Provides: bundled(golang(github.com/cpuguy83/go_md2man/v2)) = v2.0.2 +Provides: bundled(golang(github.com/creack/pty)) = v1.1.18 +Provides: bundled(golang(github.com/cyphar/filepath_securejoin)) = v0.2.3 +Provides: bundled(golang(github.com/d2g/dhcp4)) = v0.0.0_20170904100407_a1d1b6c41b1c +Provides: bundled(golang(github.com/d2g/dhcp4client)) = v1.0.0 +Provides: bundled(golang(github.com/docker/cli)) = v20.10.17+incompatible +Provides: bundled(golang(github.com/docker/distribution)) = v2.8.1+incompatible +Provides: bundled(golang(github.com/docker/docker)) = v20.10.17+incompatible +Provides: bundled(golang(github.com/docker/docker_credential_helpers)) = v0.6.4 +Provides: bundled(golang(github.com/docker/go_connections)) = v0.4.0 +Provides: bundled(golang(github.com/docker/go_metrics)) = v0.0.1 +Provides: bundled(golang(github.com/docker/go_units)) = v0.4.0 +Provides: bundled(golang(github.com/docker/libtrust)) = v0.0.0_20160708172513_aabc10ec26b7 +Provides: bundled(golang(github.com/fatih/color)) = v1.13.0 +Provides: bundled(golang(github.com/felixge/httpsnoop)) = v1.0.1 +Provides: bundled(golang(github.com/fsnotify/fsnotify)) = v1.5.1 +Provides: bundled(golang(github.com/garyburd/redigo)) = v0.0.0_20150301180006_535138d7bcd7 +Provides: bundled(golang(github.com/ghodss/yaml)) = v1.0.0 +Provides: bundled(golang(github.com/go_log/log)) = v0.2.0 +Provides: bundled(golang(github.com/godbus/dbus/v5)) = v5.0.6 +Provides: bundled(golang(github.com/gofrs/uuid)) = v4.0.0+incompatible +Provides: bundled(golang(github.com/gogo/protobuf)) = v1.3.2 +Provides: bundled(golang(github.com/golang/groupcache)) = v0.0.0_20210331224755_41bb18bfe9da +Provides: bundled(golang(github.com/golang/protobuf)) = v1.5.2 +Provides: bundled(golang(github.com/google/go_cmp)) = v0.5.8 +Provides: bundled(golang(github.com/google/go_containerregistry)) = v0.10.0 +Provides: bundled(golang(github.com/google/uuid)) = v1.3.0 +Provides: bundled(golang(github.com/gorilla/handlers)) = v1.5.1 +Provides: bundled(golang(github.com/gorilla/mux)) = v1.8.0 +Provides: bundled(golang(github.com/gosimple/slug)) = v1.12.0 +Provides: bundled(golang(github.com/gosimple/unidecode)) = v1.0.1 +Provides: bundled(golang(github.com/hashicorp/errwrap)) = v1.1.0 +Provides: bundled(golang(github.com/hashicorp/go_multierror)) = v1.1.1 +Provides: bundled(golang(github.com/inconshreveable/mousetrap)) = v1.0.0 +Provides: bundled(golang(github.com/json_iterator/go)) = v1.1.12 +Provides: bundled(golang(github.com/kardianos/osext)) = v0.0.0_20190222173326_2bc1f35cddc0 +Provides: bundled(golang(github.com/klauspost/compress)) = v1.15.9 +Provides: bundled(golang(github.com/klauspost/pgzip)) = v1.2.5 +Provides: bundled(golang(github.com/letsencrypt/boulder)) = v0.0.0_20220331220046_b23ab962616e +Provides: bundled(golang(github.com/mattn/go_colorable)) = v0.1.12 +Provides: bundled(golang(github.com/mattn/go_isatty)) = v0.0.14 +Provides: bundled(golang(github.com/mattn/go_runewidth)) = v0.0.13 +Provides: bundled(golang(github.com/mattn/go_shellwords)) = v1.0.12 +Provides: bundled(golang(github.com/matttproud/golang_protobuf_extensions)) = v1.0.2_0.20181231171920_c182affec369 +Provides: bundled(golang(github.com/miekg/pkcs11)) = v1.1.1 +Provides: bundled(golang(github.com/moby/locker)) = v1.0.1 +Provides: bundled(golang(github.com/moby/sys/mount)) = v0.3.0 +Provides: bundled(golang(github.com/moby/sys/mountinfo)) = v0.6.2 +Provides: bundled(golang(github.com/moby/term)) = v0.0.0_20210610120745_9d4ed1856297 +Provides: bundled(golang(github.com/modern_go/concurrent)) = v0.0.0_20180306012644_bacd9c7ef1dd +Provides: bundled(golang(github.com/modern_go/reflect2)) = v1.0.2 +Provides: bundled(golang(github.com/morikuni/aec)) = v1.0.0 +Provides: bundled(golang(github.com/networkplumbing/go_nft)) = v0.2.0 +Provides: bundled(golang(github.com/opencontainers/go_digest)) = v1.0.0 +Provides: bundled(golang(github.com/opencontainers/image_spec)) = v1.0.3_0.20220114050600_8b9d41f48198 +Provides: bundled(golang(github.com/opencontainers/runc)) = v1.1.4 +Provides: bundled(golang(github.com/opencontainers/runtime_spec)) = v1.0.3_0.20210326190908_1c3f411f0417 +Provides: bundled(golang(github.com/opencontainers/runtime_tools)) = v0.9.1_0.20210326182921_59cdde06764b +Provides: bundled(golang(github.com/opencontainers/selinux)) = v1.10.1 +Provides: bundled(golang(github.com/opencontainers/umoci)) = v0.4.7 +Provides: bundled(golang(github.com/pelletier/go_toml)) = v1.9.5 +Provides: bundled(golang(github.com/pkg/errors)) = v0.9.1 +Provides: bundled(golang(github.com/proglottis/gpgme)) = v0.1.3 +Provides: bundled(golang(github.com/prometheus/client_golang)) = v1.12.1 +Provides: bundled(golang(github.com/prometheus/client_model)) = v0.2.0 +Provides: bundled(golang(github.com/prometheus/common)) = v0.32.1 +Provides: bundled(golang(github.com/prometheus/procfs)) = v0.7.3 +Provides: bundled(golang(github.com/rivo/uniseg)) = v0.2.0 +Provides: bundled(golang(github.com/rootless_containers/proto)) = v0.1.0 +Provides: bundled(golang(github.com/russross/blackfriday/v2)) = v2.1.0 +Provides: bundled(golang(github.com/safchain/ethtool)) = v0.0.0_20210803160452_9aa261dae9b1 +Provides: bundled(golang(github.com/seccomp/containers_golang)) = v0.6.0 +Provides: bundled(golang(github.com/seccomp/libseccomp_golang)) = v0.9.2_0.20220502022130_f33da4d89646 +Provides: bundled(golang(github.com/sergi/go_diff)) = v1.2.0 +Provides: bundled(golang(github.com/shopspring/decimal)) = v1.3.1 +Provides: bundled(golang(github.com/sigstore/sigstore)) = v1.3.1_0.20220629021053_b95fc0d626c1 +Provides: bundled(golang(github.com/sirupsen/logrus)) = v1.9.0 +Provides: bundled(golang(github.com/spf13/cobra)) = v1.5.0 +Provides: bundled(golang(github.com/spf13/pflag)) = v1.0.5 +Provides: bundled(golang(github.com/stefanberger/go_pkcs11uri)) = v0.0.0_20201008174630_78d3cae3a980 +Provides: bundled(golang(github.com/sylabs/json_resp)) = v0.8.1 +Provides: bundled(golang(github.com/syndtr/gocapability)) = v0.0.0_20200815063812_42c35b437635 +Provides: bundled(golang(github.com/theupdateframework/go_tuf)) = v0.3.1 +Provides: bundled(golang(github.com/titanous/rocacheck)) = v0.0.0_20171023193734_afe73141d399 +Provides: bundled(golang(github.com/ulikunitz/xz)) = v0.5.10 +Provides: bundled(golang(github.com/urfave/cli)) = v1.22.5 +Provides: bundled(golang(github.com/vbatts/go_mtree)) = v0.5.0 +Provides: bundled(golang(github.com/vbatts/tar_split)) = v0.11.2 +Provides: bundled(golang(github.com/vbauerster/mpb/v7)) = v7.4.2 +Provides: bundled(golang(github.com/vishvananda/netlink)) = v1.1.1_0.20210330154013_f5de75959ad5 +Provides: bundled(golang(github.com/vishvananda/netns)) = v0.0.0_20210104183010_2eb08e3e575f +Provides: bundled(golang(github.com/xeipuuv/gojsonpointer)) = v0.0.0_20190905194746_02993c407bfb +Provides: bundled(golang(github.com/xeipuuv/gojsonreference)) = v0.0.0_20180127040603_bd5ef7bd5415 +Provides: bundled(golang(github.com/xeipuuv/gojsonschema)) = v1.2.0 +Provides: bundled(golang(github.com/yvasiyarov/go_metrics)) = v0.0.0_20150112132944_c25f46c4b940 +Provides: bundled(golang(github.com/yvasiyarov/gorelic)) = v0.0.6 +Provides: bundled(golang(github.com/yvasiyarov/newrelic_platform_go)) = v0.0.0_20160601141957_9c099fbc30e9 +Provides: bundled(golang(go.etcd.io/bbolt)) = v1.3.6 +Provides: bundled(golang(go.mozilla.org/pkcs7)) = v0.0.0_20200128120323_432b2356ecb1 +Provides: bundled(golang(golang.org/x/crypto)) = v0.0.0_20220525230936_793ad666bf5e +Provides: bundled(golang(golang.org/x/net)) = v0.0.0_20220624214902_1bab6f366d9e +Provides: bundled(golang(golang.org/x/sync)) = v0.0.0_20220601150217_0de741cfad7f +Provides: bundled(golang(golang.org/x/sys)) = v0.0.0_20220715151400_c0bba94af5f8 +Provides: bundled(golang(golang.org/x/term)) = v0.0.0_20210927222741_03fcf44c2211 +Provides: bundled(golang(golang.org/x/text)) = v0.3.7 +Provides: bundled(golang(google.golang.org/genproto)) = v0.0.0_20220624142145_8cd45d7dbd1f +Provides: bundled(golang(google.golang.org/grpc)) = v1.47.0 +Provides: bundled(golang(google.golang.org/protobuf)) = v1.28.0 +Provides: bundled(golang(gopkg.in/square/go_jose.v2)) = v2.6.0 +Provides: bundled(golang(gopkg.in/yaml.v2)) = v2.4.0 +Provides: bundled(golang(gopkg.in/yaml.v3)) = v3.0.1 +Provides: bundled(golang(gotest.tools/v3)) = v3.3.0 +Provides: bundled(golang(mvdan.cc/sh/v3)) = v3.5.1 +Provides: bundled(golang(oras.land/oras_go)) = v1.2.0 + +%if "%{_target_vendor}" == "suse" +BuildRequires: binutils-gold +%endif +BuildRequires: golang +BuildRequires: git +BuildRequires: gcc +BuildRequires: make +BuildRequires: libseccomp-devel +%if "%{_target_vendor}" == "suse" +Requires: squashfs +%else +Requires: squashfs-tools +%endif +BuildRequires: cryptsetup +%if "%{?squashfuse_version}" != "" +BuildRequires: autoconf +BuildRequires: automake +BuildRequires: libtool +BuildRequires: pkgconfig +BuildRequires: fuse3-devel +BuildRequires: zlib-devel +%endif +# Requires: squashfuse +Requires: fakeroot +Requires: fuse-overlayfs +Requires: e2fsprogs +# Uncomment this for the epel build, but we don't want it for the Apptainer +# release build because there the same rpm is shared across OS versions +%if 0%{?el7} +Requires: fuse2fs +%endif + +%description +Apptainer provides functionality to make portable +containers that can be used across host environments. + +%package suid +Summary: Setuid component of Apptainer +Requires: %{name} = %{version}-%{release} +# The singularity package was renamed to apptainer. The Obsoletes is +# on this subpackage for greater compatibility after an update from the +# old singularity. +Obsoletes: singularity <= %{last_singularity_version} +# FESCo asked to have this form of Provides +Provides: alternative-for(singularity) + +%description suid +Provides the optional setuid-root portion of Apptainer. + +%prep +%if "%{?squashfuse_version}" != "" +# the default directory for other steps is where the %%prep section ends +# so do main package last +%setup -b 10 -n squashfuse-%{squashfuse_version} +%patch -P 10 -p1 +%patch -P 11 -p1 +%patch -P 12 -p1 +%setup -n %{name}-%{package_version} +%else +%autosetup -n %{name}-%{package_version} +%endif + +%build +%if "%{?squashfuse_version}" != "" +pushd ../squashfuse-%{squashfuse_version} +./autogen.sh +FLAGS=-std=c99 ./configure --enable-multithreading +%make_build squashfuse_ll +popd +%endif + +%if "%{?SOURCE1}" != "" +GOVERSION="$(echo %SOURCE1|sed 's,.*/,,;s/go//;s/\.src.*//')" +if ! ./mlocal/scripts/check-min-go-version go $GOVERSION; then + # build the go tool chain, the existing version is too old + pushd .. + tar -xf %SOURCE1 + cd go/src + ./make.bash + cd ../.. + export PATH=$PWD/go/bin:$PATH + popd +fi +%endif + +# Not all of these parameters currently have an effect, but they might be +# used someday. They are the same parameters as in the configure macro. +./mconfig %{?mconfig_opts} -V %{version}-%{release} --with-suid \ + --prefix=%{_prefix} \ + --exec-prefix=%{_exec_prefix} \ + --bindir=%{_bindir} \ + --sbindir=%{_sbindir} \ + --sysconfdir=%{_sysconfdir} \ + --datadir=%{_datadir} \ + --includedir=%{_includedir} \ + --libdir=%{_libdir} \ + --libexecdir=%{_libexecdir} \ + --localstatedir=%{_localstatedir} \ + --sharedstatedir=%{_sharedstatedir} \ + --mandir=%{_mandir} \ + --infodir=%{_infodir} + +%make_build -C builddir V= old_config= + +%install +%if "%{?SOURCE1}" != "" +export PATH=$PWD/go/bin:$PATH +%endif + +%make_install -C builddir V= +%if "%{?squashfuse_version}" != "" +install -m 755 ../squashfuse-%{squashfuse_version}/squashfuse_ll %{buildroot}%{_libexecdir}/%{name}/bin/squashfuse_ll +%endif + +%if 0%{?el7} +# Check for fuse2fs only as a pre-install so that an rpm built on el7 can +# be used on el8 & el9. Only el7 has a fuse2fs package, the others have +# the fuse2fs program in the e2fsprogs package. +%pre +if [ ! -f /usr/bin/fuse2fs ] && [ ! -f /usr/sbin/fuse2fs ]; then + echo "fuse2fs not found, please yum install /usr/*bin/fuse2fs from epel" >&2 + exit 1 +fi +%endif + +%post +# $1 in %%posttrans cannot distinguish between fresh installs and upgrades, +# so check it here and create a file to pass the knowledge to that step +if [ "$1" -eq 1 ] && [ -d %{_sysconfdir}/singularity ]; then + touch %{_sysconfdir}/%{name}/.singularityupgrade +fi + +%posttrans +# clean out empty directories under /etc/singularity +rmdir %{_sysconfdir}/singularity/* %{_sysconfdir}/singularity 2>/dev/null || true +if [ -f %{_sysconfdir}/%{name}/.singularityupgrade ]; then + pushd %{_sysconfdir}/%{name} >/dev/null + rm .singularityupgrade + # This is the first install of apptainer after removal of singularity. + # Import any singularity configurations that remain, which were left + # because they were non-default. + find %{_sysconfdir}/singularity ! -type d 2>/dev/null|while read F; do + B="$(echo $F|sed 's,%{_sysconfdir}/singularity/,,;s/\.rpmsave//')" + if [ "$B" == singularity.conf ]; then + echo "info: renaming $PWD/%{name}.conf to $PWD/%{name}.conf.rpmorig" >&2 + mv %{name}.conf %{name}.conf.rpmorig + echo "info: converting configuration from $F into $PWD/%{name}.conf" >&2 + %{_bindir}/%{name} confgen $F %{name}.conf + elif [ "$B" == remote.yaml ]; then + echo "info: renaming $PWD/$B to $PWD/$B.rpmorig" >&2 + mv $B $B.rpmorig + echo "info: merging $F into $PWD/$B" >&2 + ( + sed -n '1p' $F + sed -n '2,$p' $B.rpmorig + sed -n '3,$p' $F + ) >$B + else + if [ -f "$B" ]; then + echo "info: renaming $PWD/$B to $PWD/$B.rpmorig" >&2 + mv $B $B.rpmorig + fi + echo "info: copying $F into $PWD/$B" >&2 + cp $F $B + fi + done + popd >/dev/null +fi + +# Define `%%license` tag if not already defined. +# This is needed for EL 7 compatibility. +%{!?_licensedir:%global license %doc} + +%files +%{_bindir}/%{name} +%{_bindir}/singularity +%{_bindir}/run-singularity +%dir %{_libexecdir}/%{name} +%dir %{_libexecdir}/%{name}/bin +%{_libexecdir}/%{name}/bin/starter +%if "%{?squashfuse_version}" != "" +%{_libexecdir}/%{name}/bin/squashfuse_ll +%endif +%{_libexecdir}/%{name}/cni +%{_libexecdir}/%{name}/lib +%dir %{_sysconfdir}/%{name} +%config(noreplace) %{_sysconfdir}/%{name}/* +%{_datadir}/bash-completion/completions/* +%dir %{_localstatedir}/%{name} +%dir %{_localstatedir}/%{name}/mnt +%dir %{_localstatedir}/%{name}/mnt/session +%{_mandir}/man1/%{name}* +%{_mandir}/man1/singularity* +%license LICENSE.md +%license LICENSE_THIRD_PARTY.md +%license LICENSE_DEPENDENCIES.md +%doc README.md +%doc CHANGELOG.md + +%files suid +%attr(4755, root, root) %{_libexecdir}/%{name}/bin/starter-suid + +%changelog +* Tue Feb 21 2023 Yikun - 1.1.6-1 +- Init package diff --git a/squashfuse-0.1.105.tar.gz b/squashfuse-0.1.105.tar.gz new file mode 100644 index 0000000..d3ab2d6 Binary files /dev/null and b/squashfuse-0.1.105.tar.gz differ