Compare commits

...
234 Commits
Author SHA1 Message Date
Antonio SJ Musumeci 8e87bcbff2 docs: Add Unraid migration guidance for mergerfs
- Add a top-level Migration page covering both migration directions.
- Explain filesystem reuse, parity constraints, partition requirements,
  and unlocking encrypted Unraid disks.
- Describe intact ZFS pool imports and subdirectory branch reuse,
  distinguishing them from cases requiring a data copy.
- Add navigation and link the guide from the Unraid comparison.
2026-10-03 16:52:25 -05:00
Antonio SJ Musumeci fcd7527c9d docs: document mergerfs tradeoffs and web UI management
* Add: a comprehensive downsides FAQ with mitigations and practical
  priorities.
* Document: mergerfs-webui and link it from setup and configuration
  guides.
* Replace: obsolete mergerfs.fsck references with fsck.mergerfs
  guidance.
2026-10-01 20:14:52 -04:00
Antonio SJ Musumeci 83d1630f55 docs: rework and expand project comparisons 2026-09-22 14:50:47 -05:00
Antonio SJ Musumeci cfb925e5ae docs: clarify branch resolution and mount ordering 2026-09-21 13:46:38 -05:00
Antonio SJ Musumeci 515b0549ef docs/performance: explain mergerfs IO performance vs RAID
Describe why mergerfs does not aggregate branch performance like
RAID/ZFS (whose aggregate throughput is also capped by shared
interconnects) and instead provides independent per-branch
performance: best case A+B+C across concurrent streams, worst case
single-stream limited to one branch's speed.
2026-09-05 16:00:43 -04:00
natalefalco 8ea47df13e Include Docker Compose setup for mergerfs
Added Docker Compose configuration for running mergerfs.
2026-07-31 08:13:59 -05:00
shyney7 42ab831d97 docs/setup: link install :z example to SELinux FAQ 2026-05-17 10:14:21 -05:00
shyney7 89b4046d11 docs/faq: add SELinux relabeling entry 2026-05-17 10:14:21 -05:00
Antonio SJ Musumeci 7ec6f8e2f2 Manage cloned /dev/fuse fds in sessions
- Store cloned read-worker fds on fuse_session and close them there.
- Set up clone fallback through session helpers used by the loop.
- Carry per-request fds through receive, process, and reply paths.

Tests show significant performance increases in highly concurrent
workloads on a high core count system. Upto 15% better on reads and
27% on writes.
2026-05-14 20:13:07 -05:00
Antonio SJ Musumeci 5f7fa17ad8 Inline ioprio disabled fast path
- Expose the ioprio enabled flag as an atomic for relaxed inline checks
- Inline SetFrom construction so disabled read/write paths avoid a function call
- Move priority application into a slow helper used only when ioprio is enabled
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 30d59d4458 Use shared_mutex for fuse log config
- Guard fuse_cfg_t log file and filepath shared pointers with a shared mutex
- Replace atomic shared pointer loads and stores with shared and unique locks
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 02dc36080c Use shared_mutex for ReadDir state
- Replace atomic shared pointer loads/stores with shared/unique locking.
- Store the configured readdir mode directly as std::string.
- Copy the active ReadDir implementation under lock before invoking it.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 8734847968 Guard branch snapshots with shared_mutex
Important changes:
- Replace atomic shared_ptr operations with shared_mutex-protected snapshots.
- Retry copy-on-write branch updates when another writer publishes first.
- Keep long-running parsing and read-only filesystem checks outside branch locks.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 608cd00764 Use atomic open file reference counts
- Use relaxed increment under the map bucket lock when duplicating file handles.
- Use acq_rel decrement when releasing and checking for final references.
- Load moved ref_count with relaxed ordering during map insertion.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci b13727abde Document readdir merge mutex scope
- Explain that the mutex is per readdir call.
- Document that only dirent deduplication and list append are serialized.
- Clarify that per-branch opendir and getdents64 work runs outside the critical section.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 231aac15cd Use shared write locks until ENOSPC relocation
- Replace the per-file Mutex with std::shared_mutex for shared write access.
- Run direct and cached writes under a shared lock while the fd is stable.
- Retry failed writes under a unique lock before move-on-ENOSPC relocation.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 8b17d5f35e Synchronize node updates with path release
- Add helpers that release path locks and update node state under f.lock.
- Update getattr and setattr stat-cache invalidation while preserving ESTALE handling.
- Rework create, open, tmpfile, and set_path_info to mutate lookup/open state under the shared lock.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 8608b2be46 Clarify releasedir readdir synchronization barrier
- Document the lock/unlock pair as an acquire/release barrier.
- Explain that releasedir waits for in-flight readdir calls before destroy/free.
- Warn against removing the synchronization despite it not being a critical section.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 1e0bd54989 Move statvfs cache to use shared mutex
Its read heavy and micro benchmarks show no regression on the low end
and 30%+ throughput improvements on the highend.
2026-05-14 00:22:06 -04:00
Antonio SJ Musumeci 56285fc6e5 Pass annobin override to RPM toolset builds
Important changes:
- Add Makefile RPMBUILD and RPMBUILD_FLAGS overrides for the rpm target.
- Detect /opt/rh GCC toolsets before RPM packaging.
- Pass --undefine _annotated_build to avoid Rocky 8 annobin plugin failures.
2026-05-13 17:01:41 -04:00
Antonio SJ Musumeci dfe053a0ae Require C++20-capable build toolchains
Important changes:
- Switch mergerfs and vendored libfuse Makefiles to -std=c++20.
- Select the latest available GCC/G++ or toolset in container builds.
- Verify compiler C++20 support during package installation.
- Fix dynamic SysLog formatting for fmt C++20 checks.
2026-05-13 13:55:43 -05:00
Antonio SJ Musumeci 12ac368e8b Preserve CPPFLAGS in release builds
- Append -DNDEBUG to existing CPPFLAGS in the release target.
- Keep caller-provided build flags for Debian packaging.
2026-05-09 19:28:56 -05:00
Antonio SJ Musumeci c8f32f9f25 update build system and dependency configuration 2026-05-09 18:55:23 -05:00
Antonio SJ Musumeci ce3737ff33 Add new OS build files 2026-05-07 23:35:14 -04:00
Antonio SJ Musumeci 84e4cdeb7e make all integration tests self-contained with ephemeral mounts
- Add mergerfs_mount() context manager in posix_parity.py that creates
  temporary branch dirs in tests/.test_tmp/, mounts an ephemeral mergerfs
  instance, and cleans up after use.
- Add find_mergerfs(), find_fusermount(), mount_mergerfs(), unmount_mergerfs()
  helpers.
- Update run-tests to invoke each TEST_* script without a mountpoint arg.
- Update README.md to document the new `python3 tests/run-tests` invocation.
- Fix 44 tests to use mergerfs_mount() instead of expecting a pre-mounted path.
- Add tempfile import to 10 tests that were missing it.
- Fix TEST_mount_lifecycle to use mergerfs_mount() context manager.
- Fix TEST_posix_tmpfile missing join import.
- Fix TEST_cfg_xattr_modes error message framing (no "(known bug)").
- Fix TEST_posix_copy_file_range: loop-copy full size, compare st_size and
  SHA256 hash; add NativeTmp for space management; route native temp to
  tests/.test_tmp/ to avoid ENOSPC on /tmp.
- Fix TEST_posix_getattr_fgetattr: st_cmp skips st_size for dirs; add
  compare_ino param for safe inode comparison when inodecalc=passthrough.
- Fix TEST_unlink_rename, TEST_use_* tests: namespace cleanup, consistent
  variable naming, None check for cmpfunc.
- Fix TEST_readlink_semantics: remove type annotations to support older
  Python; wrap in mergerfs_mount() try/except.
- Add flush=True to run-tests print so test names show immediately.
2026-05-07 21:47:40 -04:00
Antonio SJ Musumeci 6d6d73c7b3 Use unmount() on FreeBSD for ENOTCONN umount 2026-05-07 17:49:24 -04:00
Antonio SJ Musumeci bea31543a0 update fuse_lib_tmpfile() node management to match fuse_lib_create() 2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci 99835a0178 Document advisory locking limitation and update deprecated options
- Add documentation explaining that advisory file locking is not supported

- Add remember-nodes to deprecated options mapping

- Add comment explaining stat_crc32b fingerprint invalidation behavior

- Fix heading level for Host vs Container identity section
2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci 342a1efcfb Add QEMU binfmt setup and fix static installer
- Fix typo in static.installer: /pkg/ -> /pkgs/

- Add missing mkdir -p /build before makeself

- Mount pkgs directory via podman build -v so static.installer can access

  previously built static tarballs without relying on bind mounts relative

  to the build context

- Register qemu-user-static binfmt handlers for cross-compilation

- Make --git-repo optional when using --setup
2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci c587a127fb add missing <new> include for std::nothrow in fuse_session.cpp 2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci f1034a3ad6 Remove timed node removal 2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci 8117998205 Remove unused lock operation plumbing 2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci cc780bc521 libfuse: remove per-node lock bookkeeping 2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci 14b7c8e607 Use stat fingerprint as cache validity sentinel
- Reserve a zero stat CRC to represent an invalid cache fingerprint.

- Include device, size, inode, mtime, and ctime data in the fingerprint.

- Recompute the fingerprint during auto-cache open before keeping cache.
2026-05-06 20:49:46 -04:00
Antonio SJ Musumeci 3ff9794fff libfuse: fix use-after-free in fuse_lib_ioctl 2026-04-30 12:51:12 -05:00
Antonio SJ Musumeci f71e27644e vendor moodycamel queue and upgrade to 1.0.5 2026-04-30 08:43:42 -04:00
Antonio SJ Musumeci 943f91ff98 Strengthen mergerfs integration tests
- Make the test runner fail on failed tests and report explicit skips
- Strengthen assertions for O_DIRECT, LUP policy, EXDEV fallback, and xattrs
- Fix cleanup and file descriptor handling across test scripts
2026-04-29 20:25:20 -04:00
Antonio SJ Musumeci ef1e01b5a5 Add unit and integration tests for recently changed code
Unit tests (29 new in tests/tests.cpp):
- str::eq() and str::startswith() char* nullptr safety
- str::from() u64/s64/int suffix parsing (K/M/G/T, overflow, invalid)
- num::humanize() byte formatting (0, 512, 1K, 1M, 1G, 1T, non-exact)
- RmdirErr priority error accumulation (ENOTEMPTY/EEXIST override rules)
- HashSet inline-to-heap growth, empty string, and large insertion
- fs::inode algorithm switching (valid/invalid names, passthrough raw ino,
  distinct inodes across algorithms)
- RND::rand64() bounds (range, max, shrink_to_rand_elem)
- Branches::Impl copy and move assignment re-links default_minfreespace

Integration tests (5 new in tests/):
- TEST_policy_lup: LUP policy create and search
- TEST_rmdir_enotempty_priority: rmdir returns ENOTEMPTY for non-empty dirs
- TEST_open_after_unlink: read/write via fd after unlink
- TEST_api_xattr: mergerfs xattr API (fullpath, relpath, basepath, allpaths)
- TEST_movefile_enospc: moveonenospc configuration round-trip

Also: remove xattr nosys checks from TEST_cfg_xattr_modes, reformat
integration test scripts to use temp_dir/cleanup_dir helpers, and
minor parameter alignment changes in fuse_getxattr, fuse_init,
fuse_rename.
2026-04-29 17:41:00 -04:00
Antonio SJ Musumeci 50c0627b2b Better handle make sanitize option 2026-04-29 12:24:10 -04:00
Antonio SJ Musumeci 660f556ac6 Don't return success on ENOTEMPTY rmdir errors 2026-04-29 12:23:49 -04:00
Antonio SJ Musumeci 997b036064 Reject invalid policies 2026-04-29 00:34:00 -04:00
Antonio SJ Musumeci 9308a3729c When file unlinked check open_files map
Inadvertantly removed. Didn't realize that the kernel would not always
send file handle when unlinked.
2026-04-28 16:04:26 -04:00
Antonio SJ Musumeci 7616d8b1ac Rework readlink to not depend on cstr nul termination 2026-04-26 01:37:08 -04:00
Antonio SJ Musumeci a86092b3c2 Don't push docs for RC tags 2026-04-23 20:46:55 -05:00
Antonio SJ Musumeci e613d9415f Better same file check for fsck 2026-04-22 22:28:56 -05:00
Antonio SJ Musumeci 1a8eb326af Tweak docs on NFS and identity management 2026-04-21 13:20:07 -05:00
Antonio SJ Musumeci 9fb1642e93 Fix docs regarding "noforget" 2026-04-20 23:25:47 -05:00
Antonio SJ Musumeci 8be86ee611 Remove mkdocs build github workflow 2026-04-19 23:48:13 -05:00
Antonio S. Musumeci da68cf3a85 Formatting: normalize brace style and parameter alignment
No functional change: libfuse/helper.cpp (fuse_daemonize,
fuse_helper_opt_proc, fuse_parse_cmdline) and mergerfs
fuse_init.cpp (_want helpers) reformatted to match the project's
GNU-style brace placement and aligned parameter lists.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci ec40866091 libfuse: small safety and portability fixes across opt/cpu/fusermount/maintenance_thread
- fuse_opt: reformat process_opt_param to project style; return the
  rv from sscanf through a named local so the error branch prints
  the failing option cleanly.

- cpu.cpp: when parsing /sys/devices/system/cpu/*/topology/core_id,
  bail out cleanly on stream read failure rather than recording an
  uninitialized core_id.

- fusermount: free(copy) on the success path of parsing the mount
  option (previously leaked on every mount), and replace the fixed-
  buffer sprintf into procfile with snprintf bounded by the buffer
  size.

- maintenance_thread: widen the iteration count from int to u64
  so a long-lived daemon doesn't wrap after ~68 years of ticks
  (real effect: silence signed-overflow warnings under UBSan).
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci bd0714272e libfuse: thread_pool/objpool/fuse_loop - safer memory and iteration
- ThreadPool::destroy used to iterate _threads directly while another
  thread could be adjusting the same vector. Snapshot the vector
  under the mutex and iterate the local copy for pthread_cancel /
  pthread_join.

- ObjPool::construct now returns NULL when the pooled allocator
  returns NULL instead of invoking placement-new on a null
  pointer. In destroy, compute to_node(obj) before running the
  destructor - the destructor may scrub bytes the to_node helper
  reads.

- In the async/sync workers in fuse_loop, msgbuf_alloc can return
  NULL under memory pressure. Fail the worker cleanly with a
  diagnostic rather than dereferencing. Also introduce a small
  _nproc() helper that treats hardware_concurrency()==0 as 1, so
  the downstream min/max arithmetic doesn't produce a zero thread
  count.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 27acd9fcc7 libfuse: use atomic fuse_session pointer in signal handler
The signal handler reads the global session pointer from an async-
signal context while another thread installs/removes the session
via set/remove_signal_handlers. Use std::atomic<fuse_session*> with
acquire/release ordering so the handler either sees NULL or a fully
published session, never a torn pointer.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 4516685ca7 libfuse: harden fuse.cpp against NULL nodes; use lock guards; check allocations
Collapse get_node/get_node_nocheck into a single function that
returns NULL when the node is absent rather than abort()ing.
Callers that previously assumed non-NULL now check the return value
and either reply ESTALE/ENOMEM or fall through gracefully; a few
places where the node is load-bearing (e.g. unhash during remove)
use fatal::abort with a descriptive message.

Switch mutex_lock / mutex_unlock pairs to mutex_lockguard so early
returns on error paths no longer leak the lock, and add NULL-safety
around fuse_dh in readdir/readdir_plus, fuse_dirents_init error
returns, the getattr path when both name and fh are empty, and the
msgbuf allocation in fuse_lib_read.

Pull the new xstrdup/xmalloc wrappers from xalloc.hpp for the root
node name so the common-case OOM path terminates with a diagnostic
instead of a silent corrupted node.

Also guard fuse_session_destroy against a NULL session - called
from error-recovery paths.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 0bdbaf1d6c libfuse: remove userdata from init/destroy APIs; drop unused fuse_reply_ioctl_iov
The low-level init/destroy callbacks were passed a userdata pointer
that mergerfs never consumed - the caller threaded it through
fuse_lowlevel_new only to hand it back unchanged. Remove the
parameter from the ops struct, from fuse_lowlevel_new(_common),
and from the internal fuse_ll struct; update fuse_lib_init and
fuse_lib_destroy accordingly.

Also drop fuse_reply_ioctl_iov: no caller in-tree invokes it and
the public API is defined by this vendored libfuse fork.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci e67b7d0825 log: default debug log path to empty; skip set when empty
libfuse previously initialized its debug log filepath to
"/dev/stderr", forcing the config layer to reopen stderr on every
load whether or not the user asked for it. Default the libfuse log
path to "" and have the mergerfs config layer only forward the
request to libfuse when the user supplied an explicit path.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 981d5473ee str: null-safe same() and startswith()
Both helpers previously called strcmp/pointer-dereference directly
on their arguments, so a stray NULL (e.g. an unset env string or an
absent xattr) crashed the process. Guard both helpers: same()
returns true only if both pointers are equal, startswith() returns
false if either side is NULL.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 364f8c4407 fs_movefile_and_open: -ENOSPC when no destination branch available
The create policy can legitimately return success with an empty
destination list (e.g. every candidate rejected). Previously we
then dereferenced dst_branch[0] and faulted. Return -ENOSPC so the
original write that triggered the move propagates a sensible errno
to userspace.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci ef9c7ad5df fuse_statfs: scale with long double to avoid integer overflow
The branch-aggregation code computed
  blocks * f_frsize / min_frsize
as u64 arithmetic. With large filesystems the intermediate product
blocks * f_frsize overflows 64 bits long before the division can
bring the result back into range, producing garbage bavail/bfree
values reported to userspace.

Compute the scale factor once in long double and multiply each
count by it, so the only narrowing happens in the final cast back
to fsblkcnt_t.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 4288a60c5d fuse_readlink: always null-terminate buffer
Old code only null-terminated when the returned/copied length was
strictly less than the caller-supplied bufsize_, leaving the buffer
unterminated on an exact-fill. Since buf_ is consumed as a C string,
reserve one byte for the terminator up front and unconditionally
terminate.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci cf38a37176 fuse_readdir: atomic initialized flag; bound error string; check msgbuf allocation
- ReadDir::_initialized is set by the first thread through the
  lazy-init path and read by every subsequent caller; make it
  std::atomic<bool> so the read/write pair has a happens-before
  relationship.

- The 'no valid mergerfs branch found' error directory entry used
  strcpy into a fixed-size dirent buffer. The message happens to
  fit today but the buffer is only NAME_MAX wide; switch to a
  length-bounded strncpy driven by the constexpr string size.

- The getdents implementations for seq/cor/cosr allocated a msgbuf
  without checking for allocation failure and then dereferenced it.
  Return -ENOMEM when the allocation fails.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci f5d9a60b9d fuse_getattr/statx: populate timeouts in fake_root and controlfile replies
The fake-root and control-file fast paths returned a synthetic stat
without touching the timeout struct. Callers that took those paths
ended up sending whatever the timeout struct happened to hold,
which is uninitialized memory for the control-file path and
whatever a prior call left there for the fake-root path. Kernel
behavior is undefined when the timeout is garbage.

Pass timeout_ into both helpers and set entry/attr from the
configured cache values.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 0c17492c78 FUSE: pass Branches::Ptr by value with empty-branches guards
Previously most FUSE ops took a reference to the live cfg.branches
object. With atomic_load now producing a shared_ptr snapshot, pass
it by value: the op works against a single stable view of the
configuration for its whole lifetime, immune to concurrent
reconfiguration.

Also bail out with -ENOENT when the policy returns an empty branch
set rather than dereferencing branches[0]. This was reachable if a
policy rejected every branch (e.g. everything readonly while
looking for a writable target).

Covers access, chmod, chown, create, getattr, getxattr, ioctl,
link, listxattr, mkdir, mknod, open, readdir (cor/cosr),
readlink, removexattr, rename, rmdir, setxattr, statfs, statx,
symlink, truncate, unlink, utimens.

While here, in _getattr only dereference st_->st_mode when lstat
succeeded (it is otherwise uninitialized).
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 98648ae44b FUSE: require valid file/dir handle; drop open_files fallback
Several FUSE ops fell back to looking up a FileInfo via
state.open_files when the kernel-provided fh was zero, returning
-ENOENT if the lookup failed. This path papered over handle-tracking
bugs and added cost on every call. Use the handle directly and
return -EBADF when it is invalid - the kernel should always hand us
a valid fh for an open resource.

Also simplify FileInfo::from_fh / DirInfo::from_fh and their to_fh
helpers: callers now handle the null-pointer case explicitly so the
ASSERT_NOT_NULL checks are redundant.

Covers fallocate, fchmod, fchown, fgetattr, flock, ftruncate,
futimens, read, release, releasedir, write, statx, ioctl (file +
dir), readdir (sequential, concurrent-on-read,
concurrent-on-search), and the passthrough fast paths in
create/open.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 0dcca0f1a3 branches: re-link default_minfreespace pointer on copy/move; atomically publish branch mode changes
Branches using the default minfreespace held a reference to their
owning Impl's _default_minfreespace. After a copy or move the
pointer still referenced the source Impl, which could become
invalid or diverge after further configuration changes. Switch to
a pointer and re-link it to the destination Impl on copy/move.

While here, convert set_branch_mode to do an atomic_store of a
newly constructed Impl rather than mutating the shared pointer
target in place. Readers holding an atomic_load snapshot are no
longer affected by a concurrent mode change.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci 103cd358f2 state: atomic ref_count in OpenFile
ref_count is read and written from multiple FUSE worker threads
during open/release. Make it std::atomic<int> and provide a move
constructor so OpenFile remains movable when the underlying atomic
is non-movable.
2026-04-18 13:47:23 -05:00
Antonio S. Musumeci aa46001923 Remove unused rwlock.hpp header and includes
rwlock.hpp was left over after migrating to atomic shared pointers
for branches configuration. No code references ReadGuard/WriteGuard
any longer, so drop the header and its stale includes.
2026-04-18 13:47:23 -05:00
Antonio SJ Musumeci ff48ff6047 Handle/ignore SIGHUP 2026-04-17 11:14:05 -05:00
Antonio SJ Musumeci d5a2187713 Fix make race condition 2026-04-16 18:20:00 -05:00
Antonio S. Musumeci 351adc2b20 Add tests for HashSet, rapidhash withSeed consistency, and ReaddirCalc parity with calc 2026-04-12 23:44:23 -05:00
Antonio S. Musumeci a1e681f36b Use ReaddirCalc in all readdir implementations
* avoids per-entry path construction
2026-04-12 23:44:23 -05:00
Antonio S. Musumeci e3a467de80 Refactor fs_inode
* add Algo enum
* ReaddirCalc class
* string_view params
* fs::path overloads
2026-04-12 23:44:23 -05:00
Antonio S. Musumeci cdadda5eb9 Replace khash with custom hash set 2026-04-12 23:44:23 -05:00
Antonio SJ Musumeci 4c9a9d7e15 Upgrade rapidhash 2026-04-12 23:44:23 -05:00
Antonio SJ Musumeci 425e30b50f Use nanosleep rather than yield and return EINTR 2026-04-11 11:30:57 -05:00
Antonio SJ Musumeci 3905355522 Detect OS for release-sample make target 2026-04-08 20:48:02 -05:00
Antonio SJ Musumeci 9e2e1afafa Update docs on limiting drive spinup 2026-04-06 14:01:20 -05:00
Antonio SJ Musumeci 6d218fa87f Misc updates and fixes 2026-04-04 02:46:24 -05:00
Antonio SJ Musumeci 3b10406f95 Add more details to project comparisons 2026-04-04 02:17:06 -05:00
Antonio SJ Musumeci f2c8e5d9eb Add FUSE request_timeout
Not yet hooked up to config parser to set as need to evaluate usage.
2026-04-01 23:20:05 -05:00
Antonio SJ Musumeci b581c5d5e7 Add support for FUSE_COPY_FILE_RANGE_64 2026-04-01 23:20:05 -05:00
Antonio SJ Musumeci 1ebc8cfa5f Enable statx with file handle 2026-03-31 19:53:17 -04:00
Antonio SJ Musumeci acfa797503 Misc formatting changes 2026-03-31 19:53:17 -04:00
Antonio SJ Musumeci 7fc20457ca Rework open file resource management 2026-03-31 19:53:17 -04:00
Antonio SJ Musumeci ecee84b323 Improve proj comparisons + new SnapRAID details 2026-03-31 19:51:28 -04:00
Antonio SJ Musumeci b8776d205d Update docs; add nofs, policyfs and greyhole 2026-03-27 22:54:39 -05:00
Antonio SJ Musumeci 06433a0548 Fallback to fusermount3 where available
Also general cleanup of mount_generic.
2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci f4b602340b Add more integration tests 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci a78aef36e0 Additional check for ENOTCONN on mount 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 0d05523dfd Move maintainence thread func take u64 rather than int
signed rollover is UB
2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 0cfab60f37 replace raw C-strings with std::string in converted libfuse files
util/mount.mergerfs.cpp:
- Remove xstrdup/xrealloc wrappers entirely
- Convert type, source, options, command, setuid to std::string
- Replace add_arg() pointer-rewriting with shell_quote() returning std::string
- Replace add_option() with simple std::string += concatenation
- Fix const_cast UB on string literal assignment

lib/helper.cpp:
- Convert add_default_subtype local subtype_opt: malloc+snprintf+free -> std::string construction

lib/mount_generic.h:
- Convert fuse_mount_sys locals source/type: malloc+strcpy+strcat -> std::string
- Remove 4 unnecessary free() calls (RAII cleanup)
- .c_str() at mount() and fuse_mnt_add_mount() boundaries

lib/mount_util.h:
- Rewrite fuse_mnt_resolve_path internals using std::string: rfind/substr
  instead of manual pointer arithmetic, strrchr, strlen chains
2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 21966ade97 Convert vendored/libfuse C sources to C++17
Convert all C source files in vendored/libfuse/ to C++17:
- lib/crc32b.c    -> crc32b.cpp  (const void* cast)
- lib/fuse_opt.c -> fuse_opt.cpp (designated init, void* arithmetic)
- lib/fuse_signals.c -> fuse_signals.cpp
- lib/helper.c   -> helper.cpp  (void* data cast)
- lib/mount.c    -> mount.cpp  (thin wrapper)
- lib/mount_generic.c -> mount_generic.h (static inline, #pragma once)
- lib/mount_bsd.c -> mount_bsd.h    (static inline, #pragma once)
- lib/mount_util.c -> mount_util.h   (static inline, #pragma once)
- util/fusermount.c -> fusermount.cpp
- util/mount.mergerfs.c -> mount.mergerfs.cpp

Key changes:
- Replace C99 designated initializer in fuse_opt_parse with field-by-field init
- Fix void* pointer arithmetic via char* base + offset pattern
- All C-style (T*)voidptr casts replaced with static_cast/reinterpret_cast
- Remove redundant #define _GNU_SOURCE (already in FUSE_FLAGS)
- Wrap __clone2 declaration in extern "C" for ia64 path
- Update Makefile: empty SRC_C, build utils with $(CXX) instead of $(CC)
- Remove stale .c files now replaced by .cpp/.h counterparts

All binaries build cleanly: mergerfs, mergerfs-fusermount, mount.mergerfs
2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 96eb51eecb Move fuse_session to C++ and make exit flag atomic 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 881147daa2 Fix null ptr handling issue in ioctl processing 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 0f1a8e9b69 Fix error condition in fs_copyfile 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 08f9cc6932 Fix readlink implementation, add more tests 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 3099b15db1 More aggressive error handing with pagesize queries 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 07b1e6bdba Auto-commit fuse_msgbuf.cpp 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 7763c7384a Use fatal::abort() rather than assert in places 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 61365d226d Move scope_guard to vendored/ 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci f33e78bc24 Cleanup procfs and abort on critical issues 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 7f52623536 Cleanup branches and add more unit tests 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci a30a4df9c0 Cleanup config and add tests 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 1e0d5aa91d fs_wait_for_mount cleanup 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci d09fadbc28 fs_inode cleanup 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 21ce889449 Fixup listxattr error handling 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 9d1a4696e9 Small improvements to fs::statvfs_cache 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci ed0cbcf064 Cleanup fuse_readlink 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 076e359891 Improve thread pool 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci bf98d3f3a6 Fix possible error handling issue with errno 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci c45c5ce920 Make cfgfile depth calculation thread friendly 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci e0a892f0d0 Improve error handling when querying pagesize 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 164b7f73c9 Misc fixes and improvements to thread pool 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 3a45be4cd8 Rework string utils, more test cases 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 107a3acf95 Better error handling in pagesize config type 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci a5eafb8972 Check string is not empty before resize in Config::keys 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 8199cc8a8b Fix error handling in fuse chmod, chown, setxattr, removexattr 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci e5d897f3dc Ensure zero initialized timeouts structs 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci c0af09f7d8 Fix incorrect type in fs_read and write 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 1b1e14ca29 Remove dead branch in fuse_reply_data 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 82b89aa099 Change session->exited to sig_atomic_t 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci aa91e1b2ba Initialize stat buffer and add stat call in fuse_mount_sys 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 82e81afcdc Add NULL check for cmsg in receive_fd to prevent segfault 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 1a5c39f813 Fix buffer overflow in readlink by reserving space for null terminator 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci fce1803d42 Use RND class for shrink_to_rand_elem and add assertions 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 5a9a17afcb fix objpool: add size tracking, improve allocation matching; fix msgbuf: clear pool on size change 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 8648de6bb6 fix policy_eppfrd.cpp: return 0 when branchinfo found, -ENOENT only when empty 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci f227a63e15 fix str.cpp: use string_view::npos, fix erase() calls, remove extra whitespace 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci aa47b74dea Remove iterator methods from Branches class 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 206ad1dd53 Fix futimens bugs: stat pointer, variable names, and negation 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 80aead7085 Add null pointer checks for FileInfo/DirInfo 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 1220befacf Fix copydata_copy_file_range bug 2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 01aa888c7a Fix eplus search bug
Not that anyone would ever use this but it was noticed in a scan.
2026-03-24 21:13:34 -04:00
Antonio SJ Musumeci 6a6aa054cd Add support for Arch, btw 2026-03-14 11:39:48 -04:00
Antonio SJ Musumeci 04939bbe72 No longer explicitly error out on broken mount point
If ENOTCONN (broken FUSE mount) umount first and continue to mount.
2026-03-08 00:39:45 -05:00
Antonio SJ Musumeci 2ea6290e4b Update limiting drive spinup doc 2026-03-04 11:05:55 -05:00
Antonio SJ Musumeci c66c68b137 Misc cleanup 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 075a8a6548 Convert some push_back to emplace_back 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 3c6d1b47f6 Remove unused "using"s 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 6f281b9f7f Remove duplicate includes 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 511bf47af9 Convert stdint to short ints 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 53c5a2cf1e Move fmtlib to vendored and upgrade to 12.1.0 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci eb8074ed9a Update vendored/nonstd to latest and fix API usage 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 38c19df95a Update vendored/boost to 1.90.0 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci b1228f11bd Update vendored/acutest to latest master 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci cfafafd7ff Update vendored/CLI11 to v2.6.2 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci c8ce82ce54 Update vendored/subprocess to v2.5 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 05c3d97368 Update vendored/rapidhash to v3 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 2c87e68635 Cleanup ugid.hpp usage 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 8f1ef59770 Move 3rd party libraries to "vendored" directory 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci a460c52861 Remove unused files 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci a4aade6666 Make global inode calculation function pointer atomic 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 1d1bcd3b87 Add extra checks in for file handle lookup 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 099ce2d91d Add more asserts 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 56cd831ec8 Fix str::trim to handle all whitespace characters
Use std::isspace() instead of comparing only to space character.
Now handles tabs, newlines, carriage returns, etc.
2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci e43be3c221 Remove unused PolicyCache implementation 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 2754536727 Fix integer overflow in size suffix parsing
Use __builtin_mul_overflow to detect overflow when applying size
suffixes (K, M, G, T) in str::from for int64_t and uint64_t. Previously,
large values with suffixes could silently wrap around.
2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci a0cff637e1 Misc cleanup 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci a5a6c892b9 Update copyright headers 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 3d50d4a52f Explicitly return EXIT_FAILURE 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci f69c31fd77 Rework objpool 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci a2860c3f1f Use debug mutex more 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 3f70e66ee3 Rework node obj pool to use objpool 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 160d3c832d Fix branches unit test 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 15bcc47d6a Remove "channels" and embed into session 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci e55bb93890 Simplify lowlevel fuse state management and req mem 2026-03-02 01:09:16 -05:00
Antonio SJ Musumeci 5a2a9e4530 Remove fuse_ll_pipe dead code 2026-03-02 01:09:16 -05:00
trapexit b4239495a0 Update docs for logging changes + new video (#1618) 2026-02-12 23:21:43 -06:00
trapexit 3222de6c9c Fixup debug output (#1617) 2026-02-12 22:50:38 -06:00
Aaron Andersen be4d388813 Add support for -n to not update mtab (#1616) 2026-02-12 20:35:48 -06:00
trapexit a47842e2f8 Add MOS to projects and update misc links (#1614) 2026-01-27 11:25:32 -06:00
trapexit f769d7474b Add new links to media docs (#1613) 2026-01-27 07:59:38 -06:00
trapexit 2ae57642f4 Misc documentation updates (#1612) 2026-01-27 07:00:22 -06:00
trapexit 7f1b7ac343 Add lshw to collect info tool (#1610) 2026-01-19 13:57:42 -06:00
trapexit 1290ab9708 Ensure ::exists() call can't throw (#1609) 2026-01-15 17:33:31 -06:00
trapexit 965e26989c Misc documentation updates (#1605) 2025-12-23 23:25:39 -06:00
trapexit 0caae4fbf0 Tag container image to ghcr for convenience (#1604) 2025-12-14 15:21:56 -06:00
trapexit 4d1760cb69 Rework new lup policy to work on 32bit systems (#1603)
__int128 is not available on 32bit platforms. Moved to extended
precision floats.
2025-12-14 14:56:34 -06:00
François-Xavier Payet 22df87c86a Add LUP policy (#1601) 2025-12-14 00:36:12 -06:00
trapexit 72fb6e4aba Update submitting bug reports, improve collect-info (#1598) 2025-12-06 13:55:14 -06:00
trapexit 9b046d9a34 Rework credential handling to support chroot & idmap (#1595)
Run in an elevated credential mode (root) and let the kernel manage
entitlements. Will result in slightly different behavior but should
not be noticed by most.
2025-12-05 21:03:48 -06:00
Emanuel Haupt d8918458bf libfuse: fix narrowing conversion of nsec fields on i386 (#1593)
On 32 bit platforms tv_nsec is a signed long while the FUSE setattr
arguments provide nanoseconds as uint32_t. C++17 treats uint32_t -> long
in a brace initializer as a narrowing conversion and rejects it on ILP32
targets. This surfaced when building on FreeBSD i386 with clang 19 where
the compiler refused the list initialization of struct timespec.

Add explicit static_cast<long>() for the nsec fields to make the
conversion intentional and portable. This avoids build failures on i386
while leaving 64 bit builds unchanged.
2025-11-29 10:45:20 -06:00
trapexit d3a3f65e7b Ignore remote when forcing generation of docs (#1592) 2025-11-25 23:39:13 -06:00
trapexit 411d181473 Add qbittorrent specific section of known issues (#1591) 2025-11-25 23:14:20 -06:00
Emanuel Haupt caf7915003 tools/preload: include correct extended attribute headers on FreeBSD (#1590)
Add conditional includes for extended attribute support. FreeBSD uses
<sys/extattr.h> instead of <sys/xattr.h>, which prevents the preload
module from building. This update selects the proper header based on
the target platform and fixes the build failure on FreeBSD.
2025-11-25 12:09:41 -06:00
trapexit 4c5473d569 Remove cirris builds, codeql; redo mkdocs builds (#1585) 2025-11-19 10:28:38 -06:00
trapexit 104f378c2c Replace 'unknown' distro-codename when building deb from tarball src (#1584) 2025-11-19 07:57:58 -06:00
trapexit a364405cf0 Remove --depth=1 to clone for tarball builder (#1583)
Led to empty changelog
2025-11-19 06:56:23 -06:00
trapexit 960f5fdbba Fix some links in docs (#1582) 2025-11-18 08:35:27 -06:00
trapexit 6813a51fce Ensure cfg references do not also try to initialize the value (#1580) 2025-11-17 17:08:36 -06:00
trapexit f7e86d90ce Add FAQ about lack of create policies about file size (#1578) 2025-11-16 23:44:24 -06:00
trapexit 66d120c721 Add Ubuntu 25.10 build (#1577) 2025-11-16 22:30:32 -06:00
trapexit 01973214bf Slight tweaks to config parsing error handling (#1575) 2025-11-13 16:05:03 -06:00
trapexit af1edadf72 Fix size comparison for returning ERANGE in listxattr (#1574) 2025-11-12 18:58:50 -06:00
trapexit 3bacaa6f7f Add lazy-umount-mountpoint page and mount -a FAQ (#1573) 2025-11-12 12:19:00 -06:00
trapexit b6e1a08aef Add link to container image repo (#1570) 2025-11-11 19:38:18 -06:00
trapexit 47bd4259b8 Add container image details (#1569) 2025-11-08 20:28:10 -06:00
trapexit dc486e79ce Add container image building (#1568) 2025-11-06 23:39:05 -06:00
trapexit 78551049d5 Add ability to passthrough 'nofail' mount option (#1567) 2025-11-06 11:05:19 -06:00
trapexit 4aab008233 Ensure backward compatibility with remember & noforget (#1566) 2025-11-05 19:43:57 -06:00
trapexit 3a7a4a13e7 Improve mergerfs intro in docs (#1563) 2025-11-04 00:02:46 -06:00
trapexit 41b65ae9e8 Misc cleanup for FreeBSD and clang (#1560) 2025-11-02 10:49:22 -06:00
trapexit 2412ae7316 Further tweaks to config parsing and error reporting (#1559)
* Further tweaks to config parsing and error reporting

* Fix static builds
2025-11-01 15:20:30 -05:00
trapexit ec16d07fba Mention how mmap error when direct io is enabled (#1557) 2025-10-31 09:31:37 -05:00
trapexit 37f10888a9 Add more debugging options to makefiles and cleanup (#1556) 2025-10-29 22:42:42 -05:00
Satadru Pramanik, DO, MPH, MEng 0db8a1ed6a Fix manpage links for 2.41.0-rc3 (#1555) 2025-10-29 12:07:49 -05:00
trapexit cfaad60275 Misc updates to arg parsing (#1554)
Ignore "defaults" and add a number of more modern VFS mount switches
2025-10-26 21:05:05 -05:00
trapexit 077484323c Fix variable names of fuse-msg-size and posix-acl (#1553) 2025-10-25 17:00:16 -05:00
trapexit 700fcfccf5 Update fuse-msg-size docs (#1552) 2025-10-24 23:34:28 -05:00
trapexit 4b4801add6 Config options docs updates (#1551) 2025-10-24 22:31:04 -05:00
trapexit 741128cb23 Fix lack of include for fmt (#1550) 2025-10-24 21:00:15 -05:00
trapexit 3bee0f0b57 Misc docs updates (#1549) 2025-10-24 20:47:33 -05:00
trapexit 727c522fb8 Rework config, centralize fuse config (#1547) 2025-10-24 08:20:29 -05:00
trapexit 00e5467e99 Misc docs updates (#1544) 2025-10-13 11:44:53 -05:00
trapexit 0371f03def Rework how fuse request context is handled (#1543) 2025-10-12 23:21:36 -05:00
trapexit d18c273293 Allow setting of passthrough max-stack-depth (#1542) 2025-10-07 21:52:13 -05:00
trapexit 0f24bb29ac Make allow-idmap optional (default: false) (#1541)
This is just to correct default behavior for now. More investigation
is necessary to properly support idmapping.
2025-10-06 14:46:58 -05:00
trapexit 3cb35a3a65 Fix rename path generation (#1540) 2025-10-01 17:47:13 -05:00
trapexit 25b0bc72df Fix dirent64::namelen calculation (#1538)
Worked fine on my machine where padding seemed to be all zeroed out
but that doesn't appear to be the case elsewhere.

Could try
https://codeberg.org/jbruchon/libjodycode/src/branch/master/dir.c#L92-L110
but needs to be tested.
2025-09-30 11:25:19 -05:00
trapexit e6fcebe4f3 Misc build fixes (#1537) 2025-09-29 14:05:07 -05:00
trapexit 46f85c0f6d Misc fixes, mostly for FreeBSD (#1536) 2025-09-28 23:17:51 -05:00
trapexit 7a9c3b7eb2 Remove need for and use of ioprio header (#1535) 2025-09-28 00:01:26 -05:00
trapexit 569a151da2 Add ability to proxy ioprio of client apps (#1534) 2025-09-26 11:36:56 -05:00
trapexit 2f3e807e9d Add getdents based readdir functions for Linux (#1533) 2025-09-23 23:51:21 -05:00
trapexit 95ab37fdd2 Fix typo in visualization and add new vid to docs (#1532) 2025-09-22 16:23:50 -05:00
trapexit 1707dc27e3 Misc doc updates (#1531) 2025-09-21 14:25:24 -05:00
trapexit c4126a0796 Add script to generate docs from scratch and push to github (#1530) 2025-09-21 13:04:55 -05:00
pjv 6acef3a638 Fix typo in moveonenospc.md (#1527)
typo
2025-09-21 11:42:15 -05:00
trapexit b3656a0243 Fix broken doc links (#1525) 2025-09-19 19:25:31 -05:00
trapexit 41b40b94fa Misc docs updates (#1524) 2025-09-19 19:20:28 -05:00
trapexit ca59ae53a5 idmap mount support (#1523)
* Add idmap mount support

* Further replacement of std::filesystem::path to fs::path

* Update docs regarding idmap support
2025-09-18 00:07:28 -05:00
trapexit 8c649d33ee Start using std::filesystem::path for fusepath (#1522) 2025-09-17 22:51:16 -05:00
trapexit f64ff9ca1e Revert removal of readdir init setup, simplify cfg usage (#1520) 2025-09-06 11:03:51 -05:00
trapexit 99c28ad3bc Add Ubuntu 25.04 builds (#1519) 2025-09-04 19:29:47 -05:00
trapexit 5becb334cd Misc updates of docs (#1518) 2025-09-01 20:58:58 -05:00
1050 changed files with 41176 additions and 29985 deletions
-150
View File
@@ -1,150 +0,0 @@
linux_task:
name: "alpine:latest"
container:
image: alpine:latest
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
name: "alpine-arm:latest"
arm_container:
image: alpine:latest
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
name: "fedora:41"
container:
image: fedora:41
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- make
- make rpm
linux_task:
name: "fedora-arm:41"
arm_container:
image: fedora:41
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- make
- make rpm
linux_task:
name: "ubuntu:22.04"
container:
image: ubuntu:22.04
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "ubuntu:24.04"
container:
image: ubuntu:24.04
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "ubuntu:latest"
container:
image: ubuntu:latest
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "ubuntu-arm:latest"
arm_container:
image: ubuntu:latest
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "debian:12"
container:
image: debian:12
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "debian:latest"
container:
image: debian:latest
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "debian-arm:latest"
arm_container:
image: debian:latest
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
+44 -16
View File
@@ -7,22 +7,31 @@ assignees: ''
---
See also https://trapexit.github.io/mergerfs/latest/support
**Describe the bug**
A clear and concise description of the unexpected behavior.
**Please be sure to use latest release of mergerfs to ensure the issue still exists. Not your distro's latest but the latest official release.**
**Please be sure to use latest release of mergerfs to ensure the issue
still exists. Not your distro's latest but the latest official
release. Support will NOT be provided for old versions.**
The master branch is **not** to be considered production ready. Feel free to file bug reports but do so indicating clearly that you are testing unreleased code.
The `master` branch is **not** to be considered production ready. Feel
free to file bug reports but do so indicating clearly that you are
testing unreleased code.
**To Reproduce**
Steps to reproduce the behavior. List **all** steps to reproduce. **All** settings.
Provide steps to reproduce the behavior. List **all** steps to
reproduce. **All** settings. The version of all software involved.
Please simplify the reproduction as much as possible.
- Unless it is dependenat on multiple branches, use a single branch
- Reproduce with standard tooling if possible (touch,truncate,rm,rmdir,ln,etc.) Having to install 3rd party software will make debugging more difficult.
* Unless it is dependenat on multiple branches, use a single branch
* Reproduce with standard tooling if possible
(touch,truncate,rm,rmdir,ln,etc.) Having to install 3rd party
software will make debugging more difficult.
**Expected behavior**
@@ -30,19 +39,38 @@ Please simplify the reproduction as much as possible.
A clear and concise description of the expected behavior.
**Workarounds already tested**
Please list all workarounds attempted and why they were ruled out as
options.
**System information:**
- OS, kernel version: `uname -a`
- mergerfs version: `mergerfs -V`
- mergerfs settings
- List of drives, filesystems, & sizes:
- `df -h`
- `lsblk -f`
- A strace of the application having a problem:
- `strace -fvTtt -s 256 -o /tmp/app.strace.txt <cmd>`
- `strace -fvTtt -s 256 -o /tmp/app.strace.txt -p <appPID>`
- strace of mergerfs while app tried to do it's thing:
- `strace -fvTtt -s 256 -p <mergerfsPID> -o /tmp/mergerfs.strace.txt`
* Attach output from
[mergerfs.collect-info](https://trapexit.github.io/mergerfs/latest/tooling/#mergerfscollect-info)
tool.
* Alternatively:
* OS: `lsb_release -a`
* kernel version: `uname -a`
* mergerfs version: `mergerfs --version`
* mergerfs settings / arguments: from fstab, systemd unit, command
line, OMV plugin, etc.
* List of drives, filesystems, & sizes:
* `df -h`
* `lsblk -o NAME,FSTYPE,FSSIZE,SIZE,MOUNTPOINTS,RM,RO,ROTA`
* All information about the relevant paths and files: permissions,
ownership, etc.
* All information about the application making the requests:
version, uid/gid
* Is mergerfs running within a container or used with container
technologies?
* Are the client apps using mergerfs running in a container?
* A strace of the application having a problem:
* `strace -fvTtt -s 256 -o /tmp/app.strace.txt <cmd>`
* `strace -fvTtt -s 256 -o /tmp/app.strace.txt -p <appPID>`
* strace of mergerfs while app tried to do it's thing:
* `strace -fvTtt -s 256 -p <mergerfsPID> -o /tmp/mergerfs.strace.txt`
**Additional context**
-42
View File
@@ -1,42 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: "7 3 * * 6"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ python, cpp ]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@v2
if: ${{ matrix.language == 'python' || matrix.language == 'cpp' }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{ matrix.language }}"
-49
View File
@@ -1,49 +0,0 @@
name: "mkdocs"
on:
push:
branches:
- master
tags:
- '*'
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Configure Git Credentials
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Install dependencies
working-directory: ./mkdocs
run: |
pip install --no-cache-dir mkdocs mkdocs-material pymdown-extensions mike
- name: Deploy docs
working-directory: ./mkdocs
env:
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
run: |
if [ "$REF_TYPE" = "tag" ]; then
mike deploy "$REF_NAME" "latest" --allow-empty --ignore-remote-status --update-aliases
mike set-default "latest"
#git checkout gh-pages
#git push -f origin gh-pages
elif [ "$REF_TYPE" = "branch" ] && [ "$REF_NAME" = "master" ]; then
mike deploy "master" --ignore-remote-status --allow-empty
#git checkout gh-pages
#git push -f origin gh-pages
fi
+1
View File
@@ -9,6 +9,7 @@
* scope_guard: https://github.com/Neargye/scope_guard
* subprocess: https://github.com/arun11299/cpp-subprocess
* CLI11: https://github.com/CLIUtils/CLI11
* string-lite: https://github.com/martinmoene/string-lite
* boost
* download boost
* ./bootstrap.sh
+1 -1
View File
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2025, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2026, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
+83 -28
View File
@@ -12,6 +12,8 @@
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
$(info mergerfs MAKEFLAGS: $(MAKEFLAGS))
ifeq ($(shell id -u),0)
FAKEROOT ?=
endif
@@ -27,6 +29,8 @@ MKDIR ?= mkdir
MKTEMP ?= mktemp
MV ?= mv
RM ?= rm
RPMBUILD ?= rpmbuild
RPMBUILD_FLAGS ?=
SED ?= sed
STRIP ?= strip
TAR ?= tar
@@ -35,6 +39,8 @@ TOUCH ?= touch
BUILDDIR := build
GITHUB_REPO ?= "https://github.com/trapexit/mergerfs"
DEFAULT_TARGET := debian:13.amd64
ifndef GIT_REPO
ifneq ($(shell $(GIT) --version 2> /dev/null),)
ifeq ($(shell test -e .git; echo $$?),0)
@@ -44,12 +50,30 @@ endif
endif
USE_XATTR ?= 1
UGID_USE_RWLOCK ?= 0
ifdef NDEBUG
OPT_FLAGS := -O2 -DNDEBUG
ifdef SANITIZE
ifeq ($(SANITIZE),1)
override SANITIZE := -fsanitize=address,undefined,leak
else ifeq ($(filter -fsanitize=%,$(SANITIZE)),)
override SANITIZE := -fsanitize=$(SANITIZE)
endif
endif
COMMON_OPT_FLAGS += \
-fstack-protector-strong \
-Wextra \
-Wno-unused-parameter
DEBUG_OPT_FLAGS += \
-O0 \
-g3 \
-fno-omit-frame-pointer \
$(SANITIZE)
ifdef RELEASE
OPT_FLAGS := -O3 $(COMMON_OPT_FLAGS)
else
OPT_FLAGS := -O0 -g -fno-omit-frame-pointer -DDEBUG
OPT_FLAGS ?= $(DEBUG_OPT_FLAGS) $(COMMON_OPT_FLAGS)
endif
ifdef STATIC
@@ -69,7 +93,7 @@ OBJS := $(SRC:src/%.cpp=build/.objs/%.cpp.o)
DEPS := $(SRC:src/%.cpp=build/.objs/%.cpp.d)
TESTS := $(wildcard tests/*.cpp)
TESTS_OBJS := $(filter-out build/.objs/mergerfs.o,$(OBJS))
TESTS_OBJS := $(filter-out build/.objs/mergerfs.cpp.o,$(OBJS))
TESTS_OBJS += $(TESTS:tests/%.cpp=build/.test_objs/%.cpp.o)
TESTS_DEPS := $(TESTS:tests/%.cpp=build/.test_objs/%.cpp.d)
TESTS_DEPS += $(DEPS)
@@ -77,7 +101,8 @@ TESTS_DEPS += $(DEPS)
MANPAGE := mergerfs.1
CPPFLAGS ?=
override CPPFLAGS += \
-D_FILE_OFFSET_BITS=64
-D_FILE_OFFSET_BITS=64 \
-DBOOST_UNORDERED_DISABLE_PARALLEL_ALGORITHMS
CFLAGS ?= \
$(OPT_FLAGS) \
-Wall \
@@ -98,28 +123,34 @@ CXXFLAGS ?= \
-Wno-unused-result \
-pipe
override CXXFLAGS += \
-std=c++17 \
-std=c++20 \
-MMD \
-MP
override INC_FLAGS := \
-Isrc \
-Ilibfuse/include
-Ivendored \
-Ivendored/libfuse/include
override MFS_FLAGS := \
-DUSE_XATTR=$(USE_XATTR) \
-DUGID_USE_RWLOCK=$(UGID_USE_RWLOCK)
-DUSE_XATTR=$(USE_XATTR)
override TESTS_FLAGS := \
-Isrc \
-Ivendored \
-Ivendored/acutest \
-DTESTS
LIBFUSE := libfuse/$(BUILDDIR)/libfuse.a
LIBFUSE := vendored/libfuse/$(BUILDDIR)/libfuse.a
LDFLAGS ?=
LDLIBS := \
-lrt \
-latomic \
-pthread \
-lstdc++fs
-pthread
override LDLIBS += \
$(LIBFUSE)
ifeq ($(CXX),g++)
GCC_VERSION := $(shell $(CXX) -dumpversion | cut -f1 -d.)
ifeq ($(shell test $(GCC_VERSION) -lt 9; echo $$?),0)
override LDLIBS += -lstdc++fs
endif
endif
# https://www.gnu.org/prep/standards/html_node/Directory-Variables.html
DESTDIR ?=
@@ -148,6 +179,8 @@ help:
@echo "USE_XATTR=0 - build program without xattrs functionality"
@echo "STATIC=1 - build static binary"
@echo "LTO=1 - build with link time optimization"
@echo "SANITIZE=1 - build with sanitizers (address,undefined,leak)"
@echo "SANITIZE=list - build with custom sanitizers (comma separated)"
@echo "CLEANUP=1 - cleanup images between release build process"
@echo "PKGDIR=/dir/ - location for release build pkgs"
@echo "GITREF=gitref - gitref to use for release builds"
@@ -156,19 +189,21 @@ help:
$(BUILDDIR)/mergerfs: $(LIBFUSE) src/version.hpp $(OBJS)
$(CXX) $(CXXFLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LDLIBS)
$(BUILDDIR)/fsck.mergerfs:
$(BUILDDIR)/fsck.mergerfs: $(BUILDDIR)/mergerfs
$(LN) -sf "mergerfs" $@
$(BUILDDIR)/mergerfs.collect-info:
$(BUILDDIR)/mergerfs.collect-info: $(BUILDDIR)/mergerfs
$(LN) -sf "mergerfs" $@
$(BUILDDIR)/tests: $(BUILDDIR)/mergerfs $(TESTS_OBJS)
$(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(TESTS_OBJS) -o $@ $(LDFLAGS) $(LDLIBS)
.PHONY: libfuse
libfuse: $(LIBFUSE)
$(MAKE) -C vendored/libfuse
$(LIBFUSE):
libfuse:
$(MAKE) NDEBUG=$(NDEBUG) -C libfuse
$(MAKE) -C vendored/libfuse $(BUILDDIR)/libfuse.a RELEASE=$(RELEASE)
tests: $(BUILDDIR)/tests
@@ -194,7 +229,7 @@ $(BUILDDIR)/stamp:
$(BUILDDIR)/.objs/%.cpp.o: src/%.cpp $(BUILDDIR)/stamp
$(CXX) $(CXXFLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@
$(BUILDDIR)/.test_objs/%.cpp.o: tests/%.cpp
$(BUILDDIR)/.test_objs/%.cpp.o: tests/%.cpp | $(BUILDDIR)/stamp
$(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@
$(BUILDDIR)/preload.so: $(BUILDDIR)/stamp tools/preload.c
@@ -206,7 +241,7 @@ preload: $(BUILDDIR)/preload.so
clean: rpm-clean
$(RM) -rf $(BUILDDIR)
$(FIND) . -name "*~" -delete
$(MAKE) -C libfuse clean
$(MAKE) -C vendored/libfuse clean
.PHONY: distclean
distclean: clean
@@ -227,7 +262,7 @@ install-base: all
.PHONY: install-mount-tools
install-mount-tools: install-base
$(MKDIR) -p "$(INSTALLBINDIR)"
$(MAKE) -C libfuse install
$(MAKE) -C vendored/libfuse install
.PHONY: install-man
install-man: man/$(MANPAGE)
@@ -244,10 +279,12 @@ install-strip: install-base
$(STRIP) "$(INSTALLBINDIR)/mergerfs"
.PHONY: uninstall
uninstall: uninstall-base uninstall-mount.mergerfs uninstall-man
uninstall: uninstall-base uninstall-mount.mergerfs uninstall-preload uninstall-man
uninstall-base:
$(RM) -f "$(INSTALLBINDIR)/mergerfs"
$(RM) -f "$(INSTALLBINDIR)/fsck.mergerfs"
$(RM) -f "$(INSTALLBINDIR)/mergerfs.collect-info"
uninstall-mount.mergerfs:
$(RM) -f "$(INSTALLBINDIR)/mount.mergerfs"
@@ -255,6 +292,9 @@ uninstall-mount.mergerfs:
uninstall-man:
$(RM) -f "$(INSTALLMAN1DIR)/$(MANPAGE)"
uninstall-preload:
$(RM) -f "$(INSTALLLIBDIR)/preload.so"
.PHONY: tarball
tarball: changelog version
$(eval VERSION := $(shell cat VERSION))
@@ -272,6 +312,10 @@ ifdef GIT_REPO
$(GIT2DEBCL) --name mergerfs > debian/changelog
else
$(CP) -v ChangeLog debian/changelog
$(eval DISTRO := $(shell lsb_release -i -s | tr '[:upper:]' '[:lower:]'))
$(eval CODENAME := $(shell lsb_release -c -s | tr '[:upper:]' '[:lower:]'))
sed -i 's/~unknown-unknown/~$(DISTRO)-$(CODENAME)/g' debian/changelog
sed -i 's/ unknown; / $(CODENAME); /g' debian/changelog
endif
.PHONY: signed-deb
@@ -301,7 +345,7 @@ rpm: tarball
$(SED) 's/__VERSION__/$(VERSION)/g' mergerfs.spec > \
rpmbuild/SOURCES/mergerfs.spec
cp -ar mergerfs-$(VERSION).tar.gz rpmbuild/SOURCES
rpmbuild -ba rpmbuild/SOURCES/mergerfs.spec \
$(RPMBUILD) $(RPMBUILD_FLAGS) -ba rpmbuild/SOURCES/mergerfs.spec \
--define "_topdir $(CURDIR)/rpmbuild"
.PHONY: install-build-pkgs
@@ -310,10 +354,11 @@ install-build-pkgs:
.PHONY: install-build-tools
install-build-tools:
./bulidtools/install-build-tools
./buildtools/install-build-tools
define build_release
$(eval GITREF ?= $(shell git describe --exact-match --tags HEAD 2>/dev/null || git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD))
@echo "GITREF=$(GITREF)"
./buildtools/build-release \
--target=$(1) \
$(if $(CLEANUP),--cleanup) \
@@ -322,13 +367,16 @@ define build_release
--branch=$(GITREF)
endef
.PHONY: release
.PHONY: release release-all
.PHONY: release-amd64 release-arm64 release-armhf release-riscv64
.PHONY: release-sample release-static release-tarball
.PHONY: release-sample release-static release-tarball release-installer
release:
$(MAKE) RELEASE=1 CPPFLAGS="$(CPPFLAGS) -DNDEBUG" all
release-all:
$(call build_release,"all")
release-sample:
$(call build_release,"debian:13.amd64")
$(if $(TARGET),,$(eval TARGET := $(shell ./buildtools/detect-local-target 2>/dev/null)))
$(call build_release,$(if $(TARGET),$(TARGET),$(DEFAULT_TARGET)))
release-amd64:
$(call build_release,"amd64")
release-arm64:
@@ -336,11 +384,18 @@ release-arm64:
release-armhf:
$(call build_release,"armhf")
release-riscv64:
$(call build_release,"risc64")
$(call build_release,"riscv64")
release-static:
$(call build_release,"static")
release-tarball:
$(call build_release,"tarball")
release-installer:
$(call build_release,"installer")
container:
$(eval GITREF ?= $(shell git describe --exact-match --tags HEAD 2>/dev/null || git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD))
@echo "GITREF=$(GITREF)"
./buildtools/build-containerimage "$(GITREF)"
.PHONY: tags
tags:
+6 -3
View File
@@ -25,6 +25,7 @@ etc.](https://trapexit.github.io/mergerfs/latest/project_comparisons/)
* Works with filesystems of [almost any
type](https://trapexit.github.io/mergerfs/latest/faq/compatibility_and_integration/#what-filesystems-can-be-used-as-branches)
* Ignore read-only filesystems when creating files
* [Hard links](https://trapexit.github.io/mergerfs/latest/faq/technical_behavior_and_limitations/#do-hard-links-work)
* Hard link [copy-on-write /
CoW](https://trapexit.github.io/mergerfs/latest/config/link_cow/)
* [Runtime configurable](https://trapexit.github.io/mergerfs/latest/runtime_interface/)
@@ -59,8 +60,10 @@ which branch will be selected for the creation.
For functions which change attributes or remove the file the behavior
may be applied to all instances found.
Read more about [policies
here](https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies/).
The way in which mergerfs behaves is controlled by the
[config/options/settings](https://trapexit.github.io/mergerfs/latest/config/options). More
specifically by
[policies](https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies/).
### Visualization
@@ -77,7 +80,7 @@ A + B = C
| | |
+-- /dir2 | +-- /dir2
| | | | |
| *-- file4 | | +-- file4
| +-- file4 | | +-- file4
| | |
| +-- /dir3 +-- /dir3
| | | | |
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
GIT_BRANCH="${1:-master}"
set -x
podman \
build \
--squash \
--no-cache \
--force-rm \
--build-arg="GIT_REPO=file:///mnt" \
--build-arg="BRANCH=${GIT_BRANCH}" \
-v "${PWD}":/mnt:ro \
-f "buildtools/containerimage/Containerfile" \
--tag "ghcr.io/trapexit/mergerfs:${GIT_BRANCH}" \
buildtools/
+37 -24
View File
@@ -1,6 +1,19 @@
#!/bin/sh
set -e
if [ -e /tmp/build-env ]; then
. /tmp/build-env
fi
rpmbuild_flags()
{
case "$(command -v "${CXX:-g++}")" in
/opt/rh/*)
printf '%s' '--undefine _annotated_build'
;;
esac
}
if [ $# -ne 2 ]; then
echo "usage: $0 <branch> <git_repo>"
echo "argv: $@"
@@ -27,37 +40,37 @@ git log HEAD^1..
mkdir "/build"
if [ -e /usr/bin/apt-get ]; then
make deb
find /tmp/ \
-type f \
-name "*.deb" \
-not -name "*dbgsym*" \
-exec cp -v {} /build/ \;
exit 0
elif [ -e /usr/bin/dnf ]; then
make rpm
find /tmp/ \
-type f \
-name "*.rpm" \
-not -name "*sym*" \
-not -name "*src.rpm" \
-exec cp -v {} /build/ \;
exit 0
make RPMBUILD_FLAGS="$(rpmbuild_flags)" rpm
elif [ -e /usr/bin/yum ]; then
. /opt/rh/devtoolset-9/enable
make rpm
find /tmp/ \
-type f \
-name "*.rpm" \
-not -name "*sym*" \
-not -name "*src.rpm" \
-exec cp -v {} /build/ \;
exit 0
make RPMBUILD_FLAGS="$(rpmbuild_flags)" rpm
elif [ -e /sbin/apk ]; then
echo "NOT YET SUPPORTED"
exit 1
elif [ -e /usr/sbin/pkg ]; then
echo "NOT YET SUPPORTED"
exit 1
elif [ -e /usr/bin/pacman ]; then
make tarball
else
echo "NOT YET SUPPORTED"
exit 1
fi
exit 1
echo "Copy packages to host..."
if [ -e /usr/bin/pacman ]; then
find "${BUILDDIR}" \
-type f \
-name "*.tar.gz" \
-exec cp -v {} /build/ \;
else
find "${BUILDDIR}" \
-type f \
\( -name "*.deb" -or -name "*.rpm" \) \
-not -name "*dbgsym*" \
-not -name "*sym*" \
-not -name "*src.rpm" \
-exec cp -v {} /build/ \;
fi
exit 0
+50 -11
View File
@@ -18,10 +18,12 @@ def build(git_repo,
'--force-rm']
if os.path.exists(git_repo):
git_repo = os.path.realpath(git_repo)
args += ['-v',f'{git_repo}:/tmp/mergerfs.git:ro']
args += ['--build-arg=GIT_REPO=/tmp/mergerfs.git']
args += ['-v',f'{git_repo}:/tmp/mergerfs.git:ro:z']
args += ['--build-arg=GIT_REPO=file:///tmp/mergerfs.git']
else:
args += [f'--build-arg=GIT_REPO={git_repo}']
os.makedirs(pkgdirpath,exist_ok=True)
args += ['-v',f'{os.path.realpath(pkgdirpath)}:/pkgs:ro']
args += ['-o',pkgdirpath,
'-f',containerfile,
f'--build-arg=BRANCH={branch}',
@@ -30,8 +32,6 @@ def build(git_repo,
# TODO: Capture output and write to log
print(args)
rv = subprocess.run(args)
os.makedirs(pkgdirpath,exist_ok=True)
report_filepath = os.path.join(pkgdirpath,"build-report.txt")
with open(report_filepath,"a+") as f:
build = os.path.basename(containerfile)
@@ -47,14 +47,49 @@ def build(git_repo,
def setup():
args = ['sudo',
'apt-get',
'install',
'-fy',
'qemu-user-static',
'qemu-user-binfmt']
# Detect distribution
if os.path.exists('/usr/bin/apt-get'):
args = ['sudo',
'apt-get',
'install',
'-fy',
'qemu-user-static',
'qemu-user-binfmt']
elif os.path.exists('/usr/bin/pacman'):
args = ['sudo',
'pacman',
'-Sy',
'--noconfirm',
'qemu-user-static']
else:
print("Unsupported distribution for setup")
sys.exit(1)
print(args)
subprocess.run(args)
setup_binfmt()
def setup_binfmt():
"""Register qemu-user-static binfmt handlers (needed on Arch, etc.)."""
binfmt_conf = """\
:qemu-aarch64:M::\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\xb7\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-aarch64-static:F
:qemu-arm:M::\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x28\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-arm-static:F
:qemu-armeb:M::\\x7fELF\\x01\\x02\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x28:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff:/usr/bin/qemu-armeb-static:F
:qemu-riscv64:M::\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\xf3\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-riscv64-static:F
:qemu-i386:M::\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x03\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-i386-static:F
"""
conf_path = '/etc/binfmt.d/qemu-static.conf'
print(f"Writing {conf_path}")
try:
with open(conf_path, 'w') as f:
f.write(binfmt_conf)
except PermissionError:
print(f"Permission denied writing {conf_path}; trying with sudo...")
subprocess.run(['sudo', 'tee', conf_path],
input=binfmt_conf.encode(), check=True)
print("Restarting systemd-binfmt...")
subprocess.run(['sudo', 'systemctl', 'restart', 'systemd-binfmt'])
def podman_cleanup():
@@ -81,7 +116,7 @@ def parse_args():
required=False,
action='store_true')
p.add_argument('--git-repo',
required=True,
required=False,
type=str)
return p.parse_args()
@@ -101,6 +136,10 @@ def main():
setup()
sys.exit(0)
if not args.git_repo:
print("error: the following arguments are required: --git-repo")
sys.exit(1)
containerfiles = []
basepath = 'buildtools/containerfiles'
if os.path.exists(f'{basepath}/{args.target}'):
+10
View File
@@ -0,0 +1,10 @@
FROM --platform=amd64 fedora:44 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
+10
View File
@@ -0,0 +1,10 @@
FROM --platform=arm64 fedora:44 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
+6 -4
View File
@@ -1,13 +1,15 @@
ARG BUILD_TIMESTAMP=0
FROM --platform=linux/amd64 alpine:latest as build
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs
ARG BRANCH=master
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
WORKDIR /tmp/mergerfs
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
RUN mkdir /build
RUN tar cvfz /build/mergerfs-static-linux_amd64.tar.gz --directory=/tmp usr sbin
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_amd64.tar.gz --directory=/tmp usr sbin
FROM scratch
COPY --from=build /build/ /
+6 -4
View File
@@ -1,13 +1,15 @@
ARG BUILD_TIMESTAMP=0
FROM --platform=linux/arm64 alpine:latest as build
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs
ARG BRANCH=master
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
WORKDIR /tmp/mergerfs
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
RUN mkdir /build
RUN tar cvfz /build/mergerfs-static-linux_arm64.tar.gz --directory=/tmp usr sbin
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_arm64.tar.gz --directory=/tmp usr sbin
FROM scratch
COPY --from=build /build/ /
+6 -4
View File
@@ -1,13 +1,15 @@
ARG BUILD_TIMESTAMP=0
FROM --platform=linux/armhf alpine:latest as build
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs
ARG BRANCH=master
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
WORKDIR /tmp/mergerfs
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
RUN mkdir /build
RUN tar cvfz /build/mergerfs-static-linux_armhf.tar.gz --directory=/tmp usr sbin
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_armhf.tar.gz --directory=/tmp usr sbin
FROM scratch
COPY --from=build /build/ /
+6 -4
View File
@@ -1,13 +1,15 @@
ARG BUILD_TIMESTAMP=0
FROM --platform=linux/i386 alpine:latest as build
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs
ARG BRANCH=master
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
WORKDIR /tmp/mergerfs
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
RUN mkdir /build
RUN tar cvfz /build/mergerfs-static-linux_i386.tar.gz --directory=/tmp usr sbin
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_i386.tar.gz --directory=/tmp usr sbin
FROM scratch
COPY --from=build /build/ /
+7 -3
View File
@@ -1,9 +1,13 @@
ARG BUILD_TIMESTAMP=0
FROM --platform=linux/amd64 ubuntu:latest as build
RUN apt-get update && apt-get install -y makeself
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs && \
DEBIAN_FRONTEND=noninteractive apt-get -qy --no-install-suggests --no-install-recommends install \
makeself
RUN mkdir -p /tmp/mergerfs/
RUN --mount=type=bind,src=pkgs,dst=/pkgs ls -lhd /pkgs
RUN --mount=type=bind,src=pkgs,dst=/pkgs cp -v /pkg/*static*.gz /tmp/mergerfs/
RUN ls -lhd /pkgs
RUN cp -v /pkgs/*static*.gz /tmp/mergerfs/
RUN mkdir -p /build
RUN makeself /tmp/mergerfs/ /build/mergerfs-installer.run "mergerfs" echo "foo"
FROM scratch
+6 -4
View File
@@ -1,13 +1,15 @@
ARG BUILD_TIMESTAMP=0
FROM --platform=linux/riscv64 alpine:latest as build
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs
ARG BRANCH=master
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
WORKDIR /tmp/mergerfs
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
RUN mkdir /build
RUN tar cvfz /build/mergerfs-static-linux_riscv64.tar.gz --directory=/tmp usr sbin
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_riscv64.tar.gz --directory=/tmp usr sbin
FROM scratch
COPY --from=build /build/ /
+7 -3
View File
@@ -1,13 +1,17 @@
ARG BUILD_TIMESTAMP=0
FROM alpine:latest as tarball
COPY install-build-pkgs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG BRANCH=master
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
ARG GIT_REPO
RUN git clone --single-branch "${GIT_REPO}" /tmp/mergerfs --branch="${BRANCH}"
RUN cd /tmp/mergerfs && make version tarball
RUN mkdir /build
RUN cp -v /tmp/mergerfs/*.tar.gz /build/
FROM scratch
COPY --from=tarball /build/ /
@@ -0,0 +1,10 @@
FROM --platform=amd64 ubuntu:25.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=arm64 ubuntu:25.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=armhf ubuntu:25.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=riscv64 ubuntu:25.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=amd64 ubuntu:25.10 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=arm64 ubuntu:25.10 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=armhf ubuntu:25.10 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=riscv64 ubuntu:25.10 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=amd64 ubuntu:26.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=arm64 ubuntu:26.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=armhf ubuntu:26.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
@@ -0,0 +1,10 @@
FROM --platform=riscv64 ubuntu:26.04 as build
COPY install-build-pkgs build-mergerfs /tmp/
RUN /tmp/install-build-pkgs
ARG BUILD_TIMESTAMP=0
ARG GIT_REPO
ARG BRANCH
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
FROM scratch
COPY --from=build /build/ /
+19
View File
@@ -0,0 +1,19 @@
FROM --platform=linux/amd64 alpine:latest as build
ARG GIT_REPO
ARG BRANCH
RUN <<EOF
apk add git gcc g++ make linux-headers
git clone --single-branch --branch="${BRANCH}" "${GIT_REPO}" /tmp/mergerfs
cd /tmp/mergerfs
make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp/install -j$(nproc) install
tar cvfz /tmp/mergerfs.tgz --directory=/tmp/install usr sbin
EOF
FROM --platform=linux/amd64 alpine:latest
RUN --mount=from=build,source=/tmp/mergerfs.tgz,target=/tmp/mergerfs.tgz <<EOF
tar xvf /tmp/mergerfs.tgz -C /
EOF
ENTRYPOINT ["/usr/local/bin/mergerfs"]
CMD ["--help"]
+119
View File
@@ -0,0 +1,119 @@
#!/bin/bash
# Detects local OS and maps to containerfiles target
# Returns target if found in buildtools/containerfiles/, empty string otherwise
set -euo pipefail
CONTAINERFILES_DIR="buildtools/containerfiles"
# Detect system architecture and map to containerfiles format
detect_arch() {
local arch
arch=$(uname -m)
case "$arch" in
x86_64) echo "amd64" ;;
aarch64) echo "arm64" ;;
armv7l) echo "armhf" ;;
i686|i386) echo "i386" ;;
riscv64) echo "riscv64" ;;
*) echo "" ;;
esac
}
# Detect distro and version from os-release
detect_distro() {
local os_release="${1:-}"
if [[ ! -r "$os_release" ]]; then
return 1
fi
# Source the file safely - ID and VERSION_ID are environment variables
. "$os_release"
local distro="$ID"
local version="$VERSION_ID"
# Map distro ID to containerfiles naming convention
case "$distro" in
ubuntu|debian|fedora) ;;
rocky|rhel|centos|almalinux) distro="rockylinux" ;;
*)
# Fallback to ID_LIKE for derivatives
if [[ -n "${ID_LIKE:-}" ]]; then
case "$ID_LIKE" in
*ubuntu*) distro="ubuntu" ;;
*debian*) distro="debian" ;;
*fedora*) distro="fedora" ;;
esac
fi
;;
esac
# Only return if we have valid distro and version
if [[ -n "$distro" && -n "$version" ]]; then
echo "$distro:$version"
else
return 1
fi
}
# Try to find a matching containerfile target
find_containerfile() {
local target="$1"
local containerfile="${CONTAINERFILES_DIR}/${target}"
# Direct match
if [[ -d "$containerfile" || -f "$containerfile" ]]; then
echo "$target"
return 0
fi
return 1
}
# Main detection logic
main() {
local arch
# Detect architecture
arch=$(detect_arch)
if [[ -z "$arch" ]]; then
echo "WARNING: Could not detect system architecture" >&2
exit 1
fi
# Try to detect distro from multiple locations
local distro_ver
distro_ver=$(detect_distro "/etc/os-release") || \
distro_ver=$(detect_distro "/usr/lib/os-release") || {
echo "WARNING: Could not detect distro from os-release" >&2
exit 1
}
# Build target string
local target="${distro_ver}.${arch}"
# Try to find matching containerfile
if find_containerfile "$target"; then
exit 0
fi
# Try with partial version matching (e.g., 25.10 → 25)
local distro="${distro_ver%%:*}"
local version="${distro_ver##*:}"
# Strip last minor version number and try again
local short_version="${version%.*}"
if [[ -n "$short_version" ]]; then
target="${distro}:${short_version}.${arch}"
if find_containerfile "$target"; then
exit 0
fi
fi
# No match found
echo "WARNING: No containerfile found for target: ${distro_ver}.${arch}" >&2
exit 1
}
main "$@"
+128 -3
View File
@@ -1,37 +1,162 @@
#!/bin/sh
set -e
BUILD_ENV=/tmp/build-env
write_gcc_env()
{
{
printf 'export CC=%s\n' "$1"
printf 'export CXX=%s\n' "$2"
} > "$BUILD_ENV"
}
write_toolset_env()
{
{
printf '. %s\n' "$1"
printf 'export CC=gcc\n'
printf 'export CXX=g++\n'
} > "$BUILD_ENV"
}
latest_apt_gxx()
{
apt-cache --names-only search '^g[+][+]-[0-9]' \
| sed -n 's/^\(g++-[0-9][0-9]*\) .*/\1/p' \
| sort -t- -k2,2V \
| tail -n 1
}
latest_dnf_toolset()
{
dnf -q list --showduplicates 'gcc-toolset-*-gcc-c++' 2>/dev/null \
| sed -n 's/^gcc-toolset-\([0-9][0-9]*\)-gcc-c++\..*/\1/p' \
| sort -V \
| tail -n 1
}
latest_yum_toolset()
{
yum -q list available 'devtoolset-*-gcc-c++' 2>/dev/null \
| sed -n 's/^devtoolset-\([0-9][0-9]*\)-gcc-c++\..*/\1/p' \
| sort -V \
| tail -n 1
}
version_gt()
{
highest="$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1)"
[ "$highest" = "$1" ] && [ "$1" != "$2" ]
}
check_cxx20()
{
cxx="${CXX:-g++}"
test_src=/tmp/mergerfs-cxx20-check.cpp
test_bin=/tmp/mergerfs-cxx20-check
cat > "$test_src" <<'EOF'
template <typename T>
concept Incrementable = requires(T value) { value + 1; };
static_assert(Incrementable<int>);
int main()
{
return 0;
}
EOF
if ! "$cxx" -std=c++20 "$test_src" -o "$test_bin" >/dev/null 2>&1; then
echo "error: $cxx does not support C++20" >&2
exit 1
fi
rm -f "$test_src" "$test_bin"
}
: > "$BUILD_ENV"
if [ -e /usr/bin/apt-get ]; then
export DEBIAN_FRONTEND=noninteractive
apt-get -qy update
cxx_pkg="$(latest_apt_gxx)"
if [ -n "$cxx_pkg" ]; then
gcc_pkg="gcc-${cxx_pkg#g++-}"
compiler_pkgs="$gcc_pkg $cxx_pkg"
write_gcc_env "$gcc_pkg" "$cxx_pkg"
else
compiler_pkgs="g++"
write_gcc_env gcc g++
fi
apt-get -qy --force-yes --no-install-suggests --no-install-recommends \
install \
ca-certificates \
build-essential \
dpkg-dev \
git \
g++ \
$compiler_pkgs \
debhelper \
lsb-release \
fakeroot
. "$BUILD_ENV"
check_cxx20
elif [ -e /usr/bin/dnf ]; then
dnf -y update
toolset="$(latest_dnf_toolset)"
dnf -y install \
git rpm-build gcc-c++ make which libatomic
default_gxx_major="$(g++ -dumpversion | cut -f1 -d.)"
if [ -n "$toolset" ] && version_gt "$toolset" "$default_gxx_major"; then
dnf -y install \
"gcc-toolset-${toolset}-gcc" \
"gcc-toolset-${toolset}-gcc-c++"
write_toolset_env "/opt/rh/gcc-toolset-${toolset}/enable"
else
write_gcc_env gcc g++
fi
. "$BUILD_ENV"
check_cxx20
elif [ -e /usr/bin/yum ]; then
yum -y update
yum -y install \
git rpm-build gcc-c++ make which
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc\*
yum -y install centos-release-scl || true
yum -y update
toolset="$(latest_yum_toolset)"
default_gxx_major="$(g++ -dumpversion | cut -f1 -d.)"
if [ -n "$toolset" ] && version_gt "$toolset" "$default_gxx_major"; then
yum -y install \
"devtoolset-${toolset}-gcc" \
"devtoolset-${toolset}-gcc-c++"
write_toolset_env "/opt/rh/devtoolset-${toolset}/enable"
else
write_gcc_env gcc g++
fi
. "$BUILD_ENV"
check_cxx20
elif [ -e /usr/bin/zypper ]; then
zypper update -y
zypper install -y \
git rpm-build gcc-c++ make which
write_gcc_env gcc g++
. "$BUILD_ENV"
check_cxx20
elif [ -e /sbin/apk ]; then
apk add \
abuild git gcc g++ make \
linux-headers
write_gcc_env gcc g++
. "$BUILD_ENV"
check_cxx20
elif [ -e /usr/sbin/pkg ]; then
pkg install \
git gmake gcc
elif [ -e /usr/bin/pacman ]; then
pacman -Sy --noconfirm base-devel git fakeroot
write_gcc_env gcc g++
. "$BUILD_ENV"
check_cxx20
fi
+18 -5
View File
@@ -1,19 +1,32 @@
#!/bin/sh
if [ "$(id -u)" -eq 0 ]; then
SUDO=
elif command -v sudo >/dev/null 2>&1; then
SUDO=sudo
elif command -v doas >/dev/null 2>&1; then
SUDO=doas
else
printf '%s\n' "error: need root privileges; run as root or install sudo/doas" >&2
exit 1
fi
if [ -e /usr/bin/apt-get ]; then
export DEBIAN_FRONTEND=noninteractive
apt-get -qy update
apt-get -qy --force-yes --no-install-suggests --no-install-recommends install \
${SUDO} apt-get -qy update
${SUDO} apt-get -qy --no-install-suggests --no-install-recommends install \
qemu-user-static \
podman
elif [ -e /usr/bin/dnf ]; then
dnf -y update
dnf -y install \
${SUDO} dnf -y update
${SUDO} dnf -y install \
qemu-user-static \
podman
elif [ -e /sbin/apk ]; then
apk add \
${SUDO} apk add \
python3 \
qemu-user-static \
podman
elif [ -e /usr/bin/pacman ]; then
${SUDO} pacman -Sy --noconfirm qemu-user-static podman
fi
+4
View File
@@ -33,3 +33,7 @@ ubuntu:24.04 amd64
ubuntu:24.04 arm64
ubuntu:24.04 armhf
ubuntu:24.04 riscv64
ubuntu:25.04 amd64
ubuntu:25.04 arm64
ubuntu:25.04 armhf
ubuntu:25.04 riscv64
+4 -1
View File
@@ -6,8 +6,11 @@ export DH_VERBOSE=1
%:
dh $@
override_dh_auto_build:
$(MAKE) release
override_dh_auto_install:
$(MAKE) \
DESTDIR=$(CURDIR)/debian/mergerfs \
PREFIX=/usr \
install
install
-786
View File
@@ -1,786 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#ifndef _FUSE_H_
#define _FUSE_H_
#include "extern_c.h"
#include "fuse_common.h"
#include "fuse_kernel.h"
#include <fcntl.h>
#include <time.h>
#include <utime.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/uio.h>
#include <stdint.h>
EXTERN_C_BEGIN
/* ----------------------------------------------------------- *
* Basic FUSE API *
* ----------------------------------------------------------- */
/** Handle for a FUSE filesystem */
struct fuse;
/** Structure containing a raw command */
struct fuse_cmd;
struct fuse_dirents_t;
typedef struct fuse_dirents_t fuse_dirents_t;
/**
* The file system operations:
*
* Most of these should work very similarly to the well known UNIX
* file system operations. A major exception is that instead of
* returning an error in 'errno', the operation should return the
* negated error value (-errno) directly.
*
* All methods are optional, but some are essential for a useful
* filesystem (e.g. getattr). Open, flush, release, fsync, opendir,
* releasedir, fsyncdir, access, create, ftruncate, fgetattr, lock,
* init and destroy are special purpose methods, without which a full
* featured filesystem can still be implemented.
*
* Almost all operations take a path which can be of any length.
*
* Changed in fuse 2.8.0 (regardless of API version)
* Previously, paths were limited to a length of PATH_MAX.
*
* See http://fuse.sourceforge.net/wiki/ for more information. There
* is also a snapshot of the relevant wiki pages in the doc/ folder.
*/
struct fuse_operations
{
/** Get file attributes.
*
* Similar to stat(). The 'st_dev' and 'st_blksize' fields are
* ignored. The 'st_ino' field is ignored except if the 'use_ino'
* mount option is given.
*/
int (*getattr) (const char *, struct stat *, fuse_timeouts_t *);
/** Read the target of a symbolic link
*
* The buffer should be filled with a null terminated string. The
* buffer size argument includes the space for the terminating
* null character. If the linkname is too long to fit in the
* buffer, it should be truncated. The return value should be 0
* for success.
*/
int (*readlink) (const char *, char *, size_t);
/** Create a file node
*
* This is called for creation of all non-directory, non-symlink
* nodes. If the filesystem defines a create() method, then for
* regular files that will be called instead.
*/
int (*mknod) (const char *, mode_t, dev_t);
/** Create a directory
*
* Note that the mode argument may not have the type specification
* bits set, i.e. S_ISDIR(mode) can be false. To obtain the
* correct directory type bits use mode|S_IFDIR
* */
int (*mkdir) (const char *, mode_t);
/** Remove a file */
int (*unlink) (const char *);
/** Remove a directory */
int (*rmdir) (const char *);
/** Create a symbolic link */
int (*symlink) (const char *, const char *, struct stat *, fuse_timeouts_t *);
/** Rename a file */
int (*rename) (const char *, const char *);
/** Create a hard link to a file */
int (*link) (const char *, const char *, struct stat *, fuse_timeouts_t *);
/** Change the permission bits of a file */
int (*chmod) (const char *, mode_t);
int (*fchmod)(const uint64_t, const mode_t);
/** Change the owner and group of a file */
int (*chown) (const char *, uid_t, gid_t);
int (*fchown)(const uint64_t, const uid_t, const gid_t);
/** Change the size of a file */
int (*truncate) (const char *, off_t);
/** File open operation
*
* No creation (O_CREAT, O_EXCL) and by default also no
* truncation (O_TRUNC) flags will be passed to open(). If an
* application specifies O_TRUNC, fuse first calls truncate()
* and then open(). Only if 'atomic_o_trunc' has been
* specified and kernel version is 2.6.24 or later, O_TRUNC is
* passed on to open.
*
* Unless the 'default_permissions' mount option is given,
* open should check if the operation is permitted for the
* given flags. Optionally open may also return an arbitrary
* filehandle in the fuse_file_info structure, which will be
* passed to all file operations.
*
* Changed in version 2.2
*/
int (*open) (const char *, fuse_file_info_t *);
/** Get file system statistics
*
* The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored
*
* Replaced 'struct statfs' parameter with 'struct statvfs' in
* version 2.5
*/
int (*statfs) (const char *, struct statvfs *);
/** Possibly flush cached data
*
* BIG NOTE: This is not equivalent to fsync(). It's not a
* request to sync dirty data.
*
* Flush is called on each close() of a file descriptor. So if a
* filesystem wants to return write errors in close() and the file
* has cached dirty data, this is a good place to write back data
* and return any errors. Since many applications ignore close()
* errors this is not always useful.
*
* NOTE: The flush() method may be called more than once for each
* open(). This happens if more than one file descriptor refers
* to an opened file due to dup(), dup2() or fork() calls. It is
* not possible to determine if a flush is final, so each flush
* should be treated equally. Multiple write-flush sequences are
* relatively rare, so this shouldn't be a problem.
*
* Filesystems shouldn't assume that flush will always be called
* after some writes, or that if will be called at all.
*
* Changed in version 2.2
*/
int (*flush) (const fuse_file_info_t *);
/** Release an open file
*
* Release is called when there are no more references to an open
* file: all file descriptors are closed and all memory mappings
* are unmapped.
*
* For every open() call there will be exactly one release() call
* with the same flags and file descriptor. It is possible to
* have a file opened more than once, in which case only the last
* release will mean, that no more reads/writes will happen on the
* file. The return value of release is ignored.
*
* Changed in version 2.2
*/
int (*release) (const fuse_file_info_t *);
/** Synchronize file contents
*
* If the datasync parameter is non-zero, then only the user data
* should be flushed, not the meta data.
*
* Changed in version 2.2
*/
int (*fsync) (const uint64_t, int);
/** Set extended attributes */
int (*setxattr) (const char *, const char *, const char *, size_t, int);
/** Get extended attributes */
int (*getxattr) (const char *, const char *, char *, size_t);
/** List extended attributes */
int (*listxattr) (const char *, char *, size_t);
/** Remove extended attributes */
int (*removexattr) (const char *, const char *);
/** Open directory
*
* Unless the 'default_permissions' mount option is given,
* this method should check if opendir is permitted for this
* directory. Optionally opendir may also return an arbitrary
* filehandle in the fuse_file_info structure, which will be
* passed to readdir, closedir and fsyncdir.
*
* Introduced in version 2.3
*/
int (*opendir) (const char *,
fuse_file_info_t *);
/** Read directory
*
* This supersedes the old getdir() interface. New applications
* should use this.
*
* The filesystem may choose between two modes of operation:
*
* 1) The readdir implementation ignores the offset parameter, and
* passes zero to the filler function's offset. The filler
* function will not return '1' (unless an error happens), so the
* whole directory is read in a single readdir operation. This
* works just like the old getdir() method.
*
* 2) The readdir implementation keeps track of the offsets of the
* directory entries. It uses the offset parameter and always
* passes non-zero offset to the filler function. When the buffer
* is full (or an error happens) the filler function will return
* '1'.
*
* Introduced in version 2.3
*/
int (*readdir)(const fuse_file_info_t *,
fuse_dirents_t *);
int (*readdir_plus)(const fuse_file_info_t *,
fuse_dirents_t *);
/** Release directory
*
* Introduced in version 2.3
*/
int (*releasedir) (const fuse_file_info_t *);
/** Synchronize directory contents
*
* If the datasync parameter is non-zero, then only the user data
* should be flushed, not the meta data
*
* Introduced in version 2.3
*/
int (*fsyncdir) (const fuse_file_info_t *, int);
/**
* Initialize filesystem
*
* The return value will passed in the private_data field of
* fuse_context to all file operations and as a parameter to the
* destroy() method.
*
* Introduced in version 2.3
* Changed in version 2.6
*/
void *(*init) (struct fuse_conn_info *conn);
/**
* Clean up filesystem
*
* Called on filesystem exit.
*
* Introduced in version 2.3
*/
void (*destroy) (void);
/**
* Check file access permissions
*
* This will be called for the access() system call. If the
* 'default_permissions' mount option is given, this method is not
* called.
*
* This method is not called under Linux kernel versions 2.4.x
*
* Introduced in version 2.5
*/
int (*access) (const char *, int);
/**
* Create and open a file
*
* If the file does not exist, first create it with the specified
* mode, and then open it.
*
* If this method is not implemented or under Linux kernel
* versions earlier than 2.6.15, the mknod() and open() methods
* will be called instead.
*
* Introduced in version 2.5
*/
int (*create) (const char *, mode_t, fuse_file_info_t *);
/**
* Change the size of an open file
*
* This method is called instead of the truncate() method if the
* truncation was invoked from an ftruncate() system call.
*
* If this method is not implemented or under Linux kernel
* versions earlier than 2.6.15, the truncate() method will be
* called instead.
*
* Introduced in version 2.5
*/
int (*ftruncate) (const uint64_t, off_t);
/**
* Get attributes from an open file
*
* This method is called instead of the getattr() method if the
* file information is available.
*
* Currently this is only called after the create() method if that
* is implemented (see above). Later it may be called for
* invocations of fstat() too.
*
* Introduced in version 2.5
*/
int (*fgetattr) (const uint64_t, struct stat *, fuse_timeouts_t *);
/**
* Perform POSIX file locking operation
*
* The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.
*
* For the meaning of fields in 'struct flock' see the man page
* for fcntl(2). The l_whence field will always be set to
* SEEK_SET.
*
* For checking lock ownership, the 'fuse_file_info->owner'
* argument must be used.
*
* For F_GETLK operation, the library will first check currently
* held locks, and if a conflicting lock is found it will return
* information without calling this method. This ensures, that
* for local locks the l_pid field is correctly filled in. The
* results may not be accurate in case of race conditions and in
* the presence of hard links, but it's unlikely that an
* application would rely on accurate GETLK results in these
* cases. If a conflicting lock is not found, this method will be
* called, and the filesystem may fill out l_pid by a meaningful
* value, or it may leave this field zero.
*
* For F_SETLK and F_SETLKW the l_pid field will be set to the pid
* of the process performing the locking operation.
*
* Note: if this method is not implemented, the kernel will still
* allow file locking to work locally. Hence it is only
* interesting for network filesystems and similar.
*
* Introduced in version 2.6
*/
int (*lock) (const fuse_file_info_t *,
int cmd,
struct flock *);
/**
* Change the access and modification times of a file with
* nanosecond resolution
*
* This supersedes the old utime() interface. New applications
* should use this.
*
* See the utimensat(2) man page for details.
*
* Introduced in version 2.6
*/
int (*utimens)(const char *, const struct timespec tv[2]);
int (*futimens)(const uint64_t fh, const struct timespec tv[2]);
/**
* Map block index within file to block index within device
*
* Note: This makes sense only for block device backed filesystems
* mounted with the 'blkdev' option
*
* Introduced in version 2.6
*/
int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
/**
* Ioctl
*
* flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in
* 64bit environment. The size and direction of data is
* determined by _IOC_*() decoding of cmd. For _IOC_NONE,
* data will be NULL, for _IOC_WRITE data is out area, for
* _IOC_READ in area and if both are set in/out area. In all
* non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.
*
* If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a
* directory file handle.
*
* Introduced in version 2.8
*/
int (*ioctl) (const fuse_file_info_t *ffi,
unsigned long cmd,
void *arg,
unsigned int flags,
void *data,
uint32_t *out_bufsz);
/**
* Poll for IO readiness events
*
* Note: If ph is non-NULL, the client should notify
* when IO readiness events occur by calling
* fuse_notify_poll() with the specified ph.
*
* Regardless of the number of times poll with a non-NULL ph
* is received, single notification is enough to clear all.
* Notifying more times incurs overhead but doesn't harm
* correctness.
*
* The callee is responsible for destroying ph with
* fuse_pollhandle_destroy() when no longer in use.
*
* Introduced in version 2.8
*/
int (*poll) (const fuse_file_info_t *ffi,
fuse_pollhandle_t *ph,
unsigned *reventsp);
/** Write contents of buffer to an open file
*
* Similar to the write() method, but data is supplied in a
* generic buffer. Use fuse_buf_copy() to transfer data to
* the destination.
*
* Introduced in version 2.9
*/
int (*write) (const fuse_file_info_t *ffi,
const char *data,
size_t size,
off_t off);
/** Store data from an open file in a buffer
*
* Similar to the read() method, but data is stored and
* returned in a generic buffer.
*
* No actual copying of data has to take place, the source
* file descriptor may simply be stored in the buffer for
* later data transfer.
*
* The buffer must be allocated dynamically and stored at the
* location pointed to by bufp. If the buffer contains memory
* regions, they too must be allocated using malloc(). The
* allocated memory will be freed by the caller.
*
* Introduced in version 2.9
*/
int (*read)(const fuse_file_info_t *ffi,
char *buf,
size_t size,
off_t off);
/**
* Perform BSD file locking operation
*
* The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN
*
* Nonblocking requests will be indicated by ORing LOCK_NB to
* the above operations
*
* For more information see the flock(2) manual page.
*
* Additionally fi->owner will be set to a value unique to
* this open file. This same value will be supplied to
* ->release() when the file is released.
*
* Note: if this method is not implemented, the kernel will still
* allow file locking to work locally. Hence it is only
* interesting for network filesystems and similar.
*
* Introduced in version 2.9
*/
int (*flock) (const fuse_file_info_t *, int op);
/**
* Allocates space for an open file
*
* This function ensures that required space is allocated for specified
* file. If this function returns success then any subsequent write
* request to specified range is guaranteed not to fail because of lack
* of space on the file system media.
*
* Introduced in version 2.9.1
*/
int (*fallocate) (const uint64_t, int, off_t, off_t);
/**
* Copy a range of data from one file to another
*
* Performs an optimized copy between two file descriptors without
* the additional cost of transferring data through the FUSE kernel
* module to user space (glibc) and then back into the FUSE filesystem
* again.
*
* In case this method is not implemented, glibc falls back to
* reading data from the source and writing to the
* destination. Effectively doing an inefficient copy of the
* data.
*/
ssize_t (*copy_file_range)(const fuse_file_info_t *fi_in,
off_t offset_in,
const fuse_file_info_t *fi_out,
off_t offset_out,
size_t size,
int flags);
ssize_t (*setupmapping)(uint64_t *fh_,
uint64_t foffset_,
uint64_t len_,
uint64_t flags_,
uint64_t moffset_);
int (*removemapping)();
int (*syncfs)();
int (*tmpfile)(const char *, mode_t, fuse_file_info_t *);
int (*statx)(const char *fusepath,
const uint32_t flags,
const uint32_t mask,
struct fuse_statx *st,
fuse_timeouts_t *timeout);
int (*statx_fh)(const uint64_t fh,
const uint32_t flags,
const uint32_t mask,
struct fuse_statx *st,
fuse_timeouts_t *timeout);
};
/** Extra context that may be needed by some filesystems
*
* The uid, gid and pid fields are not filled in case of a writepage
* operation.
*/
struct fuse_context
{
uint64_t unique;
uint64_t nodeid;
uint32_t opcode;
uid_t uid;
gid_t gid;
pid_t pid;
mode_t umask;
struct fuse *fuse;
};
/**
* Main function of FUSE.
*
* This is for the lazy. This is all that has to be called from the
* main() function.
*
* This function does the following:
* - parses command line options (-d -s and -h)
* - passes relevant mount options to the fuse_mount()
* - installs signal handlers for INT, HUP, TERM and PIPE
* - registers an exit handler to unmount the filesystem on program exit
* - creates a fuse handle
* - registers the operations
* - calls either the single-threaded or the multi-threaded event loop
*
* Note: this is currently implemented as a macro.
*
* @param argc the argument counter passed to the main() function
* @param argv the argument vector passed to the main() function
* @param op the file system operation
* @return 0 on success, nonzero on failure
*/
/*
int fuse_main(int argc, char *argv[], const struct fuse_operations *op);
*/
#define fuse_main(argc, argv, op) \
fuse_main_real(argc, argv, op, sizeof(*(op)))
/* ----------------------------------------------------------- *
* More detailed API *
* ----------------------------------------------------------- */
/**
* Create a new FUSE filesystem.
*
* @param ch the communication channel
* @param args argument vector
* @param op the filesystem operations
* @param op_size the size of the fuse_operations structure
* @return the created FUSE handle
*/
struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args,
const struct fuse_operations *op, size_t op_size);
/**
* Destroy the FUSE handle.
*
* The communication channel attached to the handle is also destroyed.
*
* NOTE: This function does not unmount the filesystem. If this is
* needed, call fuse_unmount() before calling this function.
*
* @param f the FUSE handle
*/
void fuse_destroy(struct fuse *f);
/**
* Exit from event loop
*
* @param f the FUSE handle
*/
void fuse_exit(struct fuse *f);
int fuse_config_read_thread_count(const struct fuse *f);
int fuse_config_process_thread_count(const struct fuse *f);
int fuse_config_process_thread_queue_depth(const struct fuse *f);
const char* fuse_config_pin_threads(const struct fuse *f);
/**
* FUSE event loop with multiple threads
*
* Requests from the kernel are processed, and the appropriate
* operations are called. Request are processed in parallel by
* distributing them between multiple threads.
*
* Calling this function requires the pthreads library to be linked to
* the application.
*
* @param f the FUSE handle
* @return 0 if no error occurred, -1 otherwise
*/
int fuse_loop_mt(struct fuse *f);
/**
* Get the current context
*
* The context is only valid for the duration of a filesystem
* operation, and thus must not be stored and used later.
*
* @return the context
*/
struct fuse_context *fuse_get_context(void);
/**
* Check if the current request has already been interrupted
*
* @return 1 if the request has been interrupted, 0 otherwise
*/
int fuse_interrupted(void);
/**
* Obsolete, doesn't do anything
*
* @return -EINVAL
*/
int fuse_invalidate(struct fuse *f, const char *path);
/* Deprecated, don't use */
int fuse_is_lib_option(const char *opt);
/**
* The real main function
*
* Do not call this directly, use fuse_main()
*/
int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size);
void fuse_populate_maintenance_thread(struct fuse *fuse);
int fuse_log_metrics_get(void);
void fuse_log_metrics_set(int enabled);
/**
* Iterate over cache removing stale entries
* use in conjunction with "-oremember"
*
* NOTE: This is already done for the standard sessions
*
* @param fuse struct fuse pointer for fuse instance
* @return the number of seconds until the next cleanup
*/
int fuse_clean_cache(struct fuse *fuse);
/*
* Stacking API
*/
/**
* Fuse filesystem object
*
* This is opaque object represents a filesystem layer
*/
struct fuse_fs;
/*
* These functions call the relevant filesystem operation, and return
* the result.
*
* If the operation is not defined, they return -ENOSYS, with the
* exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
* fuse_fs_releasedir and fuse_fs_statfs, which return 0.
*/
int fuse_notify_poll(fuse_pollhandle_t *ph);
/**
* Create a new fuse filesystem object
*
* This is usually called from the factory of a fuse module to create
* a new instance of a filesystem.
*
* @param op the filesystem operations
* @param op_size the size of the fuse_operations structure
* @return a new filesystem object
*/
struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size);
/* ----------------------------------------------------------- *
* Advanced API for event handling, don't worry about this... *
* ----------------------------------------------------------- */
/* NOTE: the following functions are deprecated, and will be removed
from the 3.0 API. Use the lowlevel session functions instead */
/** Function type used to process commands */
typedef void (*fuse_processor_t)(struct fuse *, struct fuse_cmd *, void *);
/** This is the part of fuse_main() before the event loop */
struct fuse *fuse_setup(int argc, char *argv[],
const struct fuse_operations *op, size_t op_size,
char **mountpoint);
/** This is the part of fuse_main() after the event loop */
void fuse_teardown(struct fuse *fuse, char *mountpoint);
/** Multi threaded event loop, which calls the custom command
processor function */
int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data);
/** Return the exited flag, which indicates if fuse_exit() has been
called */
int fuse_exited(struct fuse *f);
/** This function is obsolete and implemented as a no-op */
void fuse_set_getcontext_func(struct fuse_context *(*func)(void));
/** Get session from fuse object */
struct fuse_session *fuse_get_session(struct fuse *f);
void fuse_gc1();
void fuse_gc();
void fuse_invalidate_all_nodes();
int fuse_get_dev_fuse_fd(const struct fuse_context *fc);
int fuse_passthrough_open(const struct fuse_context *fc,
const int fd);
int fuse_passthrough_close(const struct fuse_context *fc,
const int backing_id);
EXTERN_C_END
#endif /* _FUSE_H_ */
-31
View File
@@ -1,31 +0,0 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <string>
int fuse_config_get_read_thread_count();
int fuse_config_get_process_thread_count();
int fuse_config_get_process_thread_queue_depth();
std::string fuse_config_get_pin_threads();
void fuse_config_set_read_thread_count(int const);
void fuse_config_set_process_thread_count(int const);
void fuse_config_set_process_thread_queue_depth(int const);
void fuse_config_set_pin_threads(std::string const);
-80
View File
@@ -1,80 +0,0 @@
/*
ISC License
Copyright (c) 2019, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "extern_c.h"
EXTERN_C_BEGIN
#include "kvec.h"
#include "fuse_dirent.h"
#include "fuse_direntplus.h"
#include "fuse_entry.h"
#include "linux_dirent64.h"
#include <dirent.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
enum fuse_dirents_type_e
{
UNSET = 0,
NORMAL,
PLUS
};
typedef enum fuse_dirents_type_e fuse_dirents_type_t;
typedef struct fuse_dirents_t fuse_dirents_t;
struct fuse_dirents_t
{
kvec_t(char) data;
kvec_t(uint32_t) offs;
fuse_dirents_type_t type;
};
int fuse_dirents_init(fuse_dirents_t *d);
void fuse_dirents_free(fuse_dirents_t *d);
void fuse_dirents_reset(fuse_dirents_t *d);
int fuse_dirents_add(fuse_dirents_t *d,
const struct dirent *de,
const uint64_t namelen);
int fuse_dirents_add_plus(fuse_dirents_t *d,
const struct dirent *de,
const uint64_t namelen,
const fuse_entry_t *entry,
const struct stat *st);
int fuse_dirents_add_linux(fuse_dirents_t *d,
const linux_dirent64_t *de,
const uint64_t namelen);
int fuse_dirents_add_linux_plus(fuse_dirents_t *d,
const linux_dirent64_t *de,
const uint64_t namelen,
const fuse_entry_t *entry,
const struct stat *st);
void *fuse_dirents_find(fuse_dirents_t *d,
const uint64_t ino);
int fuse_dirents_convert_plus2normal(fuse_dirents_t *d);
EXTERN_C_END
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <stdint.h>
#include <string.h>
#define DIRENT_NAMELEN(X) (strlen((X)->name))
typedef struct linux_dirent64_t linux_dirent64_t;
struct linux_dirent64_t
{
uint64_t ino;
int64_t off;
uint16_t reclen;
uint8_t type;
char name[];
};
-7
View File
@@ -1,7 +0,0 @@
#pragma once
#ifdef DEBUG
#include "mutex_debug.hpp"
#else
#include "mutex_ndebug.hpp"
#endif
-389
View File
@@ -1,389 +0,0 @@
#pragma once
#include "moodycamel/blockingconcurrentqueue.h"
#include "moodycamel/lightweightsemaphore.h"
#include "invocable.h"
#include <algorithm>
#include <atomic>
#include <csignal>
#include <cstring>
#include <future>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#include <utility>
#include <pthread.h>
#include <syslog.h>
#define SEM
//#undef SEM
#ifdef SEM
#define SEMA_WAIT(S) (S.wait())
#define SEMA_SIGNAL(S) (S.signal())
#else
#define SEMA_WAIT(S)
#define SEMA_SIGNAL(S)
#endif
struct ThreadPoolTraits : public moodycamel::ConcurrentQueueDefaultTraits
{
static const int MAX_SEMA_SPINS = 1;
};
class ThreadPool
{
public:
using PToken = moodycamel::ProducerToken;
using CToken = moodycamel::ConsumerToken;
private:
using Func = ofats::any_invocable<void()>;
using Queue = moodycamel::BlockingConcurrentQueue<Func,ThreadPoolTraits>;
public:
explicit
ThreadPool(const unsigned thread_count_ = std::thread::hardware_concurrency(),
const unsigned max_queue_depth_ = std::thread::hardware_concurrency(),
std::string const name_ = {});
~ThreadPool();
private:
static void *start_routine(void *arg_);
public:
int add_thread(std::string const name = {});
int remove_thread(void);
int set_threads(const std::size_t count);
void shutdown(void);
public:
template<typename FuncType>
void
enqueue_work(ThreadPool::PToken &ptok_,
FuncType &&func_);
template<typename FuncType>
void
enqueue_work(FuncType &&func_);
template<typename FuncType>
[[nodiscard]]
std::future<typename std::result_of<FuncType()>::type>
enqueue_task(FuncType&& func_);
public:
std::vector<pthread_t> threads() const;
ThreadPool::PToken ptoken();
private:
Queue _queue;
moodycamel::LightweightSemaphore _sema;
private:
std::string const _name;
std::vector<pthread_t> _threads;
mutable std::mutex _threads_mutex;
};
inline
ThreadPool::ThreadPool(const unsigned thread_count_,
const unsigned max_queue_depth_,
const std::string name_)
: _queue(),
_sema(max_queue_depth_),
_name(name_)
{
sigset_t oldset;
sigset_t newset;
sigfillset(&newset);
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
_threads.reserve(thread_count_);
for(std::size_t i = 0; i < thread_count_; ++i)
{
int rv;
pthread_t t;
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
if(rv != 0)
{
syslog(LOG_WARNING,
"threadpool (%s): error spawning thread - %d (%s)",
_name.c_str(),
rv,
strerror(rv));
continue;
}
if(!_name.empty())
pthread_setname_np(t,_name.c_str());
_threads.push_back(t);
}
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
if(_threads.empty())
throw std::runtime_error("threadpool: failed to spawn any threads");
syslog(LOG_DEBUG,
"threadpool (%s): spawned %zu threads w/ max queue depth %u",
_name.c_str(),
_threads.size(),
max_queue_depth_);
}
inline
ThreadPool::~ThreadPool()
{
syslog(LOG_DEBUG,
"threadpool (%s): destroying %zu threads",
_name.c_str(),
_threads.size());
for(auto t : _threads)
pthread_cancel(t);
for(auto t : _threads)
pthread_join(t,NULL);
}
inline
void*
ThreadPool::start_routine(void *arg_)
{
ThreadPool *btp = static_cast<ThreadPool*>(arg_);
bool done;
ThreadPool::Func func;
ThreadPool::Queue &q = btp->_queue;
moodycamel::LightweightSemaphore &sema = btp->_sema;
ThreadPool::CToken ctok(btp->_queue);
done = false;
while(!done)
{
q.wait_dequeue(ctok,func);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
try
{
func();
}
catch(std::exception &e)
{
done = true;
}
SEMA_SIGNAL(sema);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
}
return nullptr;
}
inline
int
ThreadPool::add_thread(const std::string name_)
{
int rv;
pthread_t t;
sigset_t oldset;
sigset_t newset;
std::string name;
name = (name_.empty() ? _name : name_);
sigfillset(&newset);
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
if(rv != 0)
{
syslog(LOG_WARNING,
"threadpool (%s): error spawning thread - %d (%s)",
_name.c_str(),
rv,
strerror(rv));
return -rv;
}
if(!name.empty())
pthread_setname_np(t,name.c_str());
{
std::lock_guard<std::mutex> lg(_threads_mutex);
_threads.push_back(t);
}
return 0;
}
inline
int
ThreadPool::remove_thread(void)
{
{
std::lock_guard<std::mutex> lg(_threads_mutex);
if(_threads.size() <= 1)
return -EINVAL;
}
std::promise<pthread_t> promise;
auto func =
[this,&promise]()
{
pthread_t t;
t = pthread_self();
promise.set_value(t);
{
std::lock_guard<std::mutex> lg(_threads_mutex);
for(auto i = _threads.begin(); i != _threads.end(); ++i)
{
if(*i != t)
continue;
_threads.erase(i);
break;
}
}
pthread_exit(NULL);
};
enqueue_work(std::move(func));
pthread_join(promise.get_future().get(),NULL);
return 0;
}
inline
int
ThreadPool::set_threads(std::size_t const count_)
{
int diff;
{
std::lock_guard<std::mutex> lg(_threads_mutex);
diff = ((int)count_ - (int)_threads.size());
}
for(auto i = diff; i > 0; --i)
add_thread();
for(auto i = diff; i < 0; ++i)
remove_thread();
return diff;
}
inline
void
ThreadPool::shutdown(void)
{
std::lock_guard<std::mutex> lg(_threads_mutex);
for(pthread_t tid : _threads)
pthread_cancel(tid);
for(pthread_t tid : _threads)
pthread_join(tid,NULL);
_threads.clear();
}
template<typename FuncType>
inline
void
ThreadPool::enqueue_work(ThreadPool::PToken &ptok_,
FuncType &&func_)
{
SEMA_WAIT(_sema);
_queue.enqueue(ptok_,
std::forward<FuncType>(func_));
}
template<typename FuncType>
inline
void
ThreadPool::enqueue_work(FuncType &&func_)
{
SEMA_WAIT(_sema);
_queue.enqueue(std::forward<FuncType>(func_));
}
template<typename FuncType>
[[nodiscard]]
inline
std::future<typename std::result_of<FuncType()>::type>
ThreadPool::enqueue_task(FuncType&& func_)
{
using TaskReturnType = typename std::result_of<FuncType()>::type;
using Promise = std::promise<TaskReturnType>;
Promise promise;
auto future = promise.get_future();
auto work =
[promise_ = std::move(promise),
func_ = std::forward<FuncType>(func_)]() mutable
{
try
{
auto rv = func_();
promise_.set_value(std::move(rv));
}
catch(...)
{
promise_.set_exception(std::current_exception());
}
};
SEMA_WAIT(_sema);
_queue.enqueue(std::move(work));
return future;
}
inline
std::vector<pthread_t>
ThreadPool::threads() const
{
std::lock_guard<std::mutex> lg(_threads_mutex);
return _threads;
}
inline
ThreadPool::PToken
ThreadPool::ptoken()
{
return ThreadPool::PToken(_queue);
}
-316
View File
@@ -1,316 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB
*/
#define _GNU_SOURCE
#include "fuse_i.h"
#include "fuse_lowlevel.h"
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
{
size_t i;
size_t size = 0;
for (i = 0; i < bufv->count; i++) {
if (bufv->buf[i].size == SIZE_MAX)
size = SIZE_MAX;
else
size += bufv->buf[i].size;
}
return size;
}
static size_t min_size(size_t s1, size_t s2)
{
return s1 < s2 ? s1 : s2;
}
static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
const struct fuse_buf *src, size_t src_off,
size_t len)
{
ssize_t res = 0;
size_t copied = 0;
while (len) {
if (dst->flags & FUSE_BUF_FD_SEEK) {
res = pwrite(dst->fd, src->mem + src_off, len,
dst->pos + dst_off);
} else {
res = write(dst->fd, src->mem + src_off, len);
}
if (res == -1) {
if (!copied)
return -errno;
break;
}
if (res == 0)
break;
copied += res;
if (!(dst->flags & FUSE_BUF_FD_RETRY))
break;
src_off += res;
dst_off += res;
len -= res;
}
return copied;
}
static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
const struct fuse_buf *src, size_t src_off,
size_t len)
{
ssize_t res = 0;
size_t copied = 0;
while (len) {
if (src->flags & FUSE_BUF_FD_SEEK) {
res = pread(src->fd, dst->mem + dst_off, len,
src->pos + src_off);
} else {
res = read(src->fd, dst->mem + dst_off, len);
}
if (res == -1) {
if (!copied)
return -errno;
break;
}
if (res == 0)
break;
copied += res;
if (!(src->flags & FUSE_BUF_FD_RETRY))
break;
dst_off += res;
src_off += res;
len -= res;
}
return copied;
}
static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
const struct fuse_buf *src, size_t src_off,
size_t len)
{
char buf[4096];
struct fuse_buf tmp = {
.size = sizeof(buf),
.flags = 0,
};
ssize_t res;
size_t copied = 0;
tmp.mem = buf;
while (len) {
size_t this_len = min_size(tmp.size, len);
size_t read_len;
res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
if (res < 0) {
if (!copied)
return res;
break;
}
if (res == 0)
break;
read_len = res;
res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
if (res < 0) {
if (!copied)
return res;
break;
}
if (res == 0)
break;
copied += res;
if (res < this_len)
break;
dst_off += res;
src_off += res;
len -= res;
}
return copied;
}
#ifdef HAVE_SPLICE
static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
const struct fuse_buf *src, size_t src_off,
size_t len, enum fuse_buf_copy_flags flags)
{
int splice_flags = 0;
off_t *srcpos = NULL;
off_t *dstpos = NULL;
off_t srcpos_val;
off_t dstpos_val;
ssize_t res;
size_t copied = 0;
if (flags & FUSE_BUF_SPLICE_MOVE)
splice_flags |= SPLICE_F_MOVE;
if (flags & FUSE_BUF_SPLICE_NONBLOCK)
splice_flags |= SPLICE_F_NONBLOCK;
if (src->flags & FUSE_BUF_FD_SEEK) {
srcpos_val = src->pos + src_off;
srcpos = &srcpos_val;
}
if (dst->flags & FUSE_BUF_FD_SEEK) {
dstpos_val = dst->pos + dst_off;
dstpos = &dstpos_val;
}
while (len) {
res = splice(src->fd, srcpos, dst->fd, dstpos, len, splice_flags);
if (res == -1) {
if (copied)
break;
if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
return -errno;
/* Maybe splice is not supported for this combination */
return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
len);
}
if (res == 0)
break;
copied += res;
if (!(src->flags & FUSE_BUF_FD_RETRY) &&
!(dst->flags & FUSE_BUF_FD_RETRY)) {
break;
}
len -= res;
}
return copied;
}
#else
static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
const struct fuse_buf *src, size_t src_off,
size_t len, enum fuse_buf_copy_flags flags)
{
(void) flags;
return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
}
#endif
static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
const struct fuse_buf *src, size_t src_off,
size_t len, enum fuse_buf_copy_flags flags)
{
int src_is_fd = src->flags & FUSE_BUF_IS_FD;
int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
if (!src_is_fd && !dst_is_fd) {
char *dstmem = dst->mem + dst_off;
char *srcmem = src->mem + src_off;
if (dstmem != srcmem) {
if (dstmem + len <= srcmem || srcmem + len <= dstmem)
memcpy(dstmem, srcmem, len);
else
memmove(dstmem, srcmem, len);
}
return len;
} else if (!src_is_fd) {
return fuse_buf_write(dst, dst_off, src, src_off, len);
} else if (!dst_is_fd) {
return fuse_buf_read(dst, dst_off, src, src_off, len);
} else if (flags & FUSE_BUF_NO_SPLICE) {
return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
} else {
return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
}
}
static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
{
if (bufv->idx < bufv->count)
return &bufv->buf[bufv->idx];
else
return NULL;
}
static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
{
const struct fuse_buf *buf = fuse_bufvec_current(bufv);
bufv->off += len;
assert(bufv->off <= buf->size);
if (bufv->off == buf->size) {
assert(bufv->idx < bufv->count);
bufv->idx++;
if (bufv->idx == bufv->count)
return 0;
bufv->off = 0;
}
return 1;
}
ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
enum fuse_buf_copy_flags flags)
{
size_t copied = 0;
if (dstv == srcv)
return fuse_buf_size(dstv);
for (;;) {
const struct fuse_buf *src = fuse_bufvec_current(srcv);
const struct fuse_buf *dst = fuse_bufvec_current(dstv);
size_t src_len;
size_t dst_len;
size_t len;
ssize_t res;
if (src == NULL || dst == NULL)
break;
src_len = src->size - srcv->off;
dst_len = dst->size - dstv->off;
len = min_size(src_len, dst_len);
res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
if (res < 0) {
if (!copied)
return res;
break;
}
copied += res;
if (!fuse_bufvec_advance(srcv, res) ||
!fuse_bufvec_advance(dstv, res))
break;
if (res < len)
break;
}
return copied;
}
File diff suppressed because it is too large Load Diff
-380
View File
@@ -1,380 +0,0 @@
/*
ISC License
Copyright (c) 2021, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "kvec.h"
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#define ROUND_UP(N,S) ((((N) + (S) - 1) / (S)) * (S))
typedef kvec_t(void*) slab_kvec_t;
typedef struct mem_stack_t mem_stack_t;
struct mem_stack_t
{
mem_stack_t *next;
};
typedef struct fmp_t fmp_t;
struct fmp_t
{
mem_stack_t *objs;
slab_kvec_t slabs;
uint64_t avail_objs;
uint64_t obj_size;
uint64_t page_size;
uint64_t slab_size;
};
static
inline
uint64_t
fmp_page_size()
{
return sysconf(_SC_PAGESIZE);
}
static
inline
void
fmp_init(fmp_t *fmp_,
const uint64_t obj_size_,
const uint64_t page_multiple_)
{
kv_init(fmp_->slabs);
fmp_->objs = NULL;
fmp_->avail_objs = 0;
fmp_->obj_size = ROUND_UP(obj_size_,sizeof(void*));
fmp_->page_size = fmp_page_size();
fmp_->slab_size = (fmp_->page_size * page_multiple_);
}
static
inline
uint64_t
fmp_slab_count(fmp_t *fmp_)
{
return kv_size(fmp_->slabs);
}
static
inline
void*
fmp_slab_alloc_posix_memalign(fmp_t *fmp_)
{
int rv;
void *mem;
const size_t alignment = fmp_->page_size;
const size_t size = fmp_->slab_size;
rv = posix_memalign(&mem,alignment,size);
if(rv != 0)
return NULL;
return NULL;
}
static
inline
void*
fmp_slab_alloc_mmap(fmp_t *fmp_)
{
void *mem;
void *address = NULL;
const size_t length = fmp_->slab_size;
const int protect = PROT_READ|PROT_WRITE;
const int flags = MAP_PRIVATE|MAP_ANONYMOUS;
const int filedes = -1;
const off_t offset = 0;
mem = mmap(address,length,protect,flags,filedes,offset);
if(mem == MAP_FAILED)
return NULL;
return mem;
}
static
inline
void
fmp_slab_free_posix_memalign(fmp_t* fmp_,
void *mem_)
{
(void)fmp_;
free(mem_);
}
static
inline
void
fmp_slab_free_mmap(fmp_t* fmp_,
void *mem_)
{
void *addr = mem_;
size_t length = fmp_->slab_size;
(void)munmap(addr,length);
}
static
inline
int
fmp_slab_alloc(fmp_t *fmp_)
{
char *i;
void *mem;
mem = fmp_slab_alloc_mmap(fmp_);
if(mem == NULL)
return -ENOMEM;
kv_push(void*,fmp_->slabs,mem);
i = ((char*)mem + fmp_->slab_size - fmp_->obj_size);
while(i >= (char*)mem)
{
mem_stack_t *obj = (mem_stack_t*)i;
obj->next = fmp_->objs;
fmp_->objs = obj;
fmp_->avail_objs++;
i -= fmp_->obj_size;
}
return 0;
}
static
inline
void*
fmp_alloc(fmp_t *fmp_)
{
void *rv;
if(fmp_->objs == NULL)
fmp_slab_alloc(fmp_);
if(fmp_->objs == NULL)
return NULL;
rv = fmp_->objs;
fmp_->objs = fmp_->objs->next;
fmp_->avail_objs--;
return rv;
}
static
inline
void*
fmp_calloc(fmp_t *fmp_)
{
void *obj;
obj = fmp_alloc(fmp_);
if(obj == NULL)
return NULL;
memset(obj,0,fmp_->obj_size);
return obj;
}
static
inline
void
fmp_free(fmp_t *fmp_,
void *obj_)
{
mem_stack_t *obj = (mem_stack_t*)obj_;
obj->next = fmp_->objs;
fmp_->objs = obj;
fmp_->avail_objs++;
}
static
inline
void
fmp_clear(fmp_t *fmp_)
{
while(kv_size(fmp_->slabs))
{
void *slab = kv_pop(fmp_->slabs);
fmp_slab_free_mmap(fmp_,slab);
}
fmp_->objs = NULL;
fmp_->avail_objs = 0;
}
static
inline
void
fmp_destroy(fmp_t *fmp_)
{
fmp_clear(fmp_);
kv_destroy(fmp_->slabs);
}
static
inline
uint64_t
fmp_avail_objs(fmp_t *fmp_)
{
return fmp_->avail_objs;
}
static
inline
uint64_t
fmp_objs_per_slab(fmp_t *fmp_)
{
return (fmp_->slab_size / fmp_->obj_size);
}
static
inline
uint64_t
fmp_objs_in_slab(fmp_t *fmp_,
void *slab_)
{
char *slab;
uint64_t objs_per_slab;
uint64_t objs_in_slab;
slab = (char*)slab_;
objs_in_slab = 0;
objs_per_slab = fmp_objs_per_slab(fmp_);
for(mem_stack_t *stack = fmp_->objs; stack != NULL; stack = stack->next)
{
char *obj = (char*)stack;
if((obj >= slab) && (obj < (slab + fmp_->slab_size)))
objs_in_slab++;
if(objs_in_slab >= objs_per_slab)
break;
}
return objs_in_slab;
}
static
inline
void
fmp_remove_objs_in_slab(fmp_t *fmp_,
void *slab_)
{
char *slab;
uint64_t objs_per_slab;
uint64_t objs_in_slab;
mem_stack_t **p;
p = &fmp_->objs;
slab = (char*)slab_;
objs_in_slab = 0;
objs_per_slab = fmp_objs_per_slab(fmp_);
while((*p) != NULL)
{
char *obj = (char*)*p;
if((obj >= slab) && (obj < (slab + fmp_->slab_size)))
{
objs_in_slab++;
*p = (*p)->next;
fmp_->avail_objs--;
if(objs_in_slab >= objs_per_slab)
break;
continue;
}
p = &(*p)->next;
}
}
static
inline
int
fmp_gc_slab(fmp_t *fmp_,
uint64_t slab_idx_)
{
char *slab;
uint64_t objs_in_slab;
uint64_t objs_per_slab;
slab_idx_ = (slab_idx_ % kv_size(fmp_->slabs));
slab = (char*)kv_A(fmp_->slabs,slab_idx_);
objs_per_slab = fmp_objs_per_slab(fmp_);
objs_in_slab = fmp_objs_in_slab(fmp_,slab);
if(objs_in_slab != objs_per_slab)
return 0;
fmp_remove_objs_in_slab(fmp_,slab);
kv_delete(fmp_->slabs,slab_idx_);
fmp_slab_free_mmap(fmp_,slab);
return 1;
}
static
inline
int
fmp_gc(fmp_t *fmp_)
{
uint64_t slab_idx;
slab_idx = rand();
return fmp_gc_slab(fmp_,slab_idx);
}
static
inline
double
fmp_slab_usage_ratio(fmp_t *fmp_)
{
double avail_objs;
double objs_per_slab;
double nums_of_slabs;
avail_objs = fmp_->avail_objs;
objs_per_slab = fmp_objs_per_slab(fmp_);
nums_of_slabs = kv_size(fmp_->slabs);
return (avail_objs / (objs_per_slab * nums_of_slabs));
}
static
inline
uint64_t
fmp_total_allocated_memory(fmp_t *fmp_)
{
return (fmp_->slab_size * kv_size(fmp_->slabs));
}
-4233
View File
File diff suppressed because it is too large Load Diff
-73
View File
@@ -1,73 +0,0 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <string>
static int g_READ_THREAD_COUNT = -1;
static int g_PROCESS_THREAD_COUNT = -1;
static int g_PROCESS_THREAD_QUEUE_DEPTH = -1;
static std::string g_PIN_THREADS = {};
int
fuse_config_get_read_thread_count()
{
return g_READ_THREAD_COUNT;
}
void
fuse_config_set_read_thread_count(int const v_)
{
g_READ_THREAD_COUNT = v_;
}
int
fuse_config_get_process_thread_count()
{
return g_PROCESS_THREAD_COUNT;
}
void
fuse_config_set_process_thread_count(int const v_)
{
g_PROCESS_THREAD_COUNT = v_;
}
int
fuse_config_get_process_thread_queue_depth()
{
return g_PROCESS_THREAD_QUEUE_DEPTH;
}
void
fuse_config_set_process_thread_queue_depth(int const v_)
{
g_PROCESS_THREAD_QUEUE_DEPTH = v_;
}
std::string
fuse_config_get_pin_threads()
{
return g_PIN_THREADS;
}
void
fuse_config_set_pin_threads(std::string const v_)
{
g_PIN_THREADS = v_;
}
-406
View File
@@ -1,406 +0,0 @@
#define _FILE_OFFSET_BITS 64
#include "fuse_attr.h"
#include "fuse_dirent.h"
#include "fuse_direntplus.h"
#include "fuse_dirents.h"
#include "fuse_entry.h"
#include "linux_dirent64.h"
#include "stat_utils.h"
#include <dirent.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 32KB - same as glibc getdents buffer size */
#define DEFAULT_SIZE (1024 * 32)
static
uint64_t
round_up(const uint64_t number_,
const uint64_t multiple_)
{
return (((number_ + multiple_ - 1) / multiple_) * multiple_);
}
static
uint64_t
align_uint64_t(uint64_t v_)
{
return ((v_ + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1));
}
static
uint64_t
fuse_dirent_size(const uint64_t namelen_)
{
uint64_t rv;
rv = offsetof(fuse_dirent_t,name);
rv += namelen_;
rv = align_uint64_t(rv);
return rv;
}
static
uint64_t
fuse_direntplus_size(const uint64_t namelen_)
{
uint64_t rv;
rv = offsetof(fuse_direntplus_t,dirent.name);
rv += namelen_;
rv = align_uint64_t(rv);
return rv;
}
static
int
fuse_dirents_buf_resize(fuse_dirents_t *d_,
uint64_t size_)
{
if((kv_size(d_->data) + size_) >= kv_max(d_->data))
{
uint64_t new_size;
new_size = round_up((kv_size(d_->data) + size_),DEFAULT_SIZE);
kv_resize(char,d_->data,new_size);
if(d_->data.a == NULL)
return -ENOMEM;
}
return 0;
}
static
void*
fuse_dirents_dirent_alloc(fuse_dirents_t *d_,
uint64_t namelen_)
{
int rv;
uint64_t size;
fuse_dirent_t *d;
size = fuse_dirent_size(namelen_);
rv = fuse_dirents_buf_resize(d_,size);
if(rv)
return NULL;
d = (fuse_dirent_t*)&kv_end(d_->data);
kv_size(d_->data) += size;
return d;
}
static
void*
fuse_dirents_direntplus_alloc(fuse_dirents_t *d_,
uint64_t namelen_)
{
int rv;
uint64_t size;
fuse_dirent_t *d;
size = fuse_direntplus_size(namelen_);
rv = fuse_dirents_buf_resize(d_,size);
if(rv)
return NULL;
d = (fuse_dirent_t*)&kv_end(d_->data);
kv_size(d_->data) += size;
return d;
}
static
void
fuse_dirents_fill_attr(fuse_attr_t *attr_,
const struct stat *st_)
{
attr_->ino = st_->st_ino;
attr_->size = st_->st_size;
attr_->blocks = st_->st_blocks;
attr_->atime = st_->st_atime;
attr_->mtime = st_->st_mtime;
attr_->ctime = st_->st_ctime;
attr_->atimensec = ST_ATIM_NSEC(st_);
attr_->mtimensec = ST_MTIM_NSEC(st_);
attr_->ctimensec = ST_CTIM_NSEC(st_);
attr_->mode = st_->st_mode;
attr_->nlink = st_->st_nlink;
attr_->uid = st_->st_uid;
attr_->gid = st_->st_gid;
attr_->rdev = st_->st_rdev;
attr_->blksize = st_->st_blksize;
}
fuse_dirent_t*
fuse_dirent_next(fuse_dirent_t *cur_)
{
char *buf;
buf = (char*)cur_;
buf += fuse_dirent_size(cur_->namelen);
return (fuse_dirent_t*)buf;
}
fuse_direntplus_t*
fuse_direntplus_next(fuse_direntplus_t *cur_)
{
char *buf;
buf = (char*)cur_;
buf += fuse_direntplus_size(cur_->dirent.namelen);
return (fuse_direntplus_t*)buf;
}
fuse_dirent_t*
fuse_dirent_find(fuse_dirents_t *d_,
const uint64_t ino_)
{
fuse_dirent_t *cur;
fuse_dirent_t *end;
if(d_->type != NORMAL)
return NULL;
cur = (fuse_dirent_t*)&kv_first(d_->data);
end = (fuse_dirent_t*)&kv_end(d_->data);
while(cur < end)
{
if(cur->ino == ino_)
return cur;
cur = fuse_dirent_next(cur);
}
return NULL;
}
fuse_direntplus_t*
fuse_direntplus_find(fuse_dirents_t *d_,
const uint64_t ino_)
{
fuse_direntplus_t *cur;
fuse_direntplus_t *end;
if(d_->type != PLUS)
return NULL;
cur = (fuse_direntplus_t*)&kv_first(d_->data);
end = (fuse_direntplus_t*)&kv_end(d_->data);
while(cur < end)
{
if(cur->dirent.ino == ino_)
return cur;
cur = fuse_direntplus_next(cur);
}
return NULL;
}
void*
fuse_dirents_find(fuse_dirents_t *d_,
const uint64_t ino_)
{
switch(d_->type)
{
default:
case UNSET:
return NULL;
case NORMAL:
return fuse_dirent_find(d_,ino_);
case PLUS:
return fuse_direntplus_find(d_,ino_);
}
}
int
fuse_dirents_convert_plus2normal(fuse_dirents_t *d_)
{
return -ENOSYS;
}
int
fuse_dirents_add(fuse_dirents_t *d_,
const struct dirent *dirent_,
const uint64_t namelen_)
{
fuse_dirent_t *d;
switch(d_->type)
{
case UNSET:
d_->type = NORMAL;
break;
case NORMAL:
break;
case PLUS:
return -EINVAL;
}
d = fuse_dirents_dirent_alloc(d_,namelen_);
if(d == NULL)
return -ENOMEM;
d->off = kv_size(d_->offs);
kv_push(uint32_t,d_->offs,kv_size(d_->data));
d->ino = dirent_->d_ino;
d->namelen = namelen_;
d->type = dirent_->d_type;
memcpy(d->name,dirent_->d_name,namelen_);
return 0;
}
int
fuse_dirents_add_plus(fuse_dirents_t *d_,
const struct dirent *dirent_,
const uint64_t namelen_,
const fuse_entry_t *entry_,
const struct stat *st_)
{
fuse_direntplus_t *d;
switch(d_->type)
{
case UNSET:
d_->type = PLUS;
break;
case NORMAL:
return -EINVAL;
case PLUS:
break;
}
d = fuse_dirents_direntplus_alloc(d_,namelen_);
if(d == NULL)
return -ENOMEM;
d->dirent.off = kv_size(d_->offs);
kv_push(uint32_t,d_->offs,kv_size(d_->data));
d->dirent.ino = dirent_->d_ino;
d->dirent.namelen = namelen_;
d->dirent.type = dirent_->d_type;
memcpy(d->dirent.name,dirent_->d_name,namelen_);
d->entry = *entry_;
fuse_dirents_fill_attr(&d->attr,st_);
return 0;
}
int
fuse_dirents_add_linux(fuse_dirents_t *d_,
const linux_dirent64_t *dirent_,
const uint64_t namelen_)
{
fuse_dirent_t *d;
switch(d_->type)
{
case UNSET:
d_->type = NORMAL;
break;
case NORMAL:
break;
case PLUS:
return -EINVAL;
}
d = fuse_dirents_dirent_alloc(d_,namelen_);
if(d == NULL)
return -ENOMEM;
d->off = kv_size(d_->offs);
kv_push(uint32_t,d_->offs,kv_size(d_->data));
d->ino = dirent_->ino;
d->namelen = namelen_;
d->type = dirent_->type;
memcpy(d->name,dirent_->name,namelen_);
return 0;
}
int
fuse_dirents_add_linux_plus(fuse_dirents_t *d_,
const linux_dirent64_t *dirent_,
const uint64_t namelen_,
const fuse_entry_t *entry_,
const struct stat *st_)
{
fuse_direntplus_t *d;
switch(d_->type)
{
case UNSET:
d_->type = PLUS;
break;
case NORMAL:
return -EINVAL;
case PLUS:
break;
}
d = fuse_dirents_direntplus_alloc(d_,namelen_);
if(d == NULL)
return -ENOMEM;
d->dirent.off = kv_size(d_->offs);
kv_push(uint32_t,d_->offs,kv_size(d_->data));
d->dirent.ino = dirent_->ino;
d->dirent.namelen = namelen_;
d->dirent.type = dirent_->type;
memcpy(d->dirent.name,dirent_->name,namelen_);
d->entry = *entry_;
fuse_dirents_fill_attr(&d->attr,st_);
return 0;
}
void
fuse_dirents_reset(fuse_dirents_t *d_)
{
d_->type = UNSET;
kv_size(d_->data) = 0;
kv_size(d_->offs) = 1;
}
int
fuse_dirents_init(fuse_dirents_t *d_)
{
d_->type = UNSET;
kv_init(d_->data);
kv_resize(char,d_->data,DEFAULT_SIZE);
if(d_->data.a == NULL)
return -ENOMEM;
kv_init(d_->offs);
kv_resize(uint32_t,d_->offs,64);
kv_push(uint32_t,d_->offs,0);
return 0;
}
void
fuse_dirents_free(fuse_dirents_t *d_)
{
kv_destroy(d_->data);
kv_destroy(d_->offs);
}
-111
View File
@@ -1,111 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB
*/
#pragma once
#include "fuse.h"
#include "fuse_lowlevel.h"
#include "fuse_msgbuf.h"
#include "extern_c.h"
struct fuse_chan;
struct fuse_ll;
struct fuse_session
{
int (*receive_buf)(struct fuse_session *se,
fuse_msgbuf_t *msgbuf);
void (*process_buf)(struct fuse_session *se,
const fuse_msgbuf_t *msgbuf);
void (*destroy)(void *data);
struct fuse_ll *f;
volatile int exited;
struct fuse_chan *ch;
};
struct fuse_req
{
struct fuse_ll *f;
uint64_t unique;
struct fuse_ctx ctx;
struct fuse_chan *ch;
unsigned int ioctl_64bit : 1;
};
struct fuse_notify_req
{
uint64_t unique;
void (*reply)(struct fuse_notify_req *,
fuse_req_t,
uint64_t,
const void *);
struct fuse_notify_req *next;
struct fuse_notify_req *prev;
};
struct fuse_ll
{
int debug;
int no_remote_posix_lock;
int no_remote_flock;
int big_writes;
struct fuse_lowlevel_ops op;
void *userdata;
uid_t owner;
struct fuse_conn_info conn;
pthread_mutex_t lock;
int got_init;
int got_destroy;
pthread_key_t pipe_key;
int broken_splice_nonblock;
uint64_t notify_ctr;
struct fuse_notify_req notify_list;
};
struct fuse_cmd
{
char *buf;
size_t buflen;
struct fuse_chan *ch;
};
EXTERN_C_BEGIN
struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args,
const struct fuse_operations *op,
size_t op_size);
struct fuse_chan *fuse_kern_chan_new(int fd);
struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
const struct fuse_lowlevel_ops *op,
size_t op_size, void *userdata);
int fuse_chan_clearfd(struct fuse_chan *ch);
void fuse_kern_unmount(const char *mountpoint, int fd);
int fuse_kern_mount(const char *mountpoint, struct fuse_args *args);
int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
int count);
void fuse_free_req(fuse_req_t req);
struct fuse *fuse_setup_common(int argc, char *argv[],
const struct fuse_operations *op,
size_t op_size,
char **mountpoint,
int *fd);
int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
EXTERN_C_END
-9
View File
@@ -1,9 +0,0 @@
#pragma once
#include "fuse_msgbuf.h"
int fuse_receive_buf(struct fuse_session *se,
fuse_msgbuf_t *msgbuf);
void fuse_process_buf(void *data,
const fuse_msgbuf_t *msgbuf,
struct fuse_chan *ch);
File diff suppressed because it is too large Load Diff
-228
View File
@@ -1,228 +0,0 @@
/*
ISC License
Copyright (c) 2022, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_msgbuf.hpp"
#include "fuse.h"
#include "fuse_kernel.h"
#include <unistd.h>
#include <atomic>
#include <cstdint>
#include <cstdlib>
#include <mutex>
#include <vector>
static std::uint32_t g_PAGESIZE = 0;
static std::uint32_t g_BUFSIZE = 0;
static std::atomic<std::uint_fast64_t> g_MSGBUF_ALLOC_COUNT;
static std::mutex g_MUTEX;
static std::vector<fuse_msgbuf_t*> g_MSGBUF_STACK;
uint64_t
msgbuf_get_bufsize()
{
return g_BUFSIZE;
}
void
msgbuf_set_bufsize(const uint32_t size_in_pages_)
{
g_BUFSIZE = ((size_in_pages_ + 1) * g_PAGESIZE);
}
void
msgbuf_page_align(fuse_msgbuf_t *msgbuf_)
{
msgbuf_->mem = (char*)msgbuf_;
msgbuf_->mem += g_PAGESIZE;
msgbuf_->size = (g_BUFSIZE - g_PAGESIZE);
}
void
msgbuf_write_align(fuse_msgbuf_t *msgbuf_)
{
msgbuf_->mem = (char*)msgbuf_;
msgbuf_->mem += g_PAGESIZE;
msgbuf_->mem -= sizeof(struct fuse_in_header);
msgbuf_->mem -= sizeof(struct fuse_write_in);
msgbuf_->size = (g_BUFSIZE - g_PAGESIZE);
}
static
__attribute__((constructor))
void
msgbuf_constructor()
{
g_PAGESIZE = sysconf(_SC_PAGESIZE);
// FUSE_MAX_MAX_PAGES for payload + 1 for message header
msgbuf_set_bufsize(FUSE_DEFAULT_MAX_MAX_PAGES + 1);
}
static
__attribute__((destructor))
void
msgbuf_destroy()
{
}
static
void*
page_aligned_malloc(const uint64_t size_)
{
int rv;
void *buf = NULL;
rv = posix_memalign(&buf,g_PAGESIZE,size_);
if(rv != 0)
return NULL;
return buf;
}
typedef void (*msgbuf_setup_func_t)(fuse_msgbuf_t*);
static
fuse_msgbuf_t*
_msgbuf_alloc(msgbuf_setup_func_t setup_func_)
{
fuse_msgbuf_t *msgbuf;
g_MUTEX.lock();
if(g_MSGBUF_STACK.empty())
{
g_MUTEX.unlock();
msgbuf = (fuse_msgbuf_t*)page_aligned_malloc(g_BUFSIZE);
if(msgbuf == NULL)
return NULL;
g_MSGBUF_ALLOC_COUNT.fetch_add(1,std::memory_order_relaxed);
}
else
{
msgbuf = g_MSGBUF_STACK.back();
g_MSGBUF_STACK.pop_back();
g_MUTEX.unlock();
}
setup_func_(msgbuf);
return msgbuf;
}
// Offset the memory so write request payload will be placed on page
// boundry so O_DIRECT can work. No impact on other message types
// except for `read` which will require using `msgbuf_page_align`.
fuse_msgbuf_t*
msgbuf_alloc()
{
return _msgbuf_alloc(msgbuf_write_align);
}
fuse_msgbuf_t*
msgbuf_alloc_page_aligned()
{
return _msgbuf_alloc(msgbuf_page_align);
}
static
void
msgbuf_destroy(fuse_msgbuf_t *msgbuf_)
{
// free(msgbuf_->mem);
free(msgbuf_);
}
void
msgbuf_free(fuse_msgbuf_t *msgbuf_)
{
std::lock_guard<std::mutex> lck(g_MUTEX);
if(msgbuf_->size != (g_BUFSIZE - g_PAGESIZE))
{
msgbuf_destroy(msgbuf_);
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
return;
}
g_MSGBUF_STACK.emplace_back(msgbuf_);
}
uint64_t
msgbuf_alloc_count()
{
return g_MSGBUF_ALLOC_COUNT;
}
uint64_t
msgbuf_avail_count()
{
std::lock_guard<std::mutex> lck(g_MUTEX);
return g_MSGBUF_STACK.size();
}
void
msgbuf_gc_10percent()
{
std::vector<fuse_msgbuf_t*> togc;
{
std::size_t size;
std::size_t ten_percent;
std::lock_guard<std::mutex> lck(g_MUTEX);
size = g_MSGBUF_STACK.size();
ten_percent = (size / 10);
for(std::size_t i = 0; i < ten_percent; i++)
{
togc.push_back(g_MSGBUF_STACK.back());
g_MSGBUF_STACK.pop_back();
}
}
for(auto msgbuf : togc)
{
msgbuf_destroy(msgbuf);
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
}
}
void
msgbuf_gc()
{
std::vector<fuse_msgbuf_t*> oldstack;
{
std::lock_guard<std::mutex> lck(g_MUTEX);
oldstack.swap(g_MSGBUF_STACK);
}
for(auto msgbuf: oldstack)
{
msgbuf_destroy(msgbuf);
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
}
}
-154
View File
@@ -1,154 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB
*/
#include "fuse_i.h"
#include "fuse_kernel.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/uio.h>
struct fuse_chan
{
struct fuse_session *se;
int fd;
size_t bufsize;
};
struct fuse_session *
fuse_session_new(void *data,
void *receive_buf,
void *process_buf,
void *destroy)
{
struct fuse_session *se = (struct fuse_session *) malloc(sizeof(*se));
if (se == NULL) {
fprintf(stderr, "fuse: failed to allocate session\n");
return NULL;
}
memset(se, 0, sizeof(*se));
se->f = data;
se->receive_buf = receive_buf;
se->process_buf = process_buf;
se->destroy = destroy;
return se;
}
void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch)
{
assert(se->ch == NULL);
assert(ch->se == NULL);
se->ch = ch;
ch->se = se;
}
void fuse_session_remove_chan(struct fuse_chan *ch)
{
struct fuse_session *se = ch->se;
if (se) {
assert(se->ch == ch);
se->ch = NULL;
ch->se = NULL;
}
}
void
fuse_session_destroy(struct fuse_session *se)
{
se->destroy(se->f);
if(se->ch != NULL)
fuse_chan_destroy(se->ch);
free(se);
}
void fuse_session_reset(struct fuse_session *se)
{
se->exited = 0;
}
int
fuse_session_exited(struct fuse_session *se)
{
return se->exited;
}
void
fuse_session_exit(struct fuse_session *se_)
{
se_->exited = 1;
}
void*
fuse_session_data(struct fuse_session *se)
{
return se->f;
}
struct fuse_chan *
fuse_chan_new(int fd,
size_t bufsize)
{
struct fuse_chan *ch;
ch = (struct fuse_chan*)malloc(sizeof(*ch));
if(ch == NULL)
{
fprintf(stderr, "fuse: failed to allocate channel\n");
return NULL;
}
memset(ch, 0, sizeof(*ch));
ch->fd = fd;
ch->bufsize = bufsize;
return ch;
}
int fuse_chan_fd(struct fuse_chan *ch)
{
return ch->fd;
}
int fuse_chan_clearfd(struct fuse_chan *ch)
{
int fd = ch->fd;
ch->fd = -1;
return fd;
}
size_t fuse_chan_bufsize(struct fuse_chan *ch)
{
return ch->bufsize;
}
struct fuse_session *fuse_chan_session(struct fuse_chan *ch)
{
return ch->se;
}
void
fuse_chan_destroy(struct fuse_chan *ch)
{
int fd;
fuse_session_remove_chan(ch);
fd = fuse_chan_fd(ch);
if(fd != -1)
close(fd);
free(ch);
}
-399
View File
@@ -1,399 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#include "fuse_i.h"
#include "fuse_opt.h"
#include "fuse_lowlevel.h"
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <sys/param.h>
enum {
KEY_HELP,
KEY_HELP_NOHEADER,
KEY_VERSION,
};
struct helper_opts
{
int foreground;
int nodefault_subtype;
char *mountpoint;
};
#define FUSE_HELPER_OPT(t, p) { t, offsetof(struct helper_opts, p), 1 }
static
const
struct fuse_opt fuse_helper_opts[] =
{
FUSE_HELPER_OPT("-f", foreground),
FUSE_HELPER_OPT("fsname=", nodefault_subtype),
FUSE_HELPER_OPT("subtype=", nodefault_subtype),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-ho", KEY_HELP_NOHEADER),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP),
FUSE_OPT_END
};
static void usage(const char *progname)
{
fprintf(stderr,
"usage: %s mountpoint [options]\n\n", progname);
fprintf(stderr,
"general options:\n"
" -o opt,[opt...] mount options\n"
" -h --help print help\n"
" -V --version print version\n"
"\n");
}
static void helper_help(void)
{
fprintf(stderr,
"FUSE options:\n"
" -d -o debug enable debug output (implies -f)\n"
" -f foreground operation\n"
"\n"
);
}
static int fuse_helper_opt_proc(void *data, const char *arg, int key,
struct fuse_args *outargs)
{
struct helper_opts *hopts = data;
switch (key) {
case KEY_HELP:
usage(outargs->argv[0]);
/* fall through */
case KEY_HELP_NOHEADER:
helper_help();
return fuse_opt_add_arg(outargs, "-h");
case KEY_VERSION:
return 1;
case FUSE_OPT_KEY_NONOPT:
if (!hopts->mountpoint) {
char mountpoint[PATH_MAX];
if (realpath(arg, mountpoint) == NULL) {
fprintf(stderr,
"fuse: bad mount point `%s': %s\n",
arg, strerror(errno));
return -1;
}
return fuse_opt_add_opt(&hopts->mountpoint, mountpoint);
} else {
fprintf(stderr, "fuse: invalid argument `%s'\n", arg);
return -1;
}
default:
return 1;
}
}
static int add_default_subtype(const char *progname, struct fuse_args *args)
{
int res;
char *subtype_opt;
const char *basename = strrchr(progname, '/');
if (basename == NULL)
basename = progname;
else if (basename[1] != '\0')
basename++;
subtype_opt = (char *) malloc(strlen(basename) + 64);
if (subtype_opt == NULL) {
fprintf(stderr, "fuse: memory allocation failed\n");
return -1;
}
sprintf(subtype_opt, "-osubtype=%s", basename);
res = fuse_opt_add_arg(args, subtype_opt);
free(subtype_opt);
return res;
}
int
fuse_parse_cmdline(struct fuse_args *args_,
char **mountpoint_,
int *foreground_)
{
int res;
struct helper_opts hopts;
memset(&hopts, 0, sizeof(hopts));
res = fuse_opt_parse(args_,
&hopts,
fuse_helper_opts,
fuse_helper_opt_proc);
if(res == -1)
return -1;
if(!hopts.nodefault_subtype)
{
res = add_default_subtype(args_->argv[0], args_);
if(res == -1)
goto err;
}
if(mountpoint_)
*mountpoint_ = hopts.mountpoint;
else
free(hopts.mountpoint);
if(foreground_)
*foreground_ = hopts.foreground;
return 0;
err:
free(hopts.mountpoint);
return -1;
}
int fuse_daemonize(int foreground)
{
if (!foreground) {
int nullfd;
int waiter[2];
char completed;
if (pipe(waiter)) {
perror("fuse_daemonize: pipe");
return -1;
}
/*
* demonize current process by forking it and killing the
* parent. This makes current process as a child of 'init'.
*/
switch(fork()) {
case -1:
perror("fuse_daemonize: fork");
return -1;
case 0:
break;
default:
read(waiter[0], &completed, sizeof(completed));
_exit(0);
}
if (setsid() == -1) {
perror("fuse_daemonize: setsid");
return -1;
}
(void) chdir("/");
nullfd = open("/dev/null", O_RDWR, 0);
if (nullfd != -1) {
(void) dup2(nullfd, 0);
(void) dup2(nullfd, 1);
(void) dup2(nullfd, 2);
if (nullfd > 2)
close(nullfd);
}
/* Propagate completion of daemon initializatation */
completed = 1;
write(waiter[1], &completed, sizeof(completed));
close(waiter[0]);
close(waiter[1]);
}
return 0;
}
static
struct fuse_chan *
fuse_mount_common(const char *mountpoint_,
struct fuse_args *args_)
{
int fd;
long bufsize;
long pagesize;
struct fuse_chan *ch;
/*
* Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
* would ensue.
*/
do
{
fd = open("/dev/null", O_RDWR);
if(fd > 2)
close(fd);
} while(fd >= 0 && fd <= 2);
fd = fuse_kern_mount(mountpoint_,args_);
if(fd == -1)
return NULL;
pagesize = sysconf(_SC_PAGESIZE);
bufsize = ((FUSE_DEFAULT_MAX_MAX_PAGES + 1) * pagesize);
ch = fuse_chan_new(fd,bufsize);
if(!ch)
fuse_kern_unmount(mountpoint_, fd);
return ch;
}
struct fuse_chan *
fuse_mount(const char *mountpoint_,
struct fuse_args *args_)
{
return fuse_mount_common(mountpoint_,args_);
}
static void fuse_unmount_common(const char *mountpoint, struct fuse_chan *ch)
{
if (mountpoint) {
int fd = ch ? fuse_chan_clearfd(ch) : -1;
fuse_kern_unmount(mountpoint, fd);
if (ch)
fuse_chan_destroy(ch);
}
}
void fuse_unmount(const char *mountpoint, struct fuse_chan *ch)
{
fuse_unmount_common(mountpoint, ch);
}
struct fuse *
fuse_setup_common(int argc,
char *argv[],
const struct fuse_operations *op,
size_t op_size,
char **mountpoint,
int *fd)
{
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
struct fuse_chan *ch;
struct fuse *fuse;
int foreground;
int res;
res = fuse_parse_cmdline(&args, mountpoint, &foreground);
if (res == -1)
return NULL;
ch = fuse_mount_common(*mountpoint, &args);
if (!ch) {
fuse_opt_free_args(&args);
goto err_free;
}
fuse = fuse_new_common(ch, &args, op, op_size);
fuse_opt_free_args(&args);
if (fuse == NULL)
goto err_unmount;
res = fuse_daemonize(foreground);
if (res == -1)
goto err_unmount;
res = fuse_set_signal_handlers(fuse_get_session(fuse));
if (res == -1)
goto err_unmount;
if (fd)
*fd = fuse_chan_fd(ch);
return fuse;
err_unmount:
fuse_unmount_common(*mountpoint, ch);
if (fuse)
fuse_destroy(fuse);
err_free:
free(*mountpoint);
return NULL;
}
struct fuse *fuse_setup(int argc, char *argv[],
const struct fuse_operations *op, size_t op_size,
char **mountpoint)
{
return fuse_setup_common(argc, argv, op, op_size, mountpoint,
NULL);
}
static void fuse_teardown_common(struct fuse *fuse, char *mountpoint)
{
struct fuse_session *se = fuse_get_session(fuse);
struct fuse_chan *ch = se->ch;
fuse_remove_signal_handlers(se);
fuse_unmount_common(mountpoint, ch);
fuse_destroy(fuse);
free(mountpoint);
}
void fuse_teardown(struct fuse *fuse, char *mountpoint)
{
fuse_teardown_common(fuse, mountpoint);
}
static int fuse_main_common(int argc, char *argv[],
const struct fuse_operations *op, size_t op_size)
{
struct fuse *fuse;
char *mountpoint;
int res;
fuse = fuse_setup_common(argc, argv, op, op_size,
&mountpoint,
NULL);
if (fuse == NULL)
return 1;
res = fuse_loop_mt(fuse);
fuse_teardown_common(fuse, mountpoint);
if (res == -1)
return 1;
return 0;
}
int fuse_main_real(int argc,
char *argv[],
const struct fuse_operations *op,
size_t op_size)
{
return fuse_main_common(argc, argv, op, op_size);
}
#undef fuse_main
int fuse_main(void);
int fuse_main(void)
{
fprintf(stderr, "fuse_main(): This function does not exist\n");
return -1;
}
int fuse_version(void)
{
return FUSE_VERSION;
}
-627
View File
@@ -1,627 +0,0 @@
/* The MIT License
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*
An example:
#include "khash.h"
KHASH_MAP_INIT_INT(32, char)
int main() {
int ret, is_missing;
khiter_t k;
khash_t(32) *h = kh_init(32);
k = kh_put(32, h, 5, &ret);
kh_value(h, k) = 10;
k = kh_get(32, h, 10);
is_missing = (k == kh_end(h));
k = kh_get(32, h, 5);
kh_del(32, h, k);
for (k = kh_begin(h); k != kh_end(h); ++k)
if (kh_exist(h, k)) kh_value(h, k) = 1;
kh_destroy(32, h);
return 0;
}
*/
/*
2013-05-02 (0.2.8):
* Use quadratic probing. When the capacity is power of 2, stepping function
i*(i+1)/2 guarantees to traverse each bucket. It is better than double
hashing on cache performance and is more robust than linear probing.
In theory, double hashing should be more robust than quadratic probing.
However, my implementation is probably not for large hash tables, because
the second hash function is closely tied to the first hash function,
which reduce the effectiveness of double hashing.
Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
2011-12-29 (0.2.7):
* Minor code clean up; no actual effect.
2011-09-16 (0.2.6):
* The capacity is a power of 2. This seems to dramatically improve the
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
- http://code.google.com/p/ulib/
- http://nothings.org/computer/judy/
* Allow to optionally use linear probing which usually has better
performance for random input. Double hashing is still the default as it
is more robust to certain non-random input.
* Added Wang's integer hash function (not used by default). This hash
function is more robust to certain non-random input.
2011-02-14 (0.2.5):
* Allow to declare global functions.
2009-09-26 (0.2.4):
* Improve portability
2008-09-19 (0.2.3):
* Corrected the example
* Improved interfaces
2008-09-11 (0.2.2):
* Improved speed a little in kh_put()
2008-09-10 (0.2.1):
* Added kh_clear()
* Fixed a compiling error
2008-09-02 (0.2.0):
* Changed to token concatenation which increases flexibility.
2008-08-31 (0.1.2):
* Fixed a bug in kh_get(), which has not been tested previously.
2008-08-31 (0.1.1):
* Added destructor
*/
#ifndef __AC_KHASH_H
#define __AC_KHASH_H
/*!
@header
Generic hash table library.
*/
#define AC_VERSION_KHASH_H "0.2.8"
#include <stdlib.h>
#include <string.h>
#include <limits.h>
/* compiler specific configuration */
#if UINT_MAX == 0xffffffffu
typedef unsigned int khint32_t;
#elif ULONG_MAX == 0xffffffffu
typedef unsigned long khint32_t;
#endif
#if ULONG_MAX == ULLONG_MAX
typedef unsigned long khint64_t;
#else
typedef unsigned long long khint64_t;
#endif
#ifndef kh_inline
#ifdef _MSC_VER
#define kh_inline __inline
#else
#define kh_inline inline
#endif
#endif /* kh_inline */
#ifndef klib_unused
#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
#define klib_unused __attribute__ ((__unused__))
#else
#define klib_unused
#endif
#endif /* klib_unused */
typedef khint32_t khint_t;
typedef khint_t khiter_t;
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
#ifndef kroundup32
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
#endif
#ifndef kcalloc
#define kcalloc(N,Z) calloc(N,Z)
#endif
#ifndef kmalloc
#define kmalloc(Z) malloc(Z)
#endif
#ifndef krealloc
#define krealloc(P,Z) realloc(P,Z)
#endif
#ifndef kfree
#define kfree(P) free(P)
#endif
static const double __ac_HASH_UPPER = 0.77;
#define __KHASH_TYPE(name, khkey_t, khval_t) \
typedef struct kh_##name##_s { \
khint_t n_buckets, size, n_occupied, upper_bound; \
khint32_t *flags; \
khkey_t *keys; \
khval_t *vals; \
} kh_##name##_t;
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
extern kh_##name##_t *kh_init_##name(void); \
extern void kh_destroy_##name(kh_##name##_t *h); \
extern void kh_clear_##name(kh_##name##_t *h); \
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
extern void kh_del_##name(kh_##name##_t *h, khint_t x);
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
SCOPE kh_##name##_t *kh_init_##name(void) { \
return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
} \
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
{ \
if (h) { \
kfree((void *)h->keys); kfree(h->flags); \
kfree((void *)h->vals); \
kfree(h); \
} \
} \
SCOPE void kh_clear_##name(kh_##name##_t *h) \
{ \
if (h && h->flags) { \
memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
h->size = h->n_occupied = 0; \
} \
} \
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
{ \
if (h->n_buckets) { \
khint_t k, i, last, mask, step = 0; \
mask = h->n_buckets - 1; \
k = __hash_func(key); i = k & mask; \
last = i; \
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
i = (i + (++step)) & mask; \
if (i == last) return h->n_buckets; \
} \
return __ac_iseither(h->flags, i)? h->n_buckets : i; \
} else return 0; \
} \
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
khint32_t *new_flags = 0; \
khint_t j = 1; \
{ \
kroundup32(new_n_buckets); \
if (new_n_buckets < 4) new_n_buckets = 4; \
if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
else { /* hash table size to be changed (shrink or expand); rehash */ \
new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
if (!new_flags) return -1; \
memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
if (h->n_buckets < new_n_buckets) { /* expand */ \
khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
if (!new_keys) { kfree(new_flags); return -1; } \
h->keys = new_keys; \
if (kh_is_map) { \
khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
if (!new_vals) { kfree(new_flags); return -1; } \
h->vals = new_vals; \
} \
} /* otherwise shrink */ \
} \
} \
if (j) { /* rehashing is needed */ \
for (j = 0; j != h->n_buckets; ++j) { \
if (__ac_iseither(h->flags, j) == 0) { \
khkey_t key = h->keys[j]; \
khval_t val; \
khint_t new_mask; \
new_mask = new_n_buckets - 1; \
if (kh_is_map) val = h->vals[j]; \
__ac_set_isdel_true(h->flags, j); \
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
khint_t k, i, step = 0; \
k = __hash_func(key); \
i = k & new_mask; \
while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
__ac_set_isempty_false(new_flags, i); \
if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
} else { /* write the element and jump out of the loop */ \
h->keys[i] = key; \
if (kh_is_map) h->vals[i] = val; \
break; \
} \
} \
} \
} \
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
} \
kfree(h->flags); /* free the working space */ \
h->flags = new_flags; \
h->n_buckets = new_n_buckets; \
h->n_occupied = h->size; \
h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
} \
return 0; \
} \
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
{ \
khint_t x; \
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
if (h->n_buckets > (h->size<<1)) { \
if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
*ret = -1; return h->n_buckets; \
} \
} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
*ret = -1; return h->n_buckets; \
} \
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
{ \
khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
else { \
last = i; \
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
if (__ac_isdel(h->flags, i)) site = i; \
i = (i + (++step)) & mask; \
if (i == last) { x = site; break; } \
} \
if (x == h->n_buckets) { \
if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
else x = i; \
} \
} \
} \
if (__ac_isempty(h->flags, x)) { /* not present at all */ \
h->keys[x] = key; \
__ac_set_isboth_false(h->flags, x); \
++h->size; ++h->n_occupied; \
*ret = 1; \
} else if (__ac_isdel(h->flags, x)) { /* deleted */ \
h->keys[x] = key; \
__ac_set_isboth_false(h->flags, x); \
++h->size; \
*ret = 2; \
} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
return x; \
} \
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
{ \
if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
__ac_set_isdel_true(h->flags, x); \
--h->size; \
} \
}
#define KHASH_DECLARE(name, khkey_t, khval_t) \
__KHASH_TYPE(name, khkey_t, khval_t) \
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
__KHASH_TYPE(name, khkey_t, khval_t) \
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
/* --- BEGIN OF HASH FUNCTIONS --- */
/*! @function
@abstract Integer hash function
@param key The integer [khint32_t]
@return The hash value [khint_t]
*/
#define kh_int_hash_func(key) (khint32_t)(key)
/*! @function
@abstract Integer comparison function
*/
#define kh_int_hash_equal(a, b) ((a) == (b))
/*! @function
@abstract 64-bit integer hash function
@param key The integer [khint64_t]
@return The hash value [khint_t]
*/
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
/*! @function
@abstract 64-bit integer comparison function
*/
#define kh_int64_hash_equal(a, b) ((a) == (b))
/*! @function
@abstract const char* hash function
@param s Pointer to a null terminated string
@return The hash value
*/
static kh_inline khint_t __ac_X31_hash_string(const char *s)
{
khint_t h = (khint_t)*s;
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
return h;
}
/*! @function
@abstract Another interface to const char* hash function
@param key Pointer to a null terminated string [const char*]
@return The hash value [khint_t]
*/
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
/*! @function
@abstract Const char* comparison function
*/
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
static kh_inline khint_t __ac_Wang_hash(khint_t key)
{
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}
#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
/* --- END OF HASH FUNCTIONS --- */
/* Other convenient macros... */
/*!
@abstract Type of the hash table.
@param name Name of the hash table [symbol]
*/
#define khash_t(name) kh_##name##_t
/*! @function
@abstract Initiate a hash table.
@param name Name of the hash table [symbol]
@return Pointer to the hash table [khash_t(name)*]
*/
#define kh_init(name) kh_init_##name()
/*! @function
@abstract Destroy a hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
*/
#define kh_destroy(name, h) kh_destroy_##name(h)
/*! @function
@abstract Reset a hash table without deallocating memory.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
*/
#define kh_clear(name, h) kh_clear_##name(h)
/*! @function
@abstract Resize a hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param s New size [khint_t]
*/
#define kh_resize(name, h, s) kh_resize_##name(h, s)
/*! @function
@abstract Insert a key to the hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param k Key [type of keys]
@param r Extra return code: -1 if the operation failed;
0 if the key is present in the hash table;
1 if the bucket is empty (never used); 2 if the element in
the bucket has been deleted [int*]
@return Iterator to the inserted element [khint_t]
*/
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
/*! @function
@abstract Retrieve a key from the hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param k Key [type of keys]
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
*/
#define kh_get(name, h, k) kh_get_##name(h, k)
/*! @function
@abstract Remove a key from the hash table.
@param name Name of the hash table [symbol]
@param h Pointer to the hash table [khash_t(name)*]
@param k Iterator to the element to be deleted [khint_t]
*/
#define kh_del(name, h, k) kh_del_##name(h, k)
/*! @function
@abstract Test whether a bucket contains data.
@param h Pointer to the hash table [khash_t(name)*]
@param x Iterator to the bucket [khint_t]
@return 1 if containing data; 0 otherwise [int]
*/
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
/*! @function
@abstract Get key given an iterator
@param h Pointer to the hash table [khash_t(name)*]
@param x Iterator to the bucket [khint_t]
@return Key [type of keys]
*/
#define kh_key(h, x) ((h)->keys[x])
/*! @function
@abstract Get value given an iterator
@param h Pointer to the hash table [khash_t(name)*]
@param x Iterator to the bucket [khint_t]
@return Value [type of values]
@discussion For hash sets, calling this results in segfault.
*/
#define kh_val(h, x) ((h)->vals[x])
/*! @function
@abstract Alias of kh_val()
*/
#define kh_value(h, x) ((h)->vals[x])
/*! @function
@abstract Get the start iterator
@param h Pointer to the hash table [khash_t(name)*]
@return The start iterator [khint_t]
*/
#define kh_begin(h) (khint_t)(0)
/*! @function
@abstract Get the end iterator
@param h Pointer to the hash table [khash_t(name)*]
@return The end iterator [khint_t]
*/
#define kh_end(h) ((h)->n_buckets)
/*! @function
@abstract Get the number of elements in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@return Number of elements in the hash table [khint_t]
*/
#define kh_size(h) ((h)->size)
/*! @function
@abstract Get the number of buckets in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@return Number of buckets in the hash table [khint_t]
*/
#define kh_n_buckets(h) ((h)->n_buckets)
/*! @function
@abstract Iterate over the entries in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@param kvar Variable to which key will be assigned
@param vvar Variable to which value will be assigned
@param code Block of code to execute
*/
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
if (!kh_exist(h,__i)) continue; \
(kvar) = kh_key(h,__i); \
(vvar) = kh_val(h,__i); \
code; \
} }
/*! @function
@abstract Iterate over the values in the hash table
@param h Pointer to the hash table [khash_t(name)*]
@param vvar Variable to which value will be assigned
@param code Block of code to execute
*/
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
if (!kh_exist(h,__i)) continue; \
(vvar) = kh_val(h,__i); \
code; \
} }
/* More convenient interfaces */
/*! @function
@abstract Instantiate a hash set containing integer keys
@param name Name of the hash table [symbol]
*/
#define KHASH_SET_INIT_INT(name) \
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
/*! @function
@abstract Instantiate a hash map containing integer keys
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
#define KHASH_MAP_INIT_INT(name, khval_t) \
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
/*! @function
@abstract Instantiate a hash set containing 64-bit integer keys
@param name Name of the hash table [symbol]
*/
#define KHASH_SET_INIT_INT64(name) \
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
/*! @function
@abstract Instantiate a hash map containing 64-bit integer keys
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
#define KHASH_MAP_INIT_INT64(name, khval_t) \
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
typedef const char *kh_cstr_t;
/*! @function
@abstract Instantiate a hash map containing const char* keys
@param name Name of the hash table [symbol]
*/
#define KHASH_SET_INIT_STR(name) \
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
/*! @function
@abstract Instantiate a hash map containing const char* keys
@param name Name of the hash table [symbol]
@param khval_t Type of values [type]
*/
#define KHASH_MAP_INIT_STR(name, khval_t) \
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
#endif /* __AC_KHASH_H */
-245
View File
@@ -1,245 +0,0 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fmp.h"
#include "mutex.hpp"
#include <pthread.h>
typedef struct lfmp_t lfmp_t;
struct lfmp_t
{
fmp_t fmp;
pthread_mutex_t lock;
};
static
inline
void
lfmp_init(lfmp_t *lfmp_,
const uint64_t obj_size_,
const uint64_t page_multiple_)
{
fmp_init(&lfmp_->fmp,obj_size_,page_multiple_);
mutex_init(&lfmp_->lock);
}
static
inline
void
lfmp_lock(lfmp_t *lfmp_)
{
mutex_lock(&lfmp_->lock);
}
static
inline
void
lfmp_unlock(lfmp_t *lfmp_)
{
mutex_unlock(&lfmp_->lock);
}
static
inline
uint64_t
lfmp_slab_count(lfmp_t *lfmp_)
{
uint64_t rv;
mutex_lock(&lfmp_->lock);
rv = fmp_slab_count(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
int
lfmp_slab_alloc(lfmp_t *lfmp_)
{
int rv;
mutex_lock(&lfmp_->lock);
rv = fmp_slab_alloc(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
void*
lfmp_alloc(lfmp_t *lfmp_)
{
void *rv;
mutex_lock(&lfmp_->lock);
rv = fmp_alloc(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
void*
lfmp_calloc(lfmp_t *lfmp_)
{
void *rv;
mutex_lock(&lfmp_->lock);
rv = fmp_calloc(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
void
lfmp_free(lfmp_t *lfmp_,
void *obj_)
{
mutex_lock(&lfmp_->lock);
fmp_free(&lfmp_->fmp,obj_);
mutex_unlock(&lfmp_->lock);
}
static
inline
void
lfmp_clear(lfmp_t *lfmp_)
{
mutex_lock(&lfmp_->lock);
fmp_clear(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
}
static
inline
void
lfmp_destroy(lfmp_t *lfmp_)
{
mutex_lock(&lfmp_->lock);
fmp_destroy(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
mutex_destroy(&lfmp_->lock);
}
static
inline
uint64_t
lfmp_avail_objs(lfmp_t *lfmp_)
{
uint64_t rv;
mutex_lock(&lfmp_->lock);
rv = fmp_avail_objs(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
uint64_t
lfmp_objs_in_slab(lfmp_t *lfmp_,
void *slab_)
{
uint64_t rv;
mutex_lock(&lfmp_->lock);
rv = fmp_objs_in_slab(&lfmp_->fmp,slab_);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
void
lfmp_remove_objs_in_slab(lfmp_t *lfmp_,
void *slab_)
{
mutex_lock(&lfmp_->lock);
fmp_remove_objs_in_slab(&lfmp_->fmp,slab_);
mutex_unlock(&lfmp_->lock);
}
static
inline
int
lfmp_gc(lfmp_t *lfmp_)
{
int rv;
mutex_lock(&lfmp_->lock);
rv = fmp_gc(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
uint64_t
lfmp_objs_per_slab(lfmp_t *lfmp_)
{
uint64_t rv;
mutex_lock(&lfmp_->lock);
rv = fmp_objs_per_slab(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
double
lfmp_slab_usage_ratio(lfmp_t *lfmp_)
{
double rv;
mutex_lock(&lfmp_->lock);
rv = fmp_slab_usage_ratio(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
static
inline
uint64_t
lfmp_total_allocated_memory(lfmp_t *lfmp_)
{
uint64_t rv;
mutex_lock(&lfmp_->lock);
rv = fmp_total_allocated_memory(&lfmp_->fmp);
mutex_unlock(&lfmp_->lock);
return rv;
}
-13
View File
@@ -1,13 +0,0 @@
#include <stdint.h>
#include <sys/types.h>
typedef struct lock_s lock_t;
struct lock_s
{
int type;
off_t start;
off_t end;
pid_t pid;
uint64_t owner;
lock_t *next;
};
-41
View File
@@ -1,41 +0,0 @@
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
namespace std
{
template<class T> struct _Unique_if
{
typedef unique_ptr<T> _Single_object;
};
template<class T> struct _Unique_if<T[]>
{
typedef unique_ptr<T[]> _Unknown_bound;
};
template<class T, size_t N> struct _Unique_if<T[N]>
{
typedef void _Known_bound;
};
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args)
{
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n)
{
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
}
-595
View File
@@ -1,595 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#include "fuse_i.h"
#include "fuse_opt.h"
#include "mount_util.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <sys/mount.h>
#ifdef __NetBSD__
#include <perfuse.h>
#define MS_RDONLY MNT_RDONLY
#define MS_NOSUID MNT_NOSUID
#define MS_NODEV MNT_NODEV
#define MS_NOEXEC MNT_NOEXEC
#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
#define MS_NOATIME MNT_NOATIME
#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
#endif
#define FUSERMOUNT_PROG "fusermount"
#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
#ifndef MS_DIRSYNC
#define MS_DIRSYNC 128
#endif
enum {
KEY_KERN_FLAG,
KEY_KERN_OPT,
KEY_FUSERMOUNT_OPT,
KEY_SUBTYPE_OPT,
KEY_MTAB_OPT,
KEY_RO,
KEY_HELP,
KEY_VERSION,
};
struct mount_opts {
int allow_other;
int ishelp;
int flags;
int auto_unmount;
int blkdev;
char *fsname;
char *subtype;
char *subtype_opt;
char *mtab_opts;
char *fusermount_opts;
char *kernel_opts;
};
#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
static const struct fuse_opt fuse_mount_opts[] = {
FUSE_MOUNT_OPT("allow_other", allow_other),
FUSE_MOUNT_OPT("blkdev", blkdev),
FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
FUSE_MOUNT_OPT("fsname=%s", fsname),
FUSE_MOUNT_OPT("subtype=%s", subtype),
FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
FUSE_OPT_KEY("large_read", KEY_KERN_OPT),
FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
FUSE_OPT_KEY("context=", KEY_KERN_OPT),
FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_KEEP),
FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
FUSE_OPT_KEY("-r", KEY_RO),
FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
FUSE_OPT_KEY("async", KEY_KERN_FLAG),
FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
FUSE_OPT_KEY("atime", KEY_KERN_FLAG),
FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_END
};
static void mount_help(void)
{
fprintf(stderr,
" -o auto_unmount auto unmount on process termination\n"
" -o default_permissions enable permission checking by kernel\n"
" -o fsname=NAME set filesystem name\n"
" -o subtype=NAME set filesystem type\n"
" -o large_read issue large read requests (2.4 only)\n"
" -o max_read=N set maximum size of read requests\n"
"\n");
}
static void exec_fusermount(const char *argv[])
{
execv(FUSERMOUNT_DIR "/mergerfs-" FUSERMOUNT_PROG, (char **) argv);
execvp("mergerfs-" FUSERMOUNT_PROG, (char **) argv);
execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv);
execvp(FUSERMOUNT_PROG, (char **) argv);
}
static void mount_version(void)
{
int pid = fork();
if (!pid) {
const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL };
exec_fusermount(argv);
_exit(1);
} else if (pid != -1)
waitpid(pid, NULL, 0);
}
struct mount_flags {
const char *opt;
unsigned long flag;
int on;
};
static const struct mount_flags mount_flags[] = {
{"rw", MS_RDONLY, 0},
{"ro", MS_RDONLY, 1},
{"suid", MS_NOSUID, 0},
{"nosuid", MS_NOSUID, 1},
{"dev", MS_NODEV, 0},
{"nodev", MS_NODEV, 1},
{"exec", MS_NOEXEC, 0},
{"noexec", MS_NOEXEC, 1},
{"async", MS_SYNCHRONOUS, 0},
{"sync", MS_SYNCHRONOUS, 1},
{"atime", MS_NOATIME, 0},
{"noatime", MS_NOATIME, 1},
#ifndef __NetBSD__
{"dirsync", MS_DIRSYNC, 1},
#endif
{NULL, 0, 0}
};
static void set_mount_flag(const char *s, int *flags)
{
int i;
for (i = 0; mount_flags[i].opt != NULL; i++) {
const char *opt = mount_flags[i].opt;
if (strcmp(opt, s) == 0) {
if (mount_flags[i].on)
*flags |= mount_flags[i].flag;
else
*flags &= ~mount_flags[i].flag;
return;
}
}
fprintf(stderr, "fuse: internal error, can't find mount flag\n");
abort();
}
static int fuse_mount_opt_proc(void *data, const char *arg, int key,
struct fuse_args *outargs)
{
struct mount_opts *mo = data;
switch (key) {
case KEY_RO:
arg = "ro";
/* fall through */
case KEY_KERN_FLAG:
set_mount_flag(arg, &mo->flags);
return 0;
case KEY_KERN_OPT:
return fuse_opt_add_opt(&mo->kernel_opts, arg);
case KEY_FUSERMOUNT_OPT:
return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
case KEY_SUBTYPE_OPT:
return fuse_opt_add_opt(&mo->subtype_opt, arg);
case KEY_MTAB_OPT:
return fuse_opt_add_opt(&mo->mtab_opts, arg);
case KEY_HELP:
mount_help();
mo->ishelp = 1;
break;
case KEY_VERSION:
mount_version();
mo->ishelp = 1;
break;
}
return 1;
}
/* return value:
* >= 0 => fd
* -1 => error
*/
static int receive_fd(int fd)
{
struct msghdr msg;
struct iovec iov;
char buf[1];
int rv;
size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
struct cmsghdr *cmsg;
iov.iov_base = buf;
iov.iov_len = 1;
memset(&msg, 0, sizeof(msg));
msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* old BSD implementations should use msg_accrights instead of
* msg_control; the interface is different. */
msg.msg_control = ccmsg;
msg.msg_controllen = sizeof(ccmsg);
while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
if (rv == -1) {
perror("recvmsg");
return -1;
}
if(!rv) {
/* EOF */
return -1;
}
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg->cmsg_type != SCM_RIGHTS) {
fprintf(stderr, "got control message of unknown type %d\n",
cmsg->cmsg_type);
return -1;
}
return *(int*)CMSG_DATA(cmsg);
}
void fuse_kern_unmount(const char *mountpoint, int fd)
{
int res;
int pid;
if (!mountpoint)
return;
if (fd != -1) {
struct pollfd pfd;
pfd.fd = fd;
pfd.events = 0;
res = poll(&pfd, 1, 0);
/* Need to close file descriptor, otherwise synchronous umount
would recurse into filesystem, and deadlock.
Caller expects fuse_kern_unmount to close the fd, so close it
anyway. */
close(fd);
/* If file poll returns POLLERR on the device file descriptor,
then the filesystem is already unmounted */
if (res == 1 && (pfd.revents & POLLERR))
return;
}
if (geteuid() == 0) {
fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
return;
}
res = umount2(mountpoint, 2);
if (res == 0)
return;
pid = fork();
if(pid == -1)
return;
if(pid == 0) {
const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z",
"--", mountpoint, NULL };
exec_fusermount(argv);
_exit(1);
}
waitpid(pid, NULL, 0);
}
static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
const char *opts, int quiet)
{
int fds[2], pid;
int res;
int rv;
if (!mountpoint) {
fprintf(stderr, "fuse: missing mountpoint parameter\n");
return -1;
}
res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
if(res == -1) {
perror("fuse: socketpair() failed");
return -1;
}
pid = fork();
if(pid == -1) {
perror("fuse: fork() failed");
close(fds[0]);
close(fds[1]);
return -1;
}
if(pid == 0) {
char env[10];
const char *argv[32];
int a = 0;
if (quiet) {
int fd = open("/dev/null", O_RDONLY);
if (fd != -1) {
dup2(fd, 1);
dup2(fd, 2);
}
}
argv[a++] = FUSERMOUNT_PROG;
if (opts) {
argv[a++] = "-o";
argv[a++] = opts;
}
argv[a++] = "--";
argv[a++] = mountpoint;
argv[a++] = NULL;
close(fds[1]);
fcntl(fds[0], F_SETFD, 0);
snprintf(env, sizeof(env), "%i", fds[0]);
setenv(FUSE_COMMFD_ENV, env, 1);
exec_fusermount(argv);
perror("fuse: failed to exec fusermount");
_exit(1);
}
close(fds[0]);
rv = receive_fd(fds[1]);
if (!mo->auto_unmount) {
/* with auto_unmount option fusermount will not exit until
this socket is closed */
close(fds[1]);
waitpid(pid, NULL, 0); /* bury zombie */
}
return rv;
}
static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
const char *mnt_opts)
{
char tmp[128];
const char *devname = "/dev/fuse";
char *source = NULL;
char *type = NULL;
struct stat stbuf;
int fd;
int res;
if (!mnt) {
fprintf(stderr, "fuse: missing mountpoint parameter\n");
return -1;
}
res = stat(mnt, &stbuf);
if (res == -1) {
fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n",
mnt, strerror(errno));
return -1;
}
if (mo->auto_unmount) {
/* Tell the caller to fallback to fusermount because
auto-unmount does not work otherwise. */
return -2;
}
fd = open(devname, O_RDWR);
if (fd == -1) {
if (errno == ENODEV || errno == ENOENT)
fprintf(stderr, "fuse: device not found, try 'modprobe fuse' first\n");
else
fprintf(stderr, "fuse: failed to open %s: %s\n",
devname, strerror(errno));
return -1;
}
snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
if (res == -1)
goto out_close;
source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
(mo->subtype ? strlen(mo->subtype) : 0) +
strlen(devname) + 32);
type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
if (!type || !source) {
fprintf(stderr, "fuse: failed to allocate memory\n");
goto out_close;
}
strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
if (mo->subtype) {
strcat(type, ".");
strcat(type, mo->subtype);
}
strcpy(source,
mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
if (res == -1 && errno == ENODEV && mo->subtype) {
/* Probably missing subtype support */
strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
if (mo->fsname) {
if (!mo->blkdev)
sprintf(source, "%s#%s", mo->subtype,
mo->fsname);
} else {
strcpy(source, type);
}
res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
}
if (res == -1) {
/*
* Maybe kernel doesn't support unprivileged mounts, in this
* case try falling back to fusermount
*/
if (errno == EPERM) {
res = -2;
} else {
int errno_save = errno;
if (mo->blkdev && errno == ENODEV &&
!fuse_mnt_check_fuseblk())
fprintf(stderr,
"fuse: 'fuseblk' support missing\n");
else
fprintf(stderr, "fuse: mount failed: %s\n",
strerror(errno_save));
}
goto out_close;
}
#ifndef __NetBSD__
#ifndef IGNORE_MTAB
if (geteuid() == 0) {
char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
res = -1;
if (!newmnt)
goto out_umount;
res = fuse_mnt_add_mount("fuse", source, newmnt, type,
mnt_opts);
free(newmnt);
if (res == -1)
goto out_umount;
}
#endif /* IGNORE_MTAB */
#endif /* __NetBSD__ */
free(type);
free(source);
return fd;
out_umount:
umount2(mnt, 2); /* lazy umount */
out_close:
free(type);
free(source);
close(fd);
return res;
}
static int get_mnt_flag_opts(char **mnt_optsp, int flags)
{
int i;
if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
return -1;
for (i = 0; mount_flags[i].opt != NULL; i++) {
if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
return -1;
}
return 0;
}
int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
{
struct mount_opts mo;
int res = -1;
char *mnt_opts = NULL;
memset(&mo, 0, sizeof(mo));
mo.flags = MS_NOSUID | MS_NODEV;
if (args &&
fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
return -1;
res = 0;
if (mo.ishelp)
goto out;
res = -1;
if (get_mnt_flag_opts(&mnt_opts, mo.flags) == -1)
goto out;
if (mo.kernel_opts && fuse_opt_add_opt(&mnt_opts, mo.kernel_opts) == -1)
goto out;
if (mo.mtab_opts && fuse_opt_add_opt(&mnt_opts, mo.mtab_opts) == -1)
goto out;
res = fuse_mount_sys(mountpoint, &mo, mnt_opts);
if (res == -2) {
if (mo.fusermount_opts &&
fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) == -1)
goto out;
if (mo.subtype) {
char *tmp_opts = NULL;
res = -1;
if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
fuse_opt_add_opt(&tmp_opts, mo.subtype_opt) == -1) {
free(tmp_opts);
goto out;
}
res = fuse_mount_fusermount(mountpoint, &mo, tmp_opts, 1);
free(tmp_opts);
if (res == -1)
res = fuse_mount_fusermount(mountpoint, &mo,
mnt_opts, 0);
} else {
res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0);
}
}
out:
free(mnt_opts);
free(mo.fsname);
free(mo.subtype);
free(mo.fusermount_opts);
free(mo.subtype_opt);
free(mo.kernel_opts);
free(mo.mtab_opts);
return res;
}
-19
View File
@@ -1,19 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#pragma once
#include <sys/types.h>
int fuse_mnt_add_mount(const char *progname, const char *fsname,
const char *mnt, const char *type, const char *opts);
int fuse_mnt_remove_mount(const char *progname, const char *mnt);
int fuse_mnt_umount(const char *progname, const char *abs_mnt,
const char *rel_mnt, int lazy);
char *fuse_mnt_resolve_path(const char *progname, const char *orig);
int fuse_mnt_check_fuseblk(void);
-60
View File
@@ -1,60 +0,0 @@
#include "node.hpp"
#include "lfmp.h"
static lfmp_t g_NODE_FMP;
static
__attribute__((constructor))
void
node_constructor()
{
lfmp_init(&g_NODE_FMP,sizeof(node_t),256);
}
static
__attribute__((destructor))
void
node_destructor()
{
lfmp_destroy(&g_NODE_FMP);
}
node_t*
node_alloc()
{
return (node_t*)lfmp_calloc(&g_NODE_FMP);
}
void
node_free(node_t *node_)
{
lfmp_free(&g_NODE_FMP,node_);
}
int
node_gc1()
{
return lfmp_gc(&g_NODE_FMP);
}
void
node_gc()
{
int rv;
int fails;
fails = 0;
do
{
rv = node_gc1();
if(rv == 0)
fails++;
} while(rv || (fails < 3));
}
lfmp_t*
node_lfmp()
{
return &g_NODE_FMP;
}
-31
View File
@@ -1,31 +0,0 @@
#pragma once
#include "lock.h"
#include "lfmp.h"
typedef struct node_s node_t;
struct node_s
{
node_t *name_next;
node_t *id_next;
uint64_t nodeid;
char *name;
node_t *parent;
uint64_t nlookup;
uint32_t refctr;
uint32_t open_count;
int32_t treelock;
lock_t *locks;
uint32_t stat_crc32b;
uint8_t is_stat_cache_valid:1;
};
node_t *node_alloc();
void node_free(node_t*);
int node_gc1();
void node_gc();
lfmp_t *node_lfmp();
-28
View File
@@ -1,28 +0,0 @@
#pragma once
#include "cpu.hpp"
#include <string>
namespace PinThreads
{
void R1L(const CPU::ThreadIdVec threads);
void R1P(const CPU::ThreadIdVec threads);
void RP1L(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void RP1P(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void R1LP1L(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void R1PP1P(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void RPSL(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void RPSP(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void R1PPSP(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads);
void pin(const CPU::ThreadIdVec read_threads,
const CPU::ThreadIdVec process_threads,
const std::string type);
}
File diff suppressed because it is too large Load Diff
-231
View File
@@ -1,231 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU GPL.
See the file COPYING.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
static char *progname;
static char *xstrdup(const char *s)
{
char *t = strdup(s);
if (!t) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
exit(1);
}
return t;
}
static void *xrealloc(void *oldptr, size_t size)
{
void *ptr = realloc(oldptr, size);
if (!ptr) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
exit(1);
}
return ptr;
}
static void add_arg(char **cmdp, const char *opt)
{
size_t optlen = strlen(opt);
size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
char *s;
s = cmd + cmdlen;
if (*cmdp)
*s++ = ' ';
*s++ = '\'';
for (; *opt; opt++) {
if (*opt == '\'') {
*s++ = '\'';
*s++ = '\\';
*s++ = '\'';
*s++ = '\'';
} else
*s++ = *opt;
}
*s++ = '\'';
*s = '\0';
*cmdp = cmd;
}
static char *add_option(const char *opt, char *options)
{
int oldlen = options ? strlen(options) : 0;
options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
if (!oldlen)
strcpy(options, opt);
else {
strcat(options, ",");
strcat(options, opt);
}
return options;
}
int main(int argc, char *argv[])
{
char *type = NULL;
char *source;
const char *mountpoint;
char *basename;
char *options = NULL;
char *command = NULL;
char *setuid = NULL;
int i;
int dev = 1;
int suid = 1;
progname = argv[0];
basename = strrchr(argv[0], '/');
if (basename)
basename++;
else
basename = argv[0];
type = "mergerfs";
if (strncmp(basename, "mount.fuse.", 11) == 0)
type = basename + 11;
if (strncmp(basename, "mount.fuseblk.", 14) == 0)
type = basename + 14;
if (type && !type[0])
type = NULL;
if (argc < 3) {
fprintf(stderr,
"usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
progname, type ? "source" : "type#[source]");
exit(1);
}
source = argv[1];
if (!source[0])
source = NULL;
mountpoint = argv[2];
for (i = 3; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
continue;
} else if (strcmp(argv[i], "-t") == 0) {
i++;
if (i == argc) {
fprintf(stderr,
"%s: missing argument to option '-t'\n",
progname);
exit(1);
}
type = argv[i];
if (strncmp(type, "fuse.", 5) == 0)
type += 5;
else if (strncmp(type, "fuseblk.", 8) == 0)
type += 8;
if (!type[0]) {
fprintf(stderr,
"%s: empty type given as argument to option '-t'\n",
progname);
exit(1);
}
} else if (strcmp(argv[i], "-o") == 0) {
char *opts;
char *opt;
i++;
if (i == argc)
break;
opts = xstrdup(argv[i]);
opt = strtok(opts, ",");
while (opt) {
int j;
int ignore = 0;
const char *ignore_opts[] = { "",
"user",
"nouser",
"users",
"auto",
"noauto",
"_netdev",
NULL};
if (strncmp(opt, "setuid=", 7) == 0) {
setuid = xstrdup(opt + 7);
ignore = 1;
}
for (j = 0; ignore_opts[j]; j++)
if (strcmp(opt, ignore_opts[j]) == 0)
ignore = 1;
if (!ignore) {
if (strcmp(opt, "nodev") == 0)
dev = 0;
else if (strcmp(opt, "nosuid") == 0)
suid = 0;
options = add_option(opt, options);
}
opt = strtok(NULL, ",");
}
}
}
if (dev)
options = add_option("dev", options);
if (suid)
options = add_option("suid", options);
if (!type) {
if (source) {
type = xstrdup(source);
source = strchr(type, '#');
if (source)
*source++ = '\0';
if (!type[0]) {
fprintf(stderr, "%s: empty filesystem type\n",
progname);
exit(1);
}
} else {
fprintf(stderr, "%s: empty source\n", progname);
exit(1);
}
}
add_arg(&command, type);
if (source)
add_arg(&command, source);
add_arg(&command, mountpoint);
if (options) {
add_arg(&command, "-o");
add_arg(&command, options);
}
if (setuid && setuid[0]) {
char *sucommand = command;
command = NULL;
add_arg(&command, "su");
add_arg(&command, "-");
add_arg(&command, setuid);
add_arg(&command, "-c");
add_arg(&command, sucommand);
} else if (!getenv("HOME")) {
/* Hack to make filesystems work in the boot environment */
setenv("HOME", "/root", 0);
}
execl("/bin/sh", "/bin/sh", "-c", command, NULL);
fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
strerror(errno));
return 1;
}
-330
View File
@@ -1,330 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#include "mount_util.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#ifndef __NetBSD__
#include <mntent.h>
#endif
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/param.h>
#ifdef __NetBSD__
#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
#define mtab_needs_update(mnt) 0
#else
static int mtab_needs_update(const char *mnt)
{
int res;
struct stat stbuf;
/* If mtab is within new mount, don't touch it */
if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
_PATH_MOUNTED[strlen(mnt)] == '/')
return 0;
/*
* Skip mtab update if /etc/mtab:
*
* - doesn't exist,
* - is a symlink,
* - is on a read-only filesystem.
*/
res = lstat(_PATH_MOUNTED, &stbuf);
if (res == -1) {
if (errno == ENOENT)
return 0;
} else {
uid_t ruid;
int err;
if (S_ISLNK(stbuf.st_mode))
return 0;
ruid = getuid();
if (ruid != 0)
setreuid(0, -1);
res = access(_PATH_MOUNTED, W_OK);
err = (res == -1) ? errno : 0;
if (ruid != 0)
setreuid(ruid, -1);
if (err == EROFS)
return 0;
}
return 1;
}
#endif /* __NetBSD__ */
static int add_mount(const char *progname, const char *fsname,
const char *mnt, const char *type, const char *opts)
{
int res;
int status;
sigset_t blockmask;
sigset_t oldmask;
sigemptyset(&blockmask);
sigaddset(&blockmask, SIGCHLD);
res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
if (res == -1) {
fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
return -1;
}
res = fork();
if (res == -1) {
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
goto out_restore;
}
if (res == 0) {
char *env = NULL;
sigprocmask(SIG_SETMASK, &oldmask, NULL);
setuid(geteuid());
execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
"-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
progname, strerror(errno));
exit(1);
}
res = waitpid(res, &status, 0);
if (res == -1)
fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
if (status != 0)
res = -1;
out_restore:
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return res;
}
int fuse_mnt_add_mount(const char *progname, const char *fsname,
const char *mnt, const char *type, const char *opts)
{
if (!mtab_needs_update(mnt))
return 0;
return add_mount(progname, fsname, mnt, type, opts);
}
static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
{
int res;
int status;
sigset_t blockmask;
sigset_t oldmask;
sigemptyset(&blockmask);
sigaddset(&blockmask, SIGCHLD);
res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
if (res == -1) {
fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
return -1;
}
res = fork();
if (res == -1) {
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
goto out_restore;
}
if (res == 0) {
char *env = NULL;
sigprocmask(SIG_SETMASK, &oldmask, NULL);
setuid(geteuid());
if (lazy) {
execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
"-l", NULL, &env);
} else {
execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
NULL, &env);
}
fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
progname, strerror(errno));
exit(1);
}
res = waitpid(res, &status, 0);
if (res == -1)
fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
if (status != 0) {
res = -1;
}
out_restore:
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return res;
}
int fuse_mnt_umount(const char *progname, const char *abs_mnt,
const char *rel_mnt, int lazy)
{
int res;
if (!mtab_needs_update(abs_mnt)) {
res = umount2(rel_mnt, lazy ? 2 : 0);
if (res == -1)
fprintf(stderr, "%s: failed to unmount %s: %s\n",
progname, abs_mnt, strerror(errno));
return res;
}
return exec_umount(progname, rel_mnt, lazy);
}
static int remove_mount(const char *progname, const char *mnt)
{
int res;
int status;
sigset_t blockmask;
sigset_t oldmask;
sigemptyset(&blockmask);
sigaddset(&blockmask, SIGCHLD);
res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
if (res == -1) {
fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
return -1;
}
res = fork();
if (res == -1) {
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
goto out_restore;
}
if (res == 0) {
char *env = NULL;
sigprocmask(SIG_SETMASK, &oldmask, NULL);
setuid(geteuid());
execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
"--fake", mnt, NULL, &env);
fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
progname, strerror(errno));
exit(1);
}
res = waitpid(res, &status, 0);
if (res == -1)
fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
if (status != 0)
res = -1;
out_restore:
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return res;
}
int fuse_mnt_remove_mount(const char *progname, const char *mnt)
{
if (!mtab_needs_update(mnt))
return 0;
return remove_mount(progname, mnt);
}
char *fuse_mnt_resolve_path(const char *progname, const char *orig)
{
char buf[PATH_MAX];
char *copy;
char *dst;
char *end;
char *lastcomp;
const char *toresolv;
if (!orig[0]) {
fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
orig);
return NULL;
}
copy = strdup(orig);
if (copy == NULL) {
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return NULL;
}
toresolv = copy;
lastcomp = NULL;
for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
if (end[0] != '/') {
char *tmp;
end[1] = '\0';
tmp = strrchr(copy, '/');
if (tmp == NULL) {
lastcomp = copy;
toresolv = ".";
} else {
lastcomp = tmp + 1;
if (tmp == copy)
toresolv = "/";
}
if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
lastcomp = NULL;
toresolv = copy;
}
else if (tmp)
tmp[0] = '\0';
}
if (realpath(toresolv, buf) == NULL) {
fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
strerror(errno));
free(copy);
return NULL;
}
if (lastcomp == NULL)
dst = strdup(buf);
else {
dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
if (dst) {
unsigned buflen = strlen(buf);
if (buflen && buf[buflen-1] == '/')
sprintf(dst, "%s%s", buf, lastcomp);
else
sprintf(dst, "%s/%s", buf, lastcomp);
}
}
free(copy);
if (dst == NULL)
fprintf(stderr, "%s: failed to allocate memory\n", progname);
return dst;
}
int fuse_mnt_check_fuseblk(void)
{
char buf[256];
FILE *f = fopen("/proc/filesystems", "r");
if (!f)
return 1;
while (fgets(buf, sizeof(buf), f))
if (strstr(buf, "fuseblk\n")) {
fclose(f);
return 1;
}
fclose(f);
return 0;
}
+6 -6
View File
@@ -12,16 +12,16 @@ is a FUSE-based union filesystem geared towards simplifying storage and manageme
and
.BR aufs .
.SH QUICKSTART
.UR https://trapexit.github.io/mergerfs/quickstart
.LI https://trapexit.github.io/mergerfs/quickstart
.UR https://trapexit.github.io/mergerfs/latest/quickstart
.LI https://trapexit.github.io/mergerfs/latest/quickstart
.UE
.SH DOCUMENTATION
.UR https://trapexit.github.io/mergerfs
.LI https://trapexit.github.io/mergerfs
.UE
.SH SUPPORT
.UR https://trapexit.github.io/mergerfs/support
.LI https://trapexit.github.io/mergerfs/support
.UR https://trapexit.github.io/mergerfs/latest/support
.LI https://trapexit.github.io/mergerfs/latest/support
.UE
.SH FEATURES
.IP \[bu] 2
@@ -74,8 +74,8 @@ When a file or directory is created, a policy is first run to determine which br
For functions which change attributes or remove the file, the behavior may be applied to all instances found.
Read more about policies here:
.UR https://trapexit.github.io/mergerfs/config/functions_categories_and_policies
.LI https://trapexit.github.io/mergerfs/config/functions_categories_and_policies
.UR https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies
.LI https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies
.UE
.SH VISUALIZATION
.nf
+1 -1
View File
@@ -4,4 +4,4 @@ set -e
python3 -m venv venv
. ./venv/bin/activate
pip3 install --no-cache-dir mkdocs mkdocs-material pymdown-extensions mike
pip3 install --no-cache-dir 'mkdocs<2.0' mkdocs-material pymdown-extensions mike
+5 -5
View File
@@ -58,13 +58,13 @@ read/writes. Contact the software author or worse case the mergerfs
author so it may be investigated further.
### write benchmark
### Write Benchmark
```
$ dd if=/dev/zero of=/mnt/mergerfs/16GB.file bs=1M count=16384 oflag=nocache conv=fdatasync status=progress
```
### read benchmark
### Read Benchmark
```
$ dd if=/mnt/mergerfs/16GB.file of=/dev/null bs=1M iflag=nocache conv=fdatasync status=progress
@@ -79,21 +79,21 @@ Below are some basic tests but also try setting `--direct=0`, changing
the `ioengine`, or playing with other options.
#### write
#### Write
```
$ fio --name=writetest --filename=/mnt/mergerfs/16GB.file --size=16Gb --rw=write --bs=1M --direct=1 --numjobs=1 --iodepth=8 --group_reporting --runtime=60 --startdelay=0 --ioengine=psync
```
#### read
#### Read
```
$ fio --name=readtest --filename=/mnt/mergerfs/16GB.file --size=16Gb --rw=read --bs=1M --direct=1 --numjobs=1 --iodepth=8 --group_reporting --runtime=60 --startdelay=0 --ioengine=psync
```
### other benchmarks
### Other Benchmarks
If you are attempting to benchmark other behaviors you must ensure you
clear kernel caches before runs. In fact it would be a good deal to
@@ -38,6 +38,32 @@ These files and xattrs are used to help `mergerfs` know whether or not
a directory happens to be a branch or is the underlying directory
mount point.
## Paths which appear after startup
The [`branches` resolution process](branches.md#resolution) runs before
this option starts waiting. A missing literal path is retained as a
branch, so a configuration such as `/mnt/hdd/disk0/foobar` can work
when `/mnt/hdd/disk0` is mounted asynchronously and `foobar` only
becomes visible afterward, provided the path then meets one of the
"mounted" conditions above.
For each branch which is not ready, mergerfs first runs `mount` once
with the full branch path as the target and then polls that same path
until the timeout expires. It does not create the path, try to mount
its parent, or rerun `mount`. In the example above, the initial
`mount /mnt/hdd/disk0/foobar` may fail while the path is absent. If
another service mounts `/mnt/hdd/disk0` and
`/mnt/hdd/disk0/foobar` appears and meets one of the "mounted"
conditions before the deadline, mergerfs detects it during polling
and continues.
Globs are different. If `/mnt/hdd/disk0/*` has no matches during
branch resolution, the literal path `/mnt/hdd/disk0/*` is retained.
The mount wait does not expand it again after `/mnt/hdd/disk0`
becomes available. Ensure the parent filesystem is mounted before
mergerfs, configure a literal branch path, or update `branches`
through the runtime API once the paths exist.
**NOTE:** If on a `systemd` based system and using `fstab` it is a
good idea to set the mount option
+32 -5
View File
@@ -14,7 +14,7 @@ Branches currently have two options which can be set. A
included in a policy calculation and a individual
[minfreespace](#minfreespace) value. The values are set by
prepending an `=` at the end of a branch designation and using commas
as delimiters. Example: `/mnt/drive=RW,1234`
as delimiters. Example: `/mnt/hdd/disk0=RW,1234`
### branch mode
@@ -34,6 +34,33 @@ Same purpose and syntax as the [global option](minfreespace.md) but
specific to the branch. Defaults to the global value.
### resolution
Each colon-delimited branch designation is resolved independently when
the `branches` option is parsed, both at startup and when it is updated
through the runtime API:
1. The path portion is expanded as a glob.
2. If the glob matches one or more directories, each match becomes a
branch.
3. If the glob has no matches, mergerfs logs a notice and keeps the
configured path unchanged as a single, literal branch.
4. mergerfs resolves each resulting path with `realpath`. If that
succeeds, the canonical path is stored. If it fails, typically
because the path does not exist yet, the unchanged path is stored.
5. A path which exists but is not a directory is logged and skipped. A
path which does not exist is logged but retained.
Consequently, a literal branch such as `/mnt/hdd/disk0/foobar` may be
configured before it exists and can become usable after
`/mnt/hdd/disk0` is mounted. Branch resolution is not repeated in the
background, however. If `/mnt/hdd/disk0/*` has no matches when it is
resolved, mergerfs stores the literal path `/mnt/hdd/disk0/*`;
directories which match it later are not discovered automatically.
Update `branches` through the runtime API or restart mergerfs to
resolve the glob again.
### globbing
To make it easier to include multiple branches mergerfs supports
@@ -42,18 +69,18 @@ MUST be escaped when using via the shell else the shell itself will
apply the glob itself.**
```
# mergerfs /mnt/hdd\*:/mnt/ssd /media
# mergerfs /mnt/hdd/\*:/mnt/ssd/\* /media
```
The above line will use all directories in /mnt prefixed with **hdd**
as well as **ssd**.
The above line will use all directories within `/mnt/hdd` and
`/mnt/ssd`.
To have the pool mounted at boot or otherwise accessible from related
tools use `/etc/fstab`.
```
# <file system> <mount point> <type> <options> <dump> <pass>
/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0
/mnt/hdd/*:/mnt/ssd/* /media mergerfs minfreespace=16G 0 0
```
**NOTE:** The globbing is done at mount or when updated using the
+11 -5
View File
@@ -11,7 +11,8 @@ works for mergerfs itself. Not the underlying filesystems.
* `cache.files=full`: Enables page caching. Files are cached across
opens.
* `cache.files=auto-full`: Enables page caching. Files are cached
across opens if mtime and size are unchanged since previous open.
across opens if mtime and size are unchanged since previous
open. Cache is dropped if mtime or size change on open.
* `cache.files=per-process`: Enable page caching (equivalent to
`cache.files=partial`) only for processes whose 'comm' name matches
one of the values defined in cache.files.process-names. If the name
@@ -40,6 +41,11 @@ transparently enable page caching when mmap is requested. This means
it should be safe to set `cache.files=off`. However, on Linux v6.5 and
below you will need to configure `cache.files` as you need.
If [passthrough.io](passthrough.md) is enabled so must page
caching. mergerfs will set `cache.files=auto-full` if `passthrough.io`
is enabled. And when using `passthrough.io` the there is no double
page caching since it is in fact passing through the IO.
[^1]: This is not unique to mergerfs and affects all FUSE
filesystems. It is something that the FUSE community hopes to
@@ -67,9 +73,9 @@ little risk in setting the value much higher. Especially if there are
no out-of-band changes.
## cache.negative_entry
## cache.negative-entry
* `cache.negative_entry=UINT`: Sets the number of seconds to cache
* `cache.negative-entry=UINT`: Sets the number of seconds to cache
negative entry queries. Defaults to `1`.
This is a cache for negative entry query responses. Such as when a
@@ -166,8 +172,8 @@ the FUSE writeback cache small writes *may* be aggregated by the
kernel and then sent to mergerfs as one larger request. This can
greatly improve the throughput for apps which write to files
inefficiently. The amount the kernel can aggregate is limited by the
size of a FUSE message. Read the fuse_msg_size section for more
details.
size of a FUSE message. Read the [fuse-msg-size](fuse-msg-size.md)
section for more details.
There is a side effect as a result of enabling writeback
caching. Underlying files won't ever be opened with O_APPEND or
+19 -14
View File
@@ -3,19 +3,24 @@
These are old, deprecated options which may no longer have any
function or have been replaced. **They should not be used.**
* **direct_io**: Bypass page cache. Use `cache.files=off`
instead.
* **kernel_cache**: Do not invalidate data cache on file open. Use
`cache.files=full` instead.
* **auto_cache**: Invalidate data cache if file mtime or
size change. Use `cache.files=auto-full` instead. (default: false)
* **async_read**: Perform reads asynchronously. Use
`async_read=true` instead.
* **sync_read**: Perform reads synchronously. Use
`async_read=false` instead.
* **splice_read**: Does nothing.
* **splice_write**: Does nothing.
* **splice_move**: Does nothing.
* **allow_other**: mergerfs v2.35.0 and above sets this FUSE option
automatically if running as root.
* **use_ino**: Effectively replaced with `inodecalc`.
* **async_read**: Use `async-read=true`.
* **atomic_o_trunc**: Does nothing.
* **attr_timeout**: Use [cache.attr](cache.md).
* **auto_cache**: Use [cache.files=auto-full](cache.md).
* **big_writes**: Does nothing.
* **direct_io**: Use [cache.files=off](cache.md).
* **entry_timeout**: Use [cache.entry](cache.md).
* **hard_remove**: Does nothing.
* **kernel_cache**: Use [cache.files=full](cache.md).
* **negative_entry**: Use [cache.negative-entry](cache.md).
* **remember**: Use `never-forget-nodes`.
* **remember-nodes**: Use `never-forget-nodes`.
* **noforget**: Use `never-forget-nodes`.
* **nonempty**: Does nothing.
* **splice_move**: Does nothing.
* **splice_read**: Does nothing.
* **splice_write**: Does nothing.
* **sync_read**: Use `async-read=false`.
* **use_ino**: Use [inodecalc](inodecalc.md).
+25 -5
View File
@@ -1,10 +1,10 @@
# func.readdir
Defaults to `seq`
* type: `STR`
* default: `seq`
* examples: `func.readdir=seq`, `func.readdir=cor:4`
Examples: `func.readdir=seq`, `func.readdir=cor:4`
`readdir` has policies to control how it reads directory content.
`readdir` has policies to control how to read directory content.
| Policy | Description |
| ------ | ----------- |
@@ -12,7 +12,7 @@ Examples: `func.readdir=seq`, `func.readdir=cor:4`
| cosr:N:M | "concurrent open, sequential read" : Concurrently open branch directories using a thread pool and process them in the order defined in `branches`. This keeps memory and CPU usage low while also reducing the time spent waiting on branches to respond. `N` is the number of threads. If negative it will be the core count divided by `abs(N)`. `M` is the queue depth. If either value is `0` it will be decided based on system configuration. |
| cosr:N | cosr:N:M with M = 0 |
| cosr | cosr:N:M with N = 0 and M = 0 |
| cor:N:M | "concurrent open and read" : Concurrently open branch directories and immediately start reading their contents using a thread pool. This will result in slightly higher memory and CPU usage but reduced latency. Particularly when using higher latency / slower speed network filesystem branches. Unlike `seq` and `cosr` the order of files could change due the async nature of the thread pool. This should not be a problem since the order of files listed in not guaranteed. `N` is the number of threads. If negative it will be the core count divided by `abs(N)`. `M` is the queue depth. If either value is `0` it will be decided based on system configuration. |
| cor:N:M | "concurrent open and read" : Concurrently open branch directories and immediately start reading their contents using a thread pool. This will result in slightly higher memory and CPU usage but reduced latency. Particularly when using higher latency / slower speed network filesystem branches. Unlike `seq` and `cosr` the order of files could change due the async nature of the thread pool. This should not be a problem since the order of files returned in not guaranteed. `N` is the number of threads. If negative it will be the core count divided by `abs(N)`. `M` is the queue depth. If either value is `0` it will be decided based on system configuration. |
| cor:N | cosr:N:M with M = 0 |
| cor | cosr:N:M with N = 0 and M = 0 |
@@ -21,3 +21,23 @@ in a directory and possibly some basic metadata about said files. To
know details about the files, as one would see from commands like
`find` or `ls`, it is required to call `stat` on the file which is
controlled by `fuse.getattr`.
Until Linux v6.16, despite being able to change the size of FUSE
messages, `readdir` requests were limited to 1 page (4KiB) in size per
message. This limitation limited throughput and therefore performance
of the `readdir` call. In v6.16 that was changed to be able to grow to
the [fuse-msg-size](fuse-msg-size.md) which allows a lot more data to
be sent per request and therefore much better performance in certain
situations.
## Technical Details
* On Linux
[getdents](https://man7.org/linux/man-pages/man2/getdents.2.html) is
used instead of
[readdir](https://man7.org/linux/man-pages/man2/readdir.2.html) in
order to leverage larger buffers for better performance on larger
directory reads as well as have more control in concurrent policies.
* [readdir](https://man7.org/linux/man-pages/man2/readdir.2.html) is
used on FreeBSD.
@@ -5,7 +5,9 @@ The POSIX filesystem API is made up of a number of functions. `creat`,
the core functions are grouped into 3 categories: `action`, `create`,
and `search`. These functions and categories can be assigned a policy
which dictates which branch is chosen when performing that
function.
function. IE. When an application requests that a file or directory is
created how should mergerfs decide which branch to choose to attempt
the create? That is the policy.
Some functions, listed in the category `N/A` below, can not be
assigned the normal policies because they are directly related to a
@@ -44,9 +46,9 @@ branch base path provided is used. Not the full path to the file or
directory in question. Meaning that mounts within the branch will not
be considered in the space calculations.
NOTE: While any policy can be assigned to a function or category, some
may not be very useful in practice. For instance: `rand` (random) may
be useful for file creation but could lead to very odd behavior if
**NOTE:** While any policy can be assigned to a function or category,
some may not be very useful in practice. For instance: `rand` (random)
may be useful for file creation but could lead to very odd behavior if
used for `chmod` if there were more than one copy of the file. Unless
users find this flexibility useful it will likely be removed in the
future.
@@ -69,7 +71,7 @@ whether or not a branch is mounted read-only, and the branch mode
[minfreespace](minfreespace.md).
Policies may have their own additional filtering such as those that
require existing paths to be present.
require relative paths to be present.
If all branches are filtered an error will be returned. Typically
`EROFS` (read-only filesystem) or `ENOSPC` (no space left on
@@ -114,6 +116,7 @@ but it makes things a bit more uniform.
| mfs (most free space) | Pick the branch with the most available free space. |
| ff (first found) | Given the order of the branches, as defined at mount time or configured at runtime, act on the first one found. |
| lfs (least free space) | Pick the branch with the least available free space. |
| lup (least used percent) | Pick the branch with the least used percentage of space |
| lus (least used space) | Pick the branch with the least used space. |
| all | Search: For **mkdir**, **mknod**, and **symlink** it will apply to all branches. **create** works like **ff**. |
| msppfrd (most shared path, percentage free random distribution) | Like **eppfrd** but if it fails to find a branch it will try again with the parent directory. Continues this pattern till finding one. |
@@ -1,13 +1,12 @@
# fuse_msg_size
# fuse-msg-size
* `fuse_msg_size=UINT|SIZE`
* Defaults to `1M`
* Performance improvements often peak at about `4M`
* type: `UINT|SIZE`
* default: `1M`
* example: `fuse-msg-size=1M`
* If the value is a `SIZE` in the form of `xB`, `xK`, `xM`, `xG`, or
`xT` then it will choose the closest multiple of the system page
size
* If the value is just an integer without a size multipler suffix then
it is treated as a page count
`xT` then it will choose the closest multiple of the system page
size. If the value is just an integer without a size multipler suffix
then it is treated as a page count
FUSE applications communicate with the kernel over a special character
device: `/dev/fuse`. A large portion of the overhead associated with
@@ -29,10 +28,10 @@ v6.13](https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/co
and above the max value supported by the kernel can range from 1
(4KiB) to 65535 (~256MiB) (assuming a page size of 4KiB.) The default
used by Linux >= 4.20, and hard coded value used before 4.20, is 32
(128KiB). In mergerfs it is referred to as `fuse_msg_size` to make it
(128KiB). In mergerfs it is referred to as `fuse-msg-size` to make it
clear what it impacts and provide some abstraction.
If the `fuse_msg_size` value provided is more than the system wide
If the `fuse-msg-size` value provided is more than the system wide
maximum mergerfs will attempt to increase the system wide value to keep
the user from needing to set the value using `sysctl`,
`/etc/sysctl.conf`, or `/proc/sys/fs/fuse/max_pages_limit`.
@@ -48,4 +47,4 @@ v4.20 and v6.13 the max value is 256. On kernels >= v6.13 the maximum
value is 65535.
NOTE: If you intend to enable `cache.files` you should also set
[readahead](readahead.md) to match `fuse_msg_size`.
[readahead](readahead.md) to match `fuse-msg-size`.
@@ -16,4 +16,4 @@ help. Like [export-support](export-support.md) this is mostly for
debugging.
This option is a kernel mount option so unable to be changed at
runtime.
runtime. It controls the `default_permissions` FUSE kernel option.
@@ -0,0 +1,27 @@
# lazy-umount-mountpoint
* type: `BOOL`
* default: `false`
* example: `lazy-umount-mountpoint=true`
When enabled mergerfs will issue a [lazy
umount](https://man7.org/linux/man-pages/man8/umount.8.html) request
to the mount point as it starts.
```
Lazy unmount. Detach the filesystem from the file hierarchy
now, and clean up all references to this filesystem as soon as
it is not busy anymore.
```
The value of using this is in situations where you wish to change
options which can only be set at initialization or upgrade mergerfs
itself. It also helps in the case of using `mount -a` which leads to a
new instance of mergerfs being mounted due to it being unable to
recognize that it is already mounted.
In theory mergerfs could notice that an instance is already mounted at
the mount point and attempt to use the runtime interface to modify the
options and only mounting a new instance if the version has changed or
initialization time only options are changed. However, given the
relative rarity of this being an issue it isn't a priority.
@@ -1,6 +1,6 @@
# link_cow
# link-cow
* `link_cow=true|false`
* `link-cow=true|false`
* Defaults to `false`
This feature offers similar functionality to what
+1 -1
View File
@@ -30,7 +30,7 @@ as appropriate and the original error returned.
If `moveonenospc` is disabled the underlying error will be returned.
NOTE: This feature has NO affect on policies. It ONLY applies to the
NOTE: This feature has NO effect on policies. It ONLY applies to the
literal write function. If the create function returns `ENOSPC` or the
[policy returns `ENOSPC`](functions_categories_policies.md#filtering)
that error will be returned back to the application making the
+41 -27
View File
@@ -30,9 +30,11 @@ config file.
## mount options
* **config**: Path to a config file. Same arguments as below in
key=val / ini style format.
`key` or `key=val` style format. `#` used for comments.
* **[branches](branches.md)**: Colon delimited list of branches. Used
primarily in config file.
primarily in a config file.
* **mountpoint=DIRPATH**: The path to the mountpoint. Used primarily
in a config file.
* **[branches-mount-timeout](branches-mount-timeout.md)=UINT**: Number
of seconds to wait at startup for branches to be a mount other than
the mountpoint's filesystem. (default: 0)
@@ -68,7 +70,7 @@ config file.
writable and its mtime or ctime is older than **symlinkify_timeout**
files will be reported as symlinks to the original files. Please
read more below before using. (default: false)
* **[symlinkify_timeout](symlinkify.md)=UINT**: Time to wait, in
* **[symlinkify-timeout](symlinkify.md)=UINT**: Time to wait, in
seconds, to activate the **symlinkify** behavior. (default: 3600)
* **nullrw=BOOL**: Turns reads and writes into no-ops. The request
will succeed but do nothing. Useful for benchmarking
@@ -86,13 +88,16 @@ config file.
* **[export-support](export-support.md)=BOOL**: Sets a low-level FUSE
feature intended to indicate the filesystem can support being
exported via NFS. (default: true)
* **security_capability=BOOL**: If false return ENOATTR when xattr
* **[kernel-permissions-check](kernel-permissions-check.md)=BOOL**:
Controls the FUSE `default_permissions` option. Primarily for
debugging. (default: true)
* **security-capability=BOOL**: If false return ENOATTR when xattr
security.capability is queried. (default: true)
* **[xattr](xattr.md)=passthrough|noattr|nosys**: Runtime control of
xattrs. Default is to passthrough xattr requests. 'noattr' will
short circuit as if nothing exists. 'nosys' will respond with ENOSYS
as if xattrs are not supported or disabled. (default: passthrough)
* **[link_cow](link_cow.md)=BOOL**: When enabled if a regular file is
* **[link-cow](link-cow.md)=BOOL**: When enabled if a regular file is
opened which has a link count > 1 it will copy the file to a
temporary file and rename over the original. Breaking the link and
providing a basic copy-on-write function similar to
@@ -101,7 +106,7 @@ config file.
means it will always use all branches in statfs calculations. 'full'
is in effect path preserving and only includes branches where the
path exists. (default: base)
* **statfs_ignore=none|ro|nc**: 'ro' will cause statfs calculations to
* **statfs-ignore=none|ro|nc**: 'ro' will cause statfs calculations to
ignore available space for branches mounted or tagged as 'read-only'
or 'no create'. 'nc' will ignore available space for branches tagged
as 'no create'. (default: none)
@@ -118,20 +123,20 @@ config file.
directory and symlink to it.
* **[readahead](readahead.md)=UINT**: Set readahead (in kilobytes) for
mergerfs and branches if greater than 0. (default: 0)
* **posix_acl=BOOL**: Enable POSIX ACL support (if supported by kernel
* **posix-acl=BOOL**: Enable POSIX ACL support (if supported by kernel
and underlying filesystem). (default: false)
* **async_read=BOOL**: Perform reads asynchronously. If disabled or
* **async-read=BOOL**: Perform reads asynchronously. If disabled or
unavailable the kernel will ensure there is at most one pending read
request per file handle and will attempt to order requests by
offset. (default: true)
* **[fuse_msg_size](fuse_msg_size.md)=PAGESIZE**: Set the max number of
* **[fuse-msg-size](fuse-msg-size.md)=PAGESIZE**: Set the max number of
pages per FUSE message. Only available on Linux >= 4.20 and ignored
otherwise. (min: 1; max: 65535; default: "1M")
* **[threads](threads.md)=INT**: Number of threads to use. When used
alone (`process-thread-count=-1`) it sets the number of threads
reading and processing FUSE messages. When used together it sets the
number of threads reading from FUSE. When set to zero it will
attempt to discover and use the number of logical cores. If the
* **[read-thread-count](threads.md)=INT**: Number of threads to
use. When used alone (`process-thread-count=-1`) it sets the number
of threads reading and processing FUSE messages. When used together
it sets the number of threads reading from FUSE. When set to zero it
will attempt to discover and use the number of logical cores. If the
thread count is set negative it will look up the number of cores
then divide by the absolute value. ie. threads=-2 on an 8 core
machine will result in 8 / 2 = 4 threads. There will always be at
@@ -139,7 +144,6 @@ config file.
`process-thread-count` then it will try to pick reasonable values
based on CPU thread count. NOTE: higher number of threads increases
parallelism but usually decreases throughput. (default: 0)
* **[read-thread-count](threads.md)=INT**: Alias for `threads`.
* **[process-thread-count](threads.md)=INT**: Enables separate thread
pool to asynchronously process FUSE requests. In this mode
`read-thread-count` refers to the number of threads reading FUSE
@@ -150,7 +154,7 @@ config file.
time. Meaning the total memory usage of the queues is queue depth
multiplied by the number of process threads plus read thread
count. 0 sets the depth to the same as the process thread
count. (default: 0)
count. (default: 2)
* **[pin-threads](pin-threads.md)=STR**: Selects a strategy to pin
threads to CPUs (default: unset)
* **[flush-on-close](flush-on-close.md)=never|always|opened-for-write**:
@@ -159,15 +163,17 @@ config file.
* **scheduling-priority=INT**: Set mergerfs' scheduling
priority. Valid values range from -20 to 19. See `setpriority` man
page for more details. (default: -10)
* **[proxy-ioprio](proxy-ioprio.md)=BOOL**: Use ioprio value from
calling threads when reading and writing. (default: false)
* **fsname=STR**: Sets the name of the filesystem as seen in
**mount**, **df**, etc. Defaults to a list of the source paths
concatenated together with the longest common prefix removed.
* **[func.FUNC](functions_categories_policies.md)=POLICY**: Sets the
specific FUSE function's policy. See below for the list of value
types. Example: **func.getattr=newest**
* **[func.readdir](func_readdir.md)=seq|cosr|cor|cosr:INT|cor:INT**: Sets `readdir`
policy. INT value sets the number of threads to use for
concurrency. (default: seq)
* **[func.readdir](func_readdir.md)=seq|cosr|cor|cosr:INT|cor:INT**:
Sets `readdir` policy. INT value sets the number of threads to use
for concurrency. (default: seq)
* **[category.action](functions_categories_policies.md)=POLICY**: Sets
policy of all FUSE functions in the action category. (default:
epall)
@@ -182,7 +188,7 @@ config file.
timeout in seconds. (default: 1)
* **[cache.entry](cache.md#cacheentry)=UINT**: File name lookup cache
timeout in seconds. (default: 1)
* **[cache.negative_entry](cache.md#cachenegative_entry)=UINT**:
* **[cache.negative-entry](cache.md#cachenegative-entry)=UINT**:
Negative file name lookup cache timeout in seconds. (default: 0)
* **[cache.files](cache.md#cachefiles)=off|partial|full|auto-full|per-process**:
File page caching mode (default: off)
@@ -201,15 +207,23 @@ config file.
with `cache.files=per-process` (if the process is not in
`process-names`) or `cache.files=off`. (Is a kernel feature added in
v6.2) (default: true)
* **[passthrough](passthrough.md)**: Enable [FUSE IO
* **[passthrough.io](passthrough.md)=ENUM**: Enable [FUSE IO
passthrough](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
if available. (default: off)
* **gid-cache-expire-timeout**: Number of seconds till supplemental
group data is refreshed in the [GID
cache](../known_issues_bugs.md#supplemental-user-groups). (default:
3600)
* **gid-cache-remove-timeout**: Number of seconds to wait till cached
data is removed due to lack of usage. (default: 43200)
* **passthrough.max-stack-depth=INT**: Set to `1` another filesystem
can be stacked on mergerfs. Set to `2` to have mergerfs stacked over
another filesystem. (default: 1)
* **never-forget-nodes=BOOL**: Keep the internal representation of
a file after the OS tells mergerfs it is no longer needed, until the
node is unlinked, removed, or replaced. Really only needed for
[exporting mergerfs via NFS](../remote_filesystems.md). Formerly
`noforget`. (default: false)
* **allow-idmap=BOOL**: Enables idmap (identity mapping). (default:
true)
* **debug**: Enable debugging information. At the moment limited to a
FUSE message trace saved to `log.file`. (default: false)
* **log.file**: The file path for the FUSE message trace. Empty string
will set to stderr. (default: stderr)
**NOTE:** Options are evaluated in the order listed so if the options
are **func.rmdir=rand,category.action=ff** the **action** category
+35 -28
View File
@@ -1,12 +1,12 @@
# passthrough
# passthrough.io
* default: `off`
* arguments:
* `off`: Passthrough is never enabled.
* `ro`: Only enable passthrough when file opened for reading only.
* `wo`: Only enable passthrough when file opened for writing only.
* `rw`: Enable passthrough when file opened for reading, writing,
or both.
* `ro`: Only enable IO passthrough when file opened for reading only.
* `wo`: Only enable IO passthrough when file opened for writing only.
* `rw`: Enable IO passthrough when file opened for reading, writing,
or both.
In [Linux 6.9](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
a IO passthrough feature was added to FUSE. Typically `mergerfs` has
@@ -14,7 +14,7 @@ to act as an active proxy for all read and write requests. This
results in, at times, significant overhead compared to direct
interaction with the underlying filesystems. Not because `mergerfs` is
doing anything particularly slow but because the additional
communication and data transfers required. With the `passthrough`
communication and data transfers required. With the `passthrough.io`
feature `mergerfs` is able to instruct the kernel to perform reads and
writes directly on the underlying file. Bypassing `mergerfs` entirely
(specifically and only for reads and writes) and thereby providing
@@ -31,46 +31,53 @@ features related to read/write are affected.
* [readahead](readahead.md): Still affects the `readahead` values of
underlying filesystems and `mergerfs` itself but no longer relevant
to `mergerfs` in that it is not servicing IO requests.
* [fuse_msg_size](fuse_msg_size.md): The primary reason to increase
* [fuse-msg-size](fuse-msg-size.md): The primary reason to increase
the FUSE message size is to allow transferring more data per request
which helps improve read and write performance. Without read/write
requests being sent to `mergerfs` there is little reason to have
larger message sizes since the only other message larger than 1
page, directory reading, currently has a small fixed buffer size.
* parallel-direct-writes: Not only is `direct-io` and `passthrough`
* parallel-direct-writes: Not only is `direct-io` and `passthrough.io`
mutually exclusive but since `mergerfs` no longer receives write
requests in passthrough mode then there is no parallel writes
possible.
* [cache.writeback](cache.md): FUSE's writeback caching is
incompatible with `passthrough`. If `cache.writeback=true` when
enabling `passthrough` it will be reset to `false`.
* [cache.files](cache.md): Must be enabled for `passthrough` to
incompatible with `passthrough.io`. If `cache.writeback=true` when
enabling `passthrough.io` it will be reset to `false`.
* [cache.files](cache.md): Must be enabled for `passthrough.io` to
work. When `cache.files=off` FUSE's `direct-io` mode is enabled
which overrides `passthrough.` Meaning `cache.files` should be set
to `partial`, `full` or `auto-full` to use `passthrough`. If
`cache.files` is set to `off` when using `passthrough` it will
which overrides `passthrough.io`. Meaning `cache.files` should be
set to `partial`, `full` or `auto-full` to use `passthrough.io`. If
`cache.files` is set to `off` when using `passthrough.io` it will
reset it to `auto-full`.
Technically `mergerfs` has the ability to choose options like
`passthrough`, `direct-io`, and page caching independently for every
file opened. However, at the moment there is no use case for picking
and choosing which to enable outside `cache.files=per-process` (which
is largely unnecessary on Linux v6.6 and above. See
`passthrough.io`, `direct-io`, and page caching independently for
every file opened. However, at the moment there is no use case for
picking and choosing which to enable outside `cache.files=per-process`
(which is largely unnecessary on Linux v6.6 and above. See
[direct-io-allow-mmap](options.md)) If such a use case arises please
reach out to the author to discuss.
[reach out to the author](../support.md) to discuss.
Unlike [preload.so](../tooling.md#preloadso), `passthrough` will work for
any software interacting with `mergerfs`. However, `passthrough`
requires Linux v6.9 or above to work.
Unlike [preload.so](../tooling.md#preloadso), `passthrough.io` will
work for any software interacting with `mergerfs`. However,
`passthrough.io` requires Linux v6.9 or above to work.
**NOTE:** This feature will **ONLY** work if `mergerfs` is running as
`root` as currently only `root` is allowed to leverage the kernel
feature.
**NOTE:** If a file has been opened and passthrough enabled, while that
file is open, if another open request is made `mergerfs` must also
enable `passthrough` for the second open request. This is a limitation
of how the passthrough feature works.
**NOTE:** If a file has been opened and `passthrough.io` enabled,
while that file is open, if another open request is made `mergerfs`
must also enable `passthrough.io` for the second open request. This is
a limitation of how the passthrough feature works.
**NOTE:** In order to add `passthrough.io` feature to `mergerfs` it
was necessary to remove the "feature" where mergerfs could open the
same file on different branches. Such as using `func.open=rand` and
having multiple files at the same relative path across different
branches. This "feature" was very very rarely used and it was
impossible to support `passthrough.io` without changing that behavior.
## Alternatives
@@ -96,7 +103,7 @@ Summary: passthrough is ~95% native speed. 200% faster than
* straight to tmpfs: 1.7 GB/s
* nullrw=true: 2.1 GB/s
* cache.files=off; passthrough=off: 800 MB/s
* cache.files=auto-full; passthrough=rw: 1.6 GB/s
* cache.files=off; passthrough.io=off: 800 MB/s
* cache.files=auto-full; passthrough.io=rw: 1.6 GB/s
* cache.files=auto-full; cache.writeback=false: 518 MB/s
* cache.files=auto-full; cache.writeback=true: 518 MB/s
+53
View File
@@ -0,0 +1,53 @@
# proxy-ioprio
* type: `BOOL`
* default: `false`
* example: `proxy-ioprio=true`
In Linux certain process schedulers have the ability to
[prioritize](https://man7.org/linux/man-pages/man2/ioprio_set.2.html)
based on a per thread [IO
priority](https://www.kernel.org/doc/Documentation/block/ioprio.txt)
assigned to the software by users using tools such as
[ionice](https://man7.org/linux/man-pages/man1/ionice.1.html). Since
such details are not provided by the FUSE protocol, users may not use
schedulers which support IO priority, and the extra overhead to query
the priority and set it within mergerfs it does not by default attempt
to mirror/proxy the value. When enabled however, for all read and
write requests, mergerfs will query the priority of the requesting
process/thread and set the same value on the thread within mergerfs
making the read/write.
This may be useful in situation where the system has high IO load or
processes are known to have varying priorities. See the [man
page](https://man7.org/linux/man-pages/man2/ioprio_set.2.html) for
specific details.
Keep in mind that if no IO scheduler has been set for a thread then by
default the IO priority will match the CPU nice value which for
mergerfs is set by [scheduling-priority](options.md). [Nice
value](https://man7.org/linux/man-pages/man2/getpriority.2.html)
proxying may be added in a future release.
## Conflicts With Other Options
If using [IO passthrough](passthrough.md) there is no reason to set
this value to `true`. However, since `passthrough.io` leads to
mergerfs IO calls being bypassed entirely will have no impact on
performance or behavior regardless.
When using `passthrough.io` the IO priority of the client app would be
used directly given the IO is passed through.
## Supported Platforms
Only Linux. This is not supported on FreeBSD.
## Performance Impact
Despite the additional syscalls requires the impact appears
undetectable with larger buffer sizes. At very small buffer sizes
(such as 512 bytes) there is a noticable but small impact (~1.5%).
+1 -1
View File
@@ -6,7 +6,7 @@ value unit is in kibibytes.
* `readahead=1024`
While the max size of messages sent between the kernel and mergerfs is
configurable via the [fuse_msg_size](fuse_msg_size.md) option that
configurable via the [fuse-msg-size](fuse-msg-size.md) option that
doesn't mean that is the size used by the kernel for read and
writes.
+2 -1
View File
@@ -2,7 +2,8 @@
If using path preservation and a `rename` fails with `EXDEV`:
1. Move file from `/branch/a/b/c` to `/branch/.mergerfs_rename_exdev/a/b/c`.
1. Move file from `/mnt/hdd/disk0/a/b/c` to
`/mnt/hdd/disk0/.mergerfs_rename_exdev/a/b/c`.
2. symlink the rename's `newpath` to the moved file.
The `target` value is determined by the value of `rename-exdev`.
+2 -2
View File
@@ -55,6 +55,6 @@ returned but it will still be possible.
## Additional Reading
* [Do hardlinks work?](../faq/technical_behavior_and_limitations.md#do-hardlinks-work)
* [Do hardlinks work?](../faq/technical_behavior_and_limitations.md#do-hard-links-work)
* [How does mergerfs handle moving and copying of files?](../faq/technical_behavior_and_limitations.md#how-does-mergerfs-handle-moving-and-copying-of-files)
* [Why are file moves and renames failing?](../faq/why_isnt_it_working.md#why-are-file-moves-renames-linkshardlinks-failing)
* [Why are file moves and renames failing?](../faq/why_isnt_it_working.md#why-are-file-moves-renames-linkshard-links-failing)
+12 -5
View File
@@ -88,11 +88,18 @@ filesystems. Mainly `ext4`.
## Logging
Filesystems, and therefore mergerfs, are doing lots of small actions
at high speed. It simply isn't reasonable to log all the actions of
the system. That said: certain details are logged at startup and when
performing maintenance tasks. These are logged via `syslog` and on
`systemd` based systems can be viewed by running
Filesystems, and therefore mergerfs, are doing **lots** of small
actions at high speed. It simply isn't reasonable to log all the
actions of the system all the time. However, there is a debug mode
which can be toggled on as needed, even at runtime, which will record
some information to help with debugging. The main thing it will record
is a trace of all FUSE messages to the location defined by the
`log.file` option. See [config section](config/options.md) for more
details.
That said even with debug mode disabled mergerfs will log certain
details via `syslog` which on `systemd` based systems can be viewed by
running:
```
journalctl -t mergerfs
+23 -5
View File
@@ -14,11 +14,11 @@ bottlenecked by their network, internet connection, or limited size of
the cache. However, there are a few situations where a tiered cache
setup could help.
1. Fast network, slow filesystems, many readers: You've a 10+Gbps
1. Fast network, slow filesystems, many readers: You've a 10Gbps+
network with many readers and your regular filesystems can't keep
up.
2. Fast network, slow filesystems, small'ish bursty writes: You have
a 10+Gbps network and wish to transfer amounts of data less than
a 10Gbps+ network and wish to transfer amounts of data less than
your cache filesystem but wish to do so quickly and the time
between bursts is long enough to migrate data.
@@ -27,8 +27,8 @@ level that can aggregate performance or using higher performance
storage would probably be the better solution. If you're going to use
mergerfs there are other tactics that may help: spreading the data
across filesystems (see the mergerfs.dup tool) and setting
`func.open=rand`, using `symlinkify`, or using dm-cache or a similar
technology to add tiered cache to the underlying device itself.
`func.open=rand` or using dm-cache or a similar technology to add
tiered cache to the underlying device itself.
With #2 one could use a block cache solution as available via LVM and
dm-cache but there is another solution which requires only mergerfs, a
@@ -52,15 +52,27 @@ script to move files around, and a cron job to run said script.
* Set your programs to use the **cache** pool.
* Configure the **base** pool with the `create` policy you would like
to lay out files as you like.
* Save one of the below scripts or create your own. The script's
* Use monstermuffin's
[mergerfs-cache-mover](https://github.com/monstermuffin/mergerfs-cache-mover),
one of the scripts below, or create your own. The script's
responsibility is to move files from the **cache** branches (not
pool) to the **base** pool.
* Use `cron` (as root) to schedule the command at whatever frequency
is appropriate for your workflow.
**NOTE:** Due to the additional overhead it is not recommended to nest
or otherwise create hierarchies of mergerfs pools. It will work but
the latency increases will further harm performance. Even when using
passthrough IO or other features.
### time based expiring
**NOTE:** this script is an **example**, not particularly efficient, and
really not intended for production deployments. Until such time that
mergerfs provides tooling for this usecase consider using
[mergerfs-cache-mover](https://github.com/monstermuffin/mergerfs-cache-mover).
Move files from cache filesystem to base pool which have an access
time older than the supplied number of days. Replace `-atime` with
`-amin` in the script if you want minutes rather than days.
@@ -92,6 +104,12 @@ respectively.
### percentage full expiring
**NOTE:** this script is an **example**, not particularly efficient, and
really not intended for production deployments. Until such time that
mergerfs provides tooling for this usecase consider using
[mergerfs-cache-mover](https://github.com/monstermuffin/mergerfs-cache-mover).
While the cache filesystem's percentage full is above the provided
value move the oldest file from the cache filesystem to the base pool.
+140 -31
View File
@@ -5,16 +5,10 @@
[Primarily Linux.](../setup/installation.md) FreeBSD is casually
supported but not well tested.
With FreeBSD certain Linux functions and FUSE features are not
supported. In many cases the absense will not be noticed however
performance may be impacted due to a core mergerfs design decision
relying on a Linux feature.
Linux allows individual threads to change credentials whereas [FreeBSD
does not](https://wiki.freebsd.org/Per-Thread%20Credentials). As a
result mergerfs must use a lock to ensure critical sections which need
to change credentials are safeguarded. This will limit throughput on
systems where requests to mergerfs come from multiple user identities.
With FreeBSD certain Linux functions and FUSE features [are not
supported.](../known_issues_bugs.md#freebsd-version) In many cases the
absence will not be noticed however more advanced features will not be
available.
### Why not support MacOS?
@@ -36,33 +30,90 @@ level API.
Windows, while used for NAS systems more often than MacOS, is still
relatively uncommon when compared to Linux. [Drive
Pool](../project_comparisons.md#stablebits-drivepool) is a reasonable
alternative.
Pool](../project_comparisons.md#stablebits-drivepool) is a good
alternative. It may also be possible to use mergerfs with WSL.
## What filesystems can be used as branches?
ext4, btrfs, xfs, f2fs, zfs, nfs, etc.
On the surface any filesystem should work but there could be issues
with non-POSIX compliant filesystems such as vfat, ntfs, cifs, exfat,
etc. When directories need to be created or files moved by mergerfs if
the filesystem returns errors due to not supporting certain POSIX
filesystem features it could result in the core functions failing.
Most any filesystem should work but there could be issues with
non-POSIX compliant filesystems such as nfs (w/ root squashing), vfat,
ntfs, cifs, exfat, etc. When directories need to be created or files
moved by mergerfs if the filesystem returns errors due to not
supporting certain POSIX filesystem features it could result in the
core functions failing.
Since mergerfs is not generally used with non-POSIX filesystems this
has not been a problem for users and there are some checks for known
edgecases but it is possible some are not accounted for. If use with a
edge cases but it is possible some are not accounted for. If use with a
filesystem results in issues please [file a
ticket](https://github.com/trapexit/mergerfs/issues) with the details.
## What types of storage devices does mergerfs work with?
Since mergerfs works at the filesystem level rather than device level
there is no compatibility concerns based on the physical storage
devices involved.
## Can I use mergerfs without SnapRAID? SnapRAID without mergerfs?
[https://www.snapraid.it](https://www.snapraid.it)
Yes. They are completely unrelated pieces of software that just happen
to work well together.
## How does mergerfs interact with SnapRAID recovery behavior?
SnapRAID does not choose parity layout based on directory structure or
relative path. As each file is discovered, SnapRAID fills parity space
for that file based on its size using the next available parity area.
This makes the logical parity layout depend on discovery order, file
size, and how much data already exists on each filesystem at that
time. Filesystems with similar used space, regardless of total
capacity, are more likely to place newly added files into similar
parity positions.
Because of that, if files are changed or removed across multiple
branches and those files happen to overlap in parity space, recovery
of those deleted files may be less reliable. This is not a general
concern, is not unique to mergerfs usage, and it does not affect
filesystem failure recovery. It is specifically a concern when trying
to recover from accidental deletion of files spread across multiple
filesystems.
In practice this mostly matters when the filesystems have very similar
used space, the files are created across those filesystems around the
same time, and you later want SnapRAID to recover an accidental
deletion spanning multiple filesystems. Otherwise this is usually not
something to worry about.
If, however, this is a concern it can be beneficial to keep files on
as few filesystems as possible or otherwise keep related files
together. See the [collocation
section.](configuration_and_policies.md#how-can-i-ensure-files-are-collocated-on-the-same-branch)
## Can I use mergerfs without NonRAID? NonRAID without mergerfs?
[https://github.com/qvr/nonraid](https://github.com/qvr/nonraid)
Yes. They are completely unrelated pieces of software that just happen
to work well together.
### How does mergerfs interact with NonRAID?
There are no known special interactions or concerns between mergerfs
and NonRAID. They are separate tools and mergerfs has no known
behavior which requires any special configuration or caveats when used
with NonRAID.
## Does mergerfs support CoW / copy-on-write / writes to read-only filesystems?
Not in the sense of a filesystem like BTRFS or ZFS nor in the
@@ -80,16 +131,8 @@ pool.
## Can mergerfs run via Docker, Podman, Kubernetes, etc.
Yes. With Docker you'll need to include `--cap-add=SYS_ADMIN
--device=/dev/fuse --security-opt=apparmor:unconfined` or similar with
other container runtimes. You should also be running it as root or
given sufficient caps to allow mergerfs to change user and group
identity as well as have root like filesystem permissions. This
ability is critical to how mergerfs works.
Also, as mentioned by [hotio](https://hotio.dev/containers/mergerfs),
with Docker you should probably be mounting with `bind-propagation`
set to `slave`.
Yes. [See installation
page.](../setup/installation.md#podman-docker-oci-containers)
## How does mergerfs interact with user namespaces?
@@ -106,13 +149,79 @@ It is generally recommended to not use user namespacing / id mapping
given the complication it introduces.
## Does mergerfs work with SELinux relabeling?
FUSE filesystems, including mergerfs, do not support per-inode
SELinux labels (see [Red Hat bug
1272868](https://bugzilla.redhat.com/show_bug.cgi?id=1272868) and the
[upstream fuse-devel
thread](https://sourceforge.net/p/fuse/mailman/fuse-devel/thread/CAN9vWDLGA4%3Dg3peKVWkX7gLRiVUUvJAQx9kiXHgqdeX6eh0MYA%40mail.gmail.com/)).
When a container runtime such as Podman or Docker is asked to relabel
a bind mount with the `:z` or `:Z` mount option, it first probes the
source for `security.selinux` xattr support. On a FUSE path that
probe returns `EOPNOTSUPP` and the runtime skips relabeling entirely.
Files inside the mount keep the `fusefs_t` type assigned by the
kernel. No tree walk is performed and no journal noise is produced at
default verbosity. The runtime logs `Labeling not supported on
"<path>"` only at debug level (for example
`podman --log-level=debug`). The flag is therefore a no-op against a
mergerfs mount; not harmful, but misleading.
Containers can still read and write a mergerfs pool without `:z`
because two independent access checks must succeed and both can be
satisfied without any relabel:
* **SELinux:** on Fedora, RHEL / CentOS Stream, and SUSE the shipped
[container-selinux](https://github.com/containers/container-selinux)
policy already permits the `container_t` process type to manage
files of type `fusefs_t`. See `container_selinux(8)`.
* **DAC:** the in-container UID and GID must still be able to read
and write the files on the underlying branches. With rootless
Podman this is most easily arranged by aligning the container UID
with the host owner of the branches (for example
`--userns=keep-id`, or Quadlet `UserNS=keep-id`).
The recommendation is to omit `:z` and `:Z` on bind mounts whose
source is a mergerfs (or any other FUSE) path, and to keep them on
bind mounts whose source is a regular local filesystem (ext4, btrfs,
xfs, etc.) where relabeling does real work. On distributions whose
container-selinux policy does not already include the `fusefs_t`
allow rule, the targeted fix is to set a single label at mount time
by adding `context="system_u:object_r:container_file_t:s0"` to the
mergerfs mount options (for example in `/etc/fstab`). Files inside
the pool then appear as `container_file_t`, which container-selinux
permits `container_t` to manage everywhere. See [Red Hat's mounting
file systems
guide](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-working_with_selinux-mounting_file_systems).
This is preferred over the much broader
`--security-opt label=disable`, which removes SELinux confinement
from the container as a whole. The trade-off of `context=` is that
host-side processes touching the mount (rsync, backup tools, web
UIs, cron jobs) see `container_file_t` rather than `fusefs_t` and
may require a small SELinux policy adjustment.
To verify on your own system:
```
podman --log-level=debug run --rm \
-v /path/to/mergerfs:/data:z \
alpine true 2>&1 | grep -iE 'relabel|setfilecon|Labeling'
```
Expected output is a single line of the form
`Labeling not supported on "/path/to/mergerfs"` and nothing in
`ausearch -m AVC,SELINUX_ERR` for the mount.
## Can mergerfs be used with NFS root squash?
If mergerfs is pooling a NFS mount then root squash should be disabled
If mergerfs is pooling a NFS mount then root squash must be disabled
as mergerfs needs to be able to have elevated privileges to do what it
does.
does (set permissions, ownership, etc.)
If you are exporting mergerfs over NFS then it is not really necessary.
If you are exporting mergerfs over NFS it is not strictly necessary to
disable root squashing but 3rd party software running as root may
fail.
See the [section on remote filesystems.](../remote_filesystems.md)
+82 -27
View File
@@ -18,37 +18,88 @@ mergerfs [does not impact](usage_and_functionality.md) the underlying
filesystems and can be added or removed without any impact it is
extremely easy to test and experiment with different settings.
[mergerfs-webui](../tooling.md#mergerfs-webui) provides a browser-based
way to inspect and change settings, branches, and policies on Linux,
and edit startup configuration. It does not replace understanding
what the options do or choosing policies suited to your use case.
Runtime changes alone do not persist across remounts.
Additional reading:
* [Config/Options](../config/options.md)
## What create policy should I use?
If it isn't clear to you then you should use `pfrd` as described in
the [quickstart guide](../quickstart.md).
## Why are there no policies based on file size?
Because it is not possible to know the final size of a file at creation
time.
See [How does mergerfs handle moving and copying of
files?](technical_behavior_and_limitations.md#how-does-mergerfs-handle-moving-and-copying-of-files)
and [Intro to Filesystems - Creating a
file](../intro_to_filesystems.md#creating-a-file).
## Why is pfrd the default create policy?
Originally the default was `epmfs` however it was found to cause
significant confusion among new users who expected mergerfs to always
choose a branch with available space. For mergerfs v2.41.0 it was
decided to change the default to the less restrictive policy `pfrd`.
significant confusion among new users who assumed mergerfs to behave
like other software which always choose a branch with most available
space without any filtering or restrictions. The reason for using
`epmfs` was to allow people to use mergerfs without impacting the
layout of their data across the branches. Keeping the total entropy of
the system in check. This, however, was not understood by users who
did not read the documentation and caused significant support burdens.
For mergerfs v2.41.0 it was decided to change the default to `pfrd` to
minimize the support burden.
`pfrd` was chosen because it prioritizes placement to branches based
on free space (percentage wise) without overloading a specific branch
as `mfs`, `lus`, or other policies could when a singular branch has
significantly more free space. Since it is impossible to know the
usage and access patterns of the filesystem spreading out files across
the branches also spreads the load and therefore good for general use
cases making it a good default.
Additional reading:
* [functions, categories and policies](../config/functions_categories_policies.md)
## How can I ensure files are collocated on the same branch?
Many people like the idea of ensuring related files, such as all the
files to a TV show season or songs in an album, are stored on the same
storage device. However, most people have no actual need for this
storage device. However, most people have no technical need for this
behavior.
1. If you backup your data it is extremely likely your backup solution
can restore only those files you are missing.
2. Software such as **Sonarr** can manage the downloading and post
processing of bespoke episodes which may be missing in a
season. Either by downloading the episode individually if available
or by downloading a full season.
can restore only those files you are missing. There is no need to
store data in related units to ensure restoring data is easy.
2. Software such as [Sonarr](https://sonarr.tv) can manage the
downloading and post processing of bespoke files / episodes which
may be missing. Either by downloading the episode individually if
available or by downloading a full season.
3. There is no benefit to keeping files collocated with regard to
drive spinup, caching, or other secondary concern.
drive spinup, caching, or other secondary concern. In fact
depending on usage patterns placing the data on the same underlying
device could harm performance.
The main use case for wanting collocation is where the branch is going
to be removed from the pool and you wish to have all data from some
logical set on that device. Such as you intend to take a drive out of
the pool to take on a trip and want a whole show on the
drive. However, even in these situations you typically end up needing
to curate the files anyway because it has show A but not show B.
to be removed from the pool and used in isolation temporarily and you
wish to have all data from some logical set on that device. Such as
you intend to take a drive out of the pool to take on a trip and want
a whole show or artist's works on the drive. However, even in these
situations you typically end up needing to curate the files anyway
because it has show A but not show B. In such cases it is more
convient to use a separate devices and sync what you wish to it.
All that said you can accomplish collocation to varying degrees using
the following methods:
@@ -56,33 +107,37 @@ the following methods:
1. Use
[mergerfs.consolidate](https://github.com/trapexit/mergerfs-tools/blob/master/src/mergerfs.consolidate)
when consolidation is needed.
2. Use a `msp` create policy.
2. Use a `msp` create policy to do a best effort collocation without
needing explicit directory curation.
3. Use `epmfs` or other `ep` create policy and manually create paths
on the branches directly.
4. Use a `ep` `create` policy and `rand` for `mkdir`.
on the branches directly as you need/desire.
4. Use a `ep` `create` policy and `pfrd` or `rand` for `mkdir`.
## How can I balance files across the pool?
Similar to collocation there is generally little reason to balance
files.
files across the pool branches.
1. Since prediction of a filesystem's death or loss of data is near
impossible there is little reason to balance in hopes of limiting
data loss.
1. Since prediction of a device's/filesystem's death or loss of data
is near impossible there is little reason to balance in hopes of
limiting data loss.
2. While performance could be impacted by having too much reading or
writing happen to singular underlying filesystems balancing won't
help unless you have the ability to manage the access patterns to
the pool.
3. Over time most configurations will lead to a random distribution of
files across the branches which is effectively "balancing."
3. Over time the churn of adding and removing files will lead to a
random distribution of files across the branches which is
effectively "balancing" if using create policies which do not favor
any particular branch.
If you wish to move files around or balance the pool you can:
All that said if you wish to move files around or balance the pool you
can:
1. Use `rand` or `pfrd` create policies and just use your system as
normal.
2. Write simple scripts using rsync or similar to move files around as
you wish.
2. Write scripts using rsync or similar to move files around as you
wish.
3. Use
[mergerfs.balance](https://github.com/trapexit/mergerfs-tools/blob/master/src/mergerfs.balance). Keep
in mind that this tool is really just an example of how to
+218
View File
@@ -0,0 +1,218 @@
# Downsides
## What are the downsides of using mergerfs?
mergerfs trades the features of an integrated storage system for the
flexibility of pooling independent filesystems. It is a filesystem
proxy, not RAID or a replacement for the underlying filesystems.
Some limitations are inherent to that design; others matter only for
particular workloads, configurations, or applications. Many can be
mitigated with appropriate settings or separate tools. The following
is a checklist of tradeoffs to evaluate, not a list of problems every
installation will experience.
### No built-in data protection or storage management
mergerfs does not provide redundancy, parity, integrity checks,
snapshots, or file versioning. If a drive fails, mergerfs cannot
recover the files on it. The other drives retain their independent
filesystems, but that failure isolation is not redundancy or a backup.
Protection must come from the underlying storage or separate tools,
such as [SnapRAID](https://www.snapraid.it) or
[NonRAID](https://github.com/qvr/nonraid).
It also does not automatically rebalance existing files when a branch
is added or space usage changes. If redistribution is needed, it must
be done separately.
See [Non-features](../index.md#non-features), [Project
Comparisons](../project_comparisons.md), and [How can I balance files
across the pool?](configuration_and_policies.md#how-can-i-balance-files-across-the-pool).
### No striping or contiguous pooled space
Each file lives entirely on one branch. Adding drives does not make a
single-file transfer faster, and a file must fit on one eligible
branch even if the pool reports enough total free space. For example,
two filesystems with 6GiB free each cannot hold one 10GiB file.
Concurrent access to different branches can still provide aggregate
throughput.
See [Project Comparisons](../project_comparisons.md#raid0-jbod-span-drive-concatenation-striping)
and [Performance](../performance.md#understanding-mergerfs-io-performance).
### Performance overhead
Filesystem requests generally pass through FUSE and the mergerfs
process before reaching the underlying filesystem. This adds latency
and CPU overhead compared to direct access. Directory listings and
other metadata operations can require work across multiple branches,
so their cost can grow as the pool grows. Page caching can also result
in data being cached both through mergerfs and on the underlying
filesystem.
[IO passthrough](../config/passthrough.md) can reduce data IO overhead,
but does not eliminate the work of combining branches. Databases, VM
images, and other latency-sensitive workloads should be benchmarked
with the intended configuration rather than assumed to perform like
direct access.
There is also memory overhead beyond page caching: request buffers,
temporary directory buffers, and FUSE node bookkeeping. Usage depends
on concurrency, buffer sizes, and the workload. Exporting the pool
over NFS requires retaining nodes with `never-forget-nodes=true`,
which can increase memory usage as more entries are accessed. This is
worth considering on memory-constrained systems or large NFS exports.
See [Resource Usage](../resource_usage.md) and [NFS exporting
mergerfs](../remote_filesystems.md#nfs-exporting-mergerfs).
See [Understanding mergerfs IO
Performance](../performance.md#understanding-mergerfs-io-performance),
[Benchmarking](../benchmarking.md), and [Why use
FUSE?](technical_behavior_and_limitations.md#why-use-fuse-why-not-a-kernel-based-solution).
### Placement and filesystem semantics need attention
A single mount point does not make the branches one filesystem.
[Policies](../config/functions_categories_policies.md) determine where
new files go and which copy is used when the same path exists on
multiple branches. Path-preserving policies and
[minfreespace](../config/minfreespace.md) can exclude branches from file
creation even while the pool has free space. mergerfs cannot know a
file's final size when it is created.
Hard links and renames still have underlying filesystem constraints.
Depending on the layout and policies, an operation can return `EXDEV`
(cross-device link) even within the pool. Applications which do not
handle that error may fail rather than fall back to copying.
Directory metadata is also selected by policy rather than necessarily
aggregated. With the default `getattr` policy of `ff`, a directory's
timestamp comes from the first matching branch. A media scanner which
uses that timestamp to skip unchanged directories can miss updates on
another branch. `func.getattr=newest` can help with timestamp-based
scans, but changes which branch supplies metadata; it does not change
which file other operations select.
Corresponding directories on different branches can have different
ownership or permissions. Since access uses the caller's credentials,
this can result in unexpected access errors or missing entries.
Keeping those permissions consistent and auditing with
`fsck.mergerfs` helps. Container UID/GID mappings and non-POSIX
branches may need additional attention; mergerfs does not bypass
their access restrictions.
See [Configuration and Policies](configuration_and_policies.md),
[rename and link](../config/rename_and_link.md), [Tips and
Notes](../tips_notes.md), and [How does mergerfs interact with user
namespaces?](compatibility_and_integration.md#how-does-mergerfs-interact-with-user-namespaces).
### Keeping drives spun down can be harder
Directory listings and file searches can query multiple branches,
potentially waking drives beyond the one containing the requested
file. If keeping disks asleep is important, this can increase power
usage, noise, and access latency. It is not a concern for SSDs or
drives which are kept spinning.
Caching and reducing background scans can limit some branch access,
but mergerfs alone cannot guarantee that unused disks stay asleep.
See [Limiting drive spinup](limit_drive_spinup.md) for the tradeoffs
and possible approaches.
### Direct branch changes and cached views
Using the underlying filesystems directly is supported, but changes
made outside the mergerfs mount are not forwarded as `inotify` or
`fanotify` events to applications watching the pool. Changes made
through the pool do generate notifications. If files are added
directly to branches, applications such as media servers may need
periodic scans, explicit refreshes, or watches on the branches
themselves.
Metadata caching introduces a separate visibility tradeoff: direct
branch changes may not appear through the pool until cached entries
expire. Longer cache durations can reduce filesystem queries at the
cost of less immediate visibility. Shorter durations or disabling the
relevant caches can help if direct branch changes are common. Avoid
uncoordinated writes to the same file through both paths, especially
when file caching is enabled.
See [Does inotify and fanotify
work?](compatibility_and_integration.md#does-inotify-and-fanotify-work),
[Can filesystems still be used
directly?](usage_and_functionality.md#can-filesystems-still-be-used-directly-outside-of-mergerfs-while-pooled),
and [Caching](../config/cache.md).
### Branch paths do not guarantee a particular disk is mounted
mergerfs works on paths and does not require each branch to be a
mount point. If an intended branch filesystem is not mounted but its
mount-point directory still exists, mergerfs can use that directory
on the filesystem beneath it. If writable and selected for creation,
new files can land there instead of on the intended disk, potentially
filling the root filesystem.
This is a deployment concern when pooling mounted filesystems, not a
problem with intentionally using ordinary directories as branches.
Configure mount dependencies or
`branches-mount-timeout` with `branches-mount-timeout-fail=true` if
startup should fail when branches are not ready. These are startup
safeguards, not continuous checks that the intended disks remain
mounted at runtime.
See [branches-mount-timeout](../config/branches-mount-timeout.md) and
[What happens if a filesystem disappears at
runtime?](usage_and_functionality.md#what-happens-if-a-filesystem-disappears-at-runtime).
### Dependence on branch responsiveness
mergerfs cannot make an unresponsive disk or remote filesystem
responsive. A request to a blocked branch blocks the thread handling
it. If enough threads are blocked, the pool can stop responding as
well. Independent on-disk filesystems do not guarantee that a failing
branch has no effect on access through the pool.
See [What happens if a branch filesystem
blocks?](technical_behavior_and_limitations.md#what-happens-if-a-branch-filesystem-blocks).
### Compatibility limitations
Some filesystem features are unavailable through FUSE or require
particular kernel versions and settings. For example, reflink requests
(`FICLONE` / `FICLONERANGE`) are not supported through the mergerfs
mount. Applications using `mmap` need page caching; older Linux or
mergerfs versions require it to be explicitly enabled.
Advisory locks taken through the pool are not forwarded to the
underlying branches. Local applications using only the mergerfs mount
still have kernel-managed locks, but those locks do not coordinate
with direct branch access or other hosts accessing a remote branch.
Inode identity also has tradeoffs when presenting multiple filesystems
as one. Most applications do not need special settings, but backup or
deduplication tools and NFS can be sensitive to inode values changing
when files move between branches or a policy selects a different
copy. [inodecalc](../config/inodecalc.md) offers different strategies:
for example, `path-hash` stabilizes identity by path for NFS, but
prevents applications from recognizing hard links by inode equality.
The hard links themselves still work.
On SELinux systems, per-file relabeling is unavailable through the
FUSE mount, so container bind-mount options `:z` and `:Z` do not
perform their usual relabeling. This does not mean containers cannot
use the pool: an appropriate SELinux policy or a mount-wide context
can allow access. A mount-wide context is not a substitute for
distinct per-file labels if the application requires them. See [Does
mergerfs work with SELinux
relabeling?](compatibility_and_integration.md#does-mergerfs-work-with-selinux-relabeling).
See [Technical Behavior and
Limitations](technical_behavior_and_limitations.md) and [Known Issues
and Bugs](../known_issues_bugs.md).
These tradeoffs do not require migrating data into a special format.
The underlying filesystems remain usable without mergerfs, and
mergerfs can be [removed without changing their
data](usage_and_functionality.md#can-mergerfs-be-removed-without-affecting-the-data).
+109 -34
View File
@@ -1,22 +1,31 @@
# Limiting drive spinup
## Does mergerfs do anything to prevent drive spinup?
No. In fact it makes it worse.
## How can I setup my system to limit drive spinup?
TL;DR: You really can't. Not through mergerfs alone. In fact mergerfs
makes an attempt to do so more complicated.
TL;DR: You really can't. Not through mergerfs alone. In fact using
mergerfs makes any general attempt to limit spinup more complicated.
mergerfs is a proxy. Despite having some caching behaviors it is not
designed to cache much more than metadata. It proxies calls between
client software and underlying filesystems. If a client makes a
request such as `open`, `readdir`, `stat`, etc. it must translate that
into something that makes sense across multiple filesystems. For
`readdir` that means running the call against all branches and
aggregating the output. For `open` that means finding the file to open
and doing so. The only way to find the file to open is to scan across
all branches and sort the results and pick one. There is no practical
way to do otherwise. Especially given so many mergerfs users expect
out-of-band changes to "just work."
mergerfs is a proxy. Despite having [some caching
behaviors](../config/cache.md) it is not designed to cache much more
than metadata. It proxies calls between client software and underlying
filesystems. If a client makes a request such as `open`, `readdir`,
`stat`, etc. it must translate that into something that makes sense
across multiple filesystems. For `readdir` that means running the same
call against all branches and aggregating the output. For `open` that
means finding the file to open and doing so. The only way to find the
file to open is to scan across all branches and sort the results and
pick one. There is some variability in how much searching is done due
to
[policies](../config/functions_categories_policies.md#policy-descriptions)
but not much. There is no practical way to do otherwise. Especially
given so many mergerfs users expect out-of-band changes to "just
work."
The best way to limit spinup of drives is to limit their usage at the
client level. Meaning keeping software from interacting with the
@@ -33,21 +42,24 @@ filesystems and on startup everything scanned and stored. From then on
it would have to carefully update all the same data the filesystems
do. It couldn't be kept in RAM because it would take up too much space
so it'd have to be on a SSD or other storage device. If anything
changed out of band it would break things in weird ways. It could
rescan on occasion but that would require spinning up
everything. Filesystem watches could be used to get updates when the
filesystem changes but that would allow for race conditions and
might keep the drives from spinning down. Something as "simple" as
keeping the current available free space on each filesystem isn't as
easy as one might think given reflinks, snapshots, and other block
level dedup technologies as well as the space used includes not just
raw file usage.
changed out of band it would break things in weird ways (and MANY
users depend on out of band changes working.) It could rescan on
occasion but that would require spinning up everything. Filesystem
watches could be used but since mergerfs is just another piece of
software interacting with the filesystem ALL mergerfs requests would
be echoed back to it causing lots of overhead throwing away a super
majority of events it triggered. And something as "simple" as keeping
the current available free space on each filesystem is not as easy as
one might think given reflinks, snapshots, and other block level dedup
technologies distort the meaning of usage values.
Even if all metadata (including xattrs) is cached some software will
open files (media like videos and audio) to check their
metadata. Granted a Plex or Jellyfin scan which may do that is
different from a random directory listing but is still something to
consider. Those "deep" scans can't be kept from waking drives.
Only if ALL metadata and all file locations were cached could mergerfs
then do very targeted file operations so as not to trigger a scan
across the pool. But that will break out of band interactions. It
would require accepting possibly bad behavior by space based policies
since space would be imprecisely computed. It would require long scans
of the filesystems on startup and a separate location to store all
this data.
### What if you only query already active drives?
@@ -56,7 +68,8 @@ Let's assume that is plausible (it isn't because some drives actually
will spin up if you ask if they are spun down... yes... really) you
would have to either cache all the metadata on the filesystem or treat
it like the filesystem doesn't exist. The former has all the problems
mentioned prior and the latter would break a lot of things.
mentioned prior and the latter would break a lot of things. Suddenly
whole filesystems worth of data would be missing because it spun down.
### Is there anything that can be done where mergerfs is involved?
@@ -65,15 +78,77 @@ Yes, but whether it works for you depends on your tolerance for the
complexity.
1. Cleanly separate writing, storing, and consuming the data.
1. Use a SSD or dedicated and limited pool of drives for downloads / torrents.
2. When downloaded move the files to the primary storage pool.
3. When setting up software like Plex, Jellyfin, etc. point to the
1. Use a SSD or dedicated and limited pool of drives for downloads / torrents.
2. When downloaded move the files to the primary storage pool.
3. When setting up software like Plex, Jellyfin, etc. point to the
underlying filesystems. Not mergerfs.
2. Add a bunch of bcache, lvmcache, dm-cache, or similar block level
cache to your setup. After a bit of use, assuming sufficient
storage space, you can limit the likelihood of the underlying
spinning disks from needing to be hit.
3. Tweak your system's settings around caching:
* mount all your filesystems with `noatime` to disable updating
"last accessed" time on files.
* mount all your filesystems with `nodiratime` to disable updating
"last accessed" time on directories.
* `sudo sysctl -w vm.vfs_cache_pressure=10`: Prioritizes keeping
file metadata cached in RAM.
* `sudo sysctl -w vm.dirty_ratio=40`: Allows up to 40% of system
memory to be filled with unwritten "dirty" data before forcing a
disk flush.
* `sudo sysctl -w vm.dirty_background_ratio=5`: Sets a lower
threshold (5%) for the kernel to start writing data in the
background without blocking applications.
* `sudo sysctl -w vm.dirty_writeback_centisecs=6000`: Increases
the interval between kernel writeback checks to 60 seconds
(default is usually 5).
* `sudo sysctl -w vm.dirty_expire_centisecs=12000`: Allows data to
stay "dirty" in RAM for up to 120 seconds before it must be
written to disk.
* To make `sysctl` values permanent add the above to
`/etc/sysctl.conf`.
* **Note:** for systems with limited RAM the above `sysctl` values
could cause issues.
* **Note:** if you increase the time for which data is stored in
RAM before being flushed to disk there is also an increased risk
that a sudden powerloss could lead to lost data.
4. Enable all the appropriate caches in mergerfs:
* [https://trapexit.github.io/mergerfs/latest/config/cache/](https://trapexit.github.io/mergerfs/latest/config/cache/)
* `cache.entry=86400`: cache file existence for 24h.
* `cache.negative-entry=86400`: cache file non-existence for 24h.
* `cache.attr=86400`: cache file metadata for 24h.
* `cache.symlinks=true`: enable symlink caching.
* `cache.readdir=true`: enable directory listing caching.
Remember too that while it may be a tradeoff you are willing to live
with there is decent evidence that spinning down drives puts increased
wear on them and can lead to their death earlier than otherwise.
### References
* [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html)
* [How to Configure vm.swappiness and Other Virtual Memory Parameters on RHEL](https://oneuptime.com/blog/post/2026-03-04-configure-vm-swappiness-virtual-memory-rhel-9/view)
### Tradeoffs
While it may be a trade off you are willing to live with there is
decent evidence that spinning down drives puts material increased wear
on them and can lead to their death earlier than otherwise. You may
end up saving money on electricity but spending more on having to
purchase new drives sooner.
## Alternatives
[Project Comparisons](../project_comparisons.md) lists a few projects
that are similar to mergerfs but offer caching. Please note that these
projects are very young and not general purpose like mergerfs. Their
compliance to POSIX filesystem rules is probably low enough that
certain behaviors may not work or worse lead to data corruption.
I'm not suggesting not to look at those projects but filesystem
development is complicated even when it appears to be just a layer on
top of another. Just be careful and do your research. That stands for
anything touching your data... mergerfs included.
To the degree that mergerfs can add features which help in this
situation, so long as it doesn't interfere with other fundamental
features, they may be added over time.
@@ -1,53 +0,0 @@
# Recommendations and Warnings
## What should mergerfs NOT be used for?
* databases: Even if the database stored data in separate files
(mergerfs wouldn't offer much otherwise) the higher latency of the
indirection will really harm performance. If it is a lightly used
sqlite3 database then it should be fine.
* VM images: For the same reasons as databases. VM images are accessed
very aggressively and mergerfs will introduce a lot of extra latency.
* As replacement for RAID: mergerfs is just for pooling branches. If
you need that kind of device performance aggregation or high
availability you should stick with RAID. However, it is fine to put
a filesystem which is on a RAID setup in mergerfs.
**However, if using [passthrough](../config/passthrough.md) the above
situations are less likely to be a concern. Best to do testing for
your specific use case.**
## It's mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them?
[mhddfs](https://github.com/trapexit/mhddfs) manages running as
`root` by calling
[getuid()](https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319)
and if it returns `0` then it will
[chown](http://linux.die.net/man/1/chown) the file. Not only is that a
race condition but it doesn't handle other situations. Rather than
attempting to simulate POSIX ACL behavior the proper way to manage
this is to use [seteuid](http://linux.die.net/man/2/seteuid) and
[setegid](http://linux.die.net/man/2/setegid), in effect, becoming the
user making the original call, and perform the action as them. This is
what mergerfs does and why mergerfs should always run as root.
In Linux setreuid syscalls apply only to the thread. glibc hides this
away by using realtime signals to inform all threads to change
credentials. Taking after Samba, mergerfs uses
`syscall(SYS_setreuid,...)` to set the callers credentials for that
thread only. Jumping back to `root` as necessary should escalated
privileges be needed (for instance: to clone paths between
filesystems).
For non-Linux systems, mergerfs uses a read-write lock and changes
credentials only when necessary. If multiple threads are to be user X
then only the first one will need to change the processes
credentials. So long as the other threads need to be user X they will
take a readlock allowing multiple threads to share the
credentials. Once a request comes in to run as user Y that thread will
attempt a write lock and change to Y's credentials when it can. If the
ability to give writers priority is supported then that flag will be
used so threads trying to change credentials don't starve. This isn't
the best solution but should work reasonably well assuming there are
few users.
@@ -1,26 +1,76 @@
# Technical Behavior and Limitations
## Do hardlinks work?
## Do hard links work?
Yes. There is no option to enable or disable links (or renames.) They
are fundamentally supported in compatible situations. That said the
inode of a file is not necessarily indicative of two file names
linking to the same underlying data. See also the option `inodecalc`
for how inode values are calculated.
Yes. [links](https://en.wikipedia.org/wiki/Hard_link) (otherwise known
as hard links) are fundamentally supported by mergerfs. There is no
option to enable or disable links. All comments elsewhere claiming they
do not work are related to the setup of the user's system.
What mergerfs does not do is fake hard links across branches. Read the
section [rename & link](../config/rename_and_link.md) for how it
works.
See the [Intro to Filesystems](../intro_to_filesystems.md) page for more
details on what a `link` is.
Remember that hardlinks will **NOT** work across devices. That
includes between the original filesystem and a mergerfs pool, between
two separate pools of the same underlying filesystems, or bind mounts
of paths within the mergerfs pool. The latter is common when using
Docker or Podman. Multiple volumes (bind mounts) to the same
underlying filesystem are considered different devices. There is no
way to link or rename between them. You should mount in the highest
directory in the mergerfs pool that includes all the paths you need if
you want links and rename to work.
A limitation of a `link` is that it **must** reference a file/inode
within the same filesystem or device. When a request is made to the
operating system to `link` (or `rename`) a file to a target location
the OS will first check if the source and target are the same
filesystem / device. If not it will return the error `EXDEV` meaning
that the request is not able to be made due to the result being "cross
device".
What catches many users off guard is that this "cross device" check is
not very deep or involved. Even the same filesystem bind mounted to
another location will typically be considered a different device. So
when mergerfs is creating a union of other filesystems there is no way
to link or rename from one of the underlying branches into
mergerfs. They are different filesystems even if logically they seem
related or the same. And similarly it is impossible for mergerfs to
truly make a link between two branches which are not in fact the same
mounted filesystem.
This is relevant to mergerfs users because bind mounts are very common
when using container runtimes such as Docker, Podman, or
Kubernetes. When you add a volume or mount to your container that is
in fact a bind mount of that path. Meaning if you were to add
`/mnt/mergerfs/downloads` and `/mnt/mergerfs/movies` as separate
mounts into your container any request to link between the two
locations within the container will fail with the `EXDEV` error even
though it will work perfectly fine on the host. If you wish for `link`
and `rename` to work you **MUST** mount into the container a point in
the directory structure above what you wish to use. In this case
`/mnt/mergerfs`.
Another situation where people will hit unexpected `EXDEV` errors is
when they use a policy which makes file creation decisions based on
existing paths. If the source of a `link` or `rename` is on branch A
but the target directory is on branch B, given the whole point of the
policy is to strictly enforce behavior based on what branches already
exist, it must return `EXDEV`. Doing otherwise would fundamentally
break the purpose of the policy. Read [rename &
link](../config/rename_and_link.md) for more details on how mergerfs
handles those two functions.
When in doubt it is trivial to test `link`:
```shell
$ cd /mnt/mergerfs/
$ touch foo
$ ln -v foo bar
'bar' => 'foo'
$ stat foo bar | grep Inode
Device: 0,173 Inode: 123456 Links: 2
Device: 0,173 Inode: 123456 Links: 2
```
For the more technical: within mergerfs the inode of a file is not
necessarily indicative of two file names linking to the same
underlying data. For more details as to why that is see the docs on
[inodecalc](../config/inodecalc.md).
## Are there any size limitations to files, directories, etc.?
Whatever the limits are of the underlying filesystems.
## Do reflink, FICLONE, or FICLONERANGE work?
@@ -101,7 +151,7 @@ You can remove the reserve by running: `tune2fs -m 0 <device>`
## I notice massive slowdowns of writes when enabling cache.files.
When file caching is enabled in any form (`cache.files!=off`) it will
When file caching is enabled in any form (`cache.files!=off`) it may
issue `getxattr` requests for `security.capability` prior to _every
single write_. This will usually result in performance degradation,
especially when using a network filesystem (such as NFS or SMB.)
@@ -133,20 +183,21 @@ disadvantages to each one.
A FUSE based solution has all the downsides of FUSE:
- Higher IO latency due to the trips in and out of kernel space
- Higher general overhead due to trips in and out of kernel space
- Double caching when using page caching
- Misc limitations due to FUSE's design
* Higher IO latency due to the trips in and out of kernel space
(though now minimized with passthrough IO)
* Higher general overhead due to trips in and out of kernel space
* Double caching when using page caching
* Misc limitations due to FUSE's design
But FUSE also has a lot of upsides:
- Easier to offer a cross platform solution
- Easier forward and backward compatibility
- Easier updates for users
- Easier and faster release cadence
- Allows more flexibility in design and features
- Overall easier to write, secure, and maintain
- Much lower barrier to entry (getting code into the kernel takes a
* Easier to offer a cross platform solution
* Easier forward and backward compatibility
* Easier updates for users
* Easier and faster release cadence
* Allows more flexibility in design and features
* Overall easier to write, secure, and maintain
* Much lower barrier to entry (getting code into the kernel takes a
lot of time and effort initially)
@@ -175,29 +226,81 @@ removed to simplify the codebase.
## How does mergerfs handle credentials?
mergerfs is a multithreaded application in order to handle requests
from the kernel concurrently. Each FUSE message has a header with
certain details about the request including the process ID (pid) of
the requesting application, the process' effective user id (uid), and
group id (gid). To ensure proper POSIX filesystem behavior and
security mergerfs must change its identity to match that of the
requester when performing the certain functions on the underlying
filesystem. As required by standards most Unix/POSIX based systems a
process and all its threads are under the same uid and gid. However,
on Linux each thread **may** have its own credentials. This allows
mergerfs to be multithreaded and for each thread to change to the
credentials as required by the incoming message it is
handling. However, currently on FreeBSD this is not possible (though
there has been
[discussions](https://wiki.freebsd.org/Per-Thread%20Credentials)) and
as such must change the credentials of the whole application when
actioning messages. mergerfs does optimize this behavior by only
changing credentials and locking the thread to do so if the process is
currently not the same as what is necessary by the incoming
request. As a result of this design FreeBSD may experience more
contention and therefore lower performance than Linux.
group id (gid).
Additionally, mergerfs [utilizes a cache for supplemental
groups](../known_issues_bugs.md#supplemental-user-groups) due the the
high cost of querying that information.
FUSE and the kernel have two ways of managing permissions. A kernel
side `default_permissions` option and leaving it to the FUSE
server. When default permissions is enabled the kernel will do the
entitlement checks and only allow requests through which should be
allowed according to normal POSIX permissions. When not enabled it is
the responsibility of the FUSE server, in this case mergerfs, to do
whatever is necessary to manage entitlements.
Prior to mergerfs v2.42.0 it would enable `default_permissions` but
also leveraged the uid and gid available in each FUSE request. The
thread actioning the request would change its credentials to match
those of the requesting application to ensure permissions were
properly handled as well as dealing with some quirks of non-POSIX
compatible filesystems. However, this strategy had two major
issues. First, it was not compatible with the new `allow-idmap`
feature of FUSE which allows a filesystem to advertise it can be used
with id mapping which is often used with containers. Secondly, it
caused permission issues when the kernel would say something was
allowed but due to the way mergerfs was changing creds would
fail. This mostly came in the form of `chroot`ed setups like used in
containers. Neither of these tended to impact casual users but did
break some niche or power user use cases. Since the casual user won't
notice the subtle changes and it would enable new use cases it was
decided that the credential handling would change.
As of v2.42.0 mergerfs now runs as root more generally. Either
changing credentials when creating files (Linux) or chown'ing them
after creation (FreeBSD).
As a result of this change it is now necessary for the FUSE
`default_permissions` feature be used for proper entitlements
management. mergerfs does allow it to be disabled but it should only
be done so for debugging purposes.
## Does mergerfs support idmap?
Yes, by setting [allow-idmap=true](../config/options.md) (which is the
default.)
Requires that
[kernel-permissions-check](../config/kernel-permissions-check.md) be
enabled (the default.)
## What happens if a branch filesystem blocks?
POSIX filesystem calls unfortunately are entirely synchronous. If the
underlying filesystem mergerfs is interacting with freezes up or
blocks then the thread issuing the request will block same as any
other piece of software. If enough threads block then mergerfs will
block. There are no timeouts or ways to truly work around this
situation. If mergerfs spawned new threads it is very likely they too
would end up blocked.
## What information does mergerfs have when making decisions?
Every incoming request contains:
* The `tid` or `pid` of the calling thread/process
* The `uid` and `gid` of the calling thread/process
* The `umask` of the calling thread/process
* The arguments of the filesystem call in question
Naturally the mergerfs config as well as anything queriable from the
operating system or filesystems are also available.
## Why does running `mount -a` result in new instances of mergerfs?
See [lazy-umount-mountpoint](../config/lazy-umount-mountpoint.md)
+56 -2
View File
@@ -1,5 +1,26 @@
# Usage and Functionality
## What should mergerfs NOT be used for?
* Situations where you need large amounts of contiguous space beyond
that available on any singular device. Such as putting 10GiB a file
on 2 6GiB filesystems.
* databases: Even if the database stored data in separate files
(mergerfs wouldn't offer much otherwise) the higher latency of the
indirection will harm performance. Though if it is a sqlite3
database then its likely fine.
* VM images: For the same reasons as databases. VM images are accessed
very aggressively and mergerfs will introduce a lot of extra latency.
* As replacement for RAID: mergerfs is just for pooling branches. If
you need device performance aggregation or high availability you
should stick with RAID. However, it is fine to put a filesystem
which is on a RAID setup in mergerfs.
**However, if using [passthrough](../config/passthrough.md) the
performance related issues above are less likely to be a concern. Best
to do testing for your specific use case.**
## What happens when file paths overlap?
It depends on the situation and the configuration of mergerfs. The
@@ -9,13 +30,19 @@ filesystem function is requested. Which file to select or act on.
See how [policies](../config/functions_categories_policies.md) work.
## Is adding or removing mergerfs to/from my setup destructive?
No. [See
below.](#can-mergerfs-be-used-with-filesystems-which-already-have-data)
## Can mergerfs be used with filesystems which already have data?
Yes. mergerfs is really just a proxy and does **NOT** interfere with
the normal form or function of the filesystems, mounts, or paths it
manages. It literally is interacting with your filesystems as any
other application does. It can not do anything that any other random
piece of software can't do.
piece of software can't do. There is no migration needed.
mergerfs is **not** a traditional filesystem that takes control over
the underlying drive, block device, or partition. mergerfs is **not**
@@ -23,6 +50,9 @@ RAID. It does **not** manipulate the data that passes through it. It
does **not** shard data across filesystems. It only shards some
**behavior** and aggregates others.
Think of it as a filesystem [reverse
proxy](https://en.wikipedia.org/wiki/Reverse_proxy).
## Can mergerfs be removed without affecting the data?
@@ -105,9 +135,33 @@ normal conditions.
Keep in mind that if out-of-band changes are made while also
leveraging any caching of the filesystem layout such as with
`cache.entry`, `cache.negative_entry`, `cache.attr`, `cache.symlinks`,
`cache.entry`, `cache.negative-entry`, `cache.attr`, `cache.symlinks`,
or `cache.readdir` you may experience temporary inconsistency till the
cache expires. mergerfs is not actively watching all branches for
changes and the kernel will have no way to know anything changed so as
to clear or ignore the cache. This is the same issue you can have with
[remote filesystems](../remote_filesystems.md).
## What are the performance characteristics of using mergerfs?
mergerfs does not stripe or shard data. Instead it acts like a reverse
proxy / multiplexer. As such it is not possible to have RAID like
performance improvements. However, its performance is not limited like
[block level concatenation / JBOD /
SPAN.](../project_comparisons.md#raid0-jbod-span-drive-concatenation-striping)
* Single file / single stream: Max throughput limited to speed of the
chosen underlying branch + FUSE overhead (usually 5–30% slower than
direct access; near native with [IO
passthrough](../config/passthrough.md)).
* Aggregate / multi-stream: Can approach sum of all branches' speeds
when many independent IO operations run in parallel (multiple files,
processes, or clients).
* Other factors: Latency-sensitive workloads suffer more (slow
branches or network drives hurt responsiveness); directory listing
and metadata ops slower due to combining branches.
Real-world performance varies heavily with workload, kernel, options,
and hardware. See [benchmarking](../benchmarking.md) and
[performance](../performance.md) section for testing tips.
+33 -17
View File
@@ -36,15 +36,15 @@ and [FAQ](configuration_and_policies.md) for policy recommendations.
## Why do I get an "out of space" / "no space left on device" / ENOSPC error even though there appears to be lots of space available?
One or multiple of the below are likely causing branches
One or multiple of the below are likely causing branches to be
[filtered out](../config/functions_categories_policies.md#filtering).
* [minfreespace](../config/minfreespace.md) could be filtering out
branches
* if using an `ep*`
[policy](../config/functions_categories_policies.md) there may not
be an available branch
* due to [branch modes](../config/branches.md#branch-mode) being set
to to `NC` or `RO` branches will be excluded from consideration
* using an `ep*` [policy](../config/functions_categories_policies.md)
and there may not be an available branch
* [branch modes](../config/branches.md#branch-mode) set to `NC` or
`RO` branches will be excluded from consideration
* if using `ext4` ensure you are not running into reserve limits (use
`tune2fs -m 0` to remove the reserve)
* it is possible that the filesystem selected by the policy has run
@@ -56,7 +56,7 @@ within the pool (behavior modified by [statfs](../config/statfs.md)
and [statfs_ignore](../config/statfs.md#statfs_ignore)). It does not
represent a contiguous space. In the same way that read-only
filesystems, those with quotas, or reserved space report the full
theoretical space available. Not the practical usable space.
theoretical space available but not the practical usable space.
## Why isn't the create policy working?
@@ -81,7 +81,7 @@ where it was created.
$ sudo touch /mnt/mergerfs/new-file
$ sudo getfattr -n user.mergerfs.allpaths /mnt/mergerfs/new-file
# file: mnt/mergerfs/new-file
user.mergerfs.allpaths="/mnt/hdd/drive1/new-file"
user.mergerfs.allpaths="/mnt/hdd/disk0/new-file"
```
If the location of the file is where it should be according to the
@@ -119,7 +119,7 @@ may be able to read some files and directories on one filesystem but
not another resulting in an incomplete set. And if one of the branches
it can access is empty then it will return an empty list.
Try using [mergerfs.fsck](https://github.com/trapexit/mergerfs-tools)
Try using [fsck.mergerfs](../tooling.md#fsckmergerfs)
tool to check for and fix inconsistencies in permissions. If you
aren't seeing anything at all be sure that the basic permissions are
correct. The user and group values are correct and that directories
@@ -134,7 +134,7 @@ the use of mergerfs from [container platforms such as
Docker.](compatibility_and_integration.md)
## Why are file moves, renames, links/hardlinks failing?
## Why are file moves, renames, links/hard links failing?
mergerfs fully supports rename and link functions to the degree that
is possible in a union filesystem. Problems related to rename and link
@@ -225,18 +225,34 @@ the software's author and request proper handling of `EXDEV` errors.
### Additional Reading
* [rename and link](../config/rename_and_link.md)
* [Do hardlinks work?](technical_behavior_and_limitations.md#do-hardlinks-work)
* [Do hard links work?](technical_behavior_and_limitations.md#do-hard-links-work)
* [How does mergerfs handle moving and copying of files?](technical_behavior_and_limitations.md#how-does-mergerfs-handle-moving-and-copying-of-files)
* [Moving files and directories fails with Samba](../known_issues_bugs.md#moving-files-and-directories-fails-with-samba)
## Why is rtorrent failing with ENODEV (No such device)?
## Why is software needing mmap failing?
Be sure to set
[cache.files=partial|full|auto-full|per-process](../config/cache.md)
or use Linux kernel v6.6 or above. rtorrent and some other
applications use [mmap](http://linux.die.net/man/2/mmap) to read and
write to files and offer no fallback to traditional methods.
See [Known Issues and Bugs#3rd Party
Software](../known_issues_bugs.md#3rd-party-software) and
[QuickStart](../quickstart.md).
## Why don't quick scans fail to pick up new media?
Media consumption software like Plex, Jellyfin, Emby, Airsonic,
Navidrome, etc. will use timestamps of directories to see if it should
be scanned. Be sure to set `func.getattr=newest` to catch that.
A full scan would still pick up all media and for those concerned
about device activity it is recommened to disable quick scans and
leave periodic full scans so the activity is managed. Or to configure
your software which manages downloads (such as *Arr family of
software) to trigger specific scans when files are added to the pool.
Additional reading:
* [Does inotify and fanotify
work?](compatibility_and_integration.md#does-inotify-and-fanotify-work)
## Why don't I see mergerfs options in mount command or /proc/mounts?
+78 -19
View File
@@ -1,30 +1,65 @@
# mergerfs - a featureful union filesystem
**mergerfs** is a
[FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace) based
[union filesystem](https://en.wikipedia.org/wiki/Union_mount) geared
towards simplifying storage and management of files across numerous
commodity storage devices. It is similar to [**mhddfs**, **unionfs**,
**aufs**, **DrivePool**, etc.](project_comparisons.md)
**mergerfs** is a [union
filesystem](https://en.wikipedia.org/wiki/Union_mount) that makes
multiple filesystems and/or directories appear as a single unified
directory. mergerfs is designed to simplify how you manage files
across multiple independent filesystems without the complexity,
fragility, or cost of RAID or similar storage aggregation
technologies.
mergerfs as a smart pooling layer allows you to combine any number of
[existing
filesystems](faq/usage_and_functionality.md#can-mergerfs-be-used-with-filesystems-which-already-have-data);
whether they are on hard drives, SSDs, network shares, or other
mounted storage; into what looks like one large filesystem, while
still maintaining direct access to each individual filesystem. Unlike
RAID, there's no rebuild process if a device fails. You only lose
those files that were on that specific filesystem. You can also add or
remove filesystems at any time without restructuring the pool.
mergerfs excels at cost-effective storage expansion, making it ideal
for media libraries, backups, archival data, and other write-sometimes,
read-often workloads where you need lots of space but don't want
the overhead of traditional storage aggregation technologies.
**Key advantages:**
* [Non-destructive](faq/usage_and_functionality.md#is-adding-or-removing-mergerfs-tofrom-my-setup-destructive) adding and removing of filesystems (even at runtime)
* Mix and match filesystems of any [size](faq/technical_behavior_and_limitations.md#are-there-any-size-limitations-to-files-directories-etc),
[type](faq/compatibility_and_integration.md#what-filesystems-can-be-used-as-branches),
or [underlying
device](faq/compatibility_and_integration.md#what-types-of-storage-devices-does-mergerfs-work-with)
* No parity calculations or rebuild times
* Does not require hard drives to be spinning if not in use
* [Direct access to files](faq/usage_and_functionality.md#can-filesystems-still-be-used-directly-outside-of-mergerfs-while-pooled) on individual filesystems when needed
* Flexible [policies](config/functions_categories_policies.md) for controlling where new files are created
For users seeking alternatives to mhddfs, unionfs, aufs, or DrivePool,
mergerfs offers a mature, actively maintained solution with extensive
configuration options and documentation. See the [project comparisons
for more details.](project_comparisons.md)
## Features
* Logically combine numerous filesystems/paths into a single
mount point (JBOFS: Just a Bunch of FileSystems)
mount point (JBOFS: Just a Bunch of FileSystems, think filesystem
[reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy))
* Combine paths of the same or different filesystems
* Ability to add or remove filesystems/paths without impacting the
rest of the data
* Unaffected by individual filesystem failure
* Configurable file selection and creation placement
* Configurable file creation placement
* File IO [passthrough](config/passthrough.md) for near native IO
performance (where supported)
* Works with filesystems of any size
* Works with filesystems of [almost any
type](faq/compatibility_and_integration.md#what-filesystems-can-be-used-as-branches)
* Ignore read-only filesystems when creating files
* Hard link [copy-on-write / CoW](config/link_cow.md)
* [Runtime configurable](runtime_interface.md)
* [Hard links](faq/technical_behavior_and_limitations.md#do-hard-links-work)
* Hard link [copy-on-write / CoW](config/link-cow.md)
* [Runtime configurability](runtime_interface.md)
* Support for extended attributes (xattrs)
* Support for file attributes (chattr)
* Support for POSIX ACLs
@@ -32,18 +67,23 @@ commodity storage devices. It is similar to [**mhddfs**, **unionfs**,
## Non-features
* Read/write overlay on top of read-only filesystem like OverlayFS
* RAID like redundancy (see [SnapRAID](https://www.snapraid.it) and
[NonRAID](https://github.com/qvr/nonraid))
* RAID like performance improvements
* LVM/RAID style block device aggregation
* Data integrity checks, snapshots, file versioning
* Read/write overlays on top of read-only filesystems (like OverlayFS)
* File whiteout
* RAID like parity calculation (see [SnapRAID](https://www.snapraid.it))
* Redundancy
* Splitting of files across branches
* Active rebalancing of content
* [Actively attempt to keep hard drives spun down](faq/limit_drive_spinup.md)
## How it works
mergerfs logically merges multiple filesystem paths together. Not
block devices, not filesystem mounts, just paths. It acts as a proxy
to the underlying filesystem paths. Combining the behaviors of some
to the underlying filesystem. Combining the behaviors of some
functions and being a selector for others.
When the contents of a directory are requested mergerfs combines the
@@ -56,15 +96,16 @@ which branch will be selected for the creation.
For functions which change attributes or remove the file the behavior
may be applied to all instances found.
Read more about [policies
here](config/functions_categories_policies.md).
The way in which mergerfs behaves is controlled by the
[config / options / settings](config/options.md). More specifically by
[policies](config/functions_categories_policies.md).
### Visualization
```
A + B = C
/disk1 /disk2 /merged
/mnt/hdd/disk0 /mnt/hdd/disk1 /media
| | |
+-- /dir1 +-- /dir1 +-- /dir1
| | | | | |
@@ -74,7 +115,7 @@ A + B = C
| | |
+-- /dir2 | +-- /dir2
| | | | |
| *-- file4 | | +-- file4
| +-- file4 | | +-- file4
| | |
| +-- /dir3 +-- /dir3
| | | | |
@@ -84,7 +125,25 @@ A + B = C
+-- file7 +-- file7 +-- file7
```
## Getting Started
Head to the [quick start guide](quickstart.md).
## About This Documentation
* Like the software the documentation changes over time. Ensure that
you are reading the documentation related to the version of the
software you are using.
* The documentation is explicit, literal, and reasonably thorough. If
a suspected feature is not mentioned it does not exist. Do not read
into the wording. What is described is how it functions. If you feel
like something is not explained sufficiently or missing please [ask
in one of the supported forums](support.md#contact-issue-submission)
and the docs will be updated.
* The search feature of MkDocs is not great. Searching for "literal
strings" will generally not work. Alternative solutions are being
investigated.
* While not intended for end users nor completely accurate some might
find the AI generated docs at https://deepwiki.com/trapexit/mergerfs
interesting or useful.
+2
View File
@@ -51,6 +51,8 @@ those needing that knowledge.
* [file descriptor](https://en.wikipedia.org/wiki/File_descriptor): A
handle used by software, provided by the operating system, to
reference open files.
* [mmap](https://en.wikipedia.org/wiki/Mmap): A way to abstract access
to a file by making it appear as a region of memory.
## Files

Some files were not shown because too many files have changed in this diff Show More