Compare commits

...
27 Commits
Author SHA1 Message Date
Antonio S. Musumeci 947024914a Replace std::filesystem::path with custom fs::relpath
std::filesystem::path is wrong for mergerfs's hot path:
- `branch->path / fusepath_` heap-allocates per FUSE op (3 allocs each
  on libstdc++; the SSO buffer is 15 bytes and any joined path exceeds it).
- The implicit `operator string_type()` adds another std::string when
  passing to `fs::*` wrappers that take `const std::string&`.
- Per-branch loops rebuild fullpath from scratch every iteration.

The new fs::relpath is a fixed 8192-byte (2 * PATH_MAX) inline buffer,
NUL-terminated, never heap-allocates. The rel data is anchored at offset
REL_OFFSET = 4096; the 4096 bytes before the anchor form the "prefix
area" so a long base/prefix can be written there in place via
set_prefix() without disturbing the rel. The remaining ~4096 bytes
after the rel are available for trailing append (filenames in
readdir-style loops, etc.).

set_prefix's cost is O(prefix size) -- only the actually-used bytes
are touched -- so the buffer size is a stack-footprint question, not a
hot-path cost. FUSE worker threads use the pthread default stack
(~8 MB on Linux); a 4-relpath frame in fuse_link/fuse_rename is 32 KB,
~0.4% of the stack budget.

The per-branch hot loop:

  fs::relpath fullpath = fusepath_;       // one copy of the rel
  for (auto branch : branches) {
    fullpath.set_prefix(branch->path);   // writes only [start, 4096)
    fs::syscall(fullpath);
  }

set_prefix only rewrites the prefix area; the rel stays anchored. A
cached _rel_size means no recomputation across iterations.

Microbenchmark (8 branches, 30-char fusepath, -O3, 4 M path builds):

  variant                                 ns/build   allocs/build
  --------------------------------------  --------   ------------
  origin/master (std::filesystem::path)   158        3.00
  fs::relpath assign_concat (single-shot) 9.1        0
  fs::relpath set_prefix (loop-optimized) 4.5        0

35x faster, zero allocations in the hot path.

Contract: paths flow through fs::relpath in canonical form -- prefix
without trailing '/', rel without leading '/'. No sanitization;
violations yield double slashes. Mergerfs satisfies this by
construction:
- libfuse's try_get_path() walks leaf-to-root via prepend(), then
  calls consolidate_to_rel(strip=true) which drops the leading '/'
  that prepend produces.
- branch paths are realpath-canonicalized at config load so they
  never have a trailing slash.

Mergerfs has exactly one path-manipulation type: a relative fusepath.
Branch paths and other absolute paths are stored as std::string -- they
are spliced into the prefix area of an fs::relpath at syscall time but
are never themselves mutated as path objects.

Major changes:
- vendored/libfuse/{include/fs_path.hpp,lib/fs_path.cpp}: new
  fs::relpath class (8 KB inline, REL anchor at 4096, set_prefix /
  clear_prefix / consolidate_to_rel, _rel_size cache, self-aliasing-
  safe assign_concat, sticky ENAMETOOLONG via errnum()).
- 30+ FUSE-op call sites converted from `fullpath = branch / fusepath`
  to the `set_prefix` loop pattern (fuse_unlink, fuse_chmod,
  fuse_chown, fuse_listxattr, fuse_truncate, fuse_utimens,
  fuse_setxattr, fuse_rmdir, fuse_removexattr, fuse_link's
  preserve-path and create-path loops, fuse_rename's create-path
  and preserve-path loops, fuse_statfs FULL mode, fs::findallfiles,
  fs::findonfs).
- fs::* syscall wrappers that previously only took
  `const std::string&` got `const char*` overloads so fs::relpath's
  implicit `operator const char*()` lands cleanly (fs::link,
  fs::lutimens, fs::umount_lazy, fs::open_dir_ro, fs::readlink,
  fs::lremovexattr, fs::lstatvfs, fs::info, fs::statvfs_cache).
- fs::exists gained a (fs::relpath&, basepath, ...) overload that
  calls set_prefix internally so per-branch policy fan-out loops
  share a single fullpath, rewriting only the prefix area on each
  iteration.
- All policies (epall, epff, eplfs, eplus, epmfs, eppfrd, ff, lup,
  msplfs, msplus, mspmfs, msppfrd, newest) refactored to hoist the
  fs::relpath fullpath construction out of the per-branch loop and
  use the set_prefix-based fs::exists overload.
- fs::inode::ReaddirCalc::_fusepath migrated from std::string to
  fs::relpath with the same resize/append API.
- std::filesystem removed entirely from the project: branches.cpp
  and option_parser.cpp's exists/is_directory/equivalent calls now
  use direct ::stat(); mergerfs_fsck.cpp's recursive_directory_iterator
  was replaced with an opendir-based walk.

Tests (tests/tests.cpp): 27 new fs::relpath test cases covering
construction, set_prefix in/out of REL anchor, prefix()/rel()
accessors, clear_prefix, concat canonical form, self-aliasing
(`p /= "x"`), operator/=, resize zero-fill, swap, copy/move,
equality, implicit conversions, lexically_relative, remove_filename,
_rel_size invariant across every mutator, capacity bounds.

Full suite: 213/213 pass.
2026-05-23 21:15:09 -04: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
283 changed files with 7438 additions and 5292 deletions
+3
View File
@@ -31,3 +31,6 @@ rpmbuild/
# mkdocs files
mkdocs/venv
__pycache__/
# Claude Code session state
.claude/
+31 -22
View File
@@ -29,6 +29,8 @@ MKDIR ?= mkdir
MKTEMP ?= mktemp
MV ?= mv
RM ?= rm
RPMBUILD ?= rpmbuild
RPMBUILD_FLAGS ?=
SED ?= sed
STRIP ?= strip
TAR ?= tar
@@ -37,7 +39,7 @@ TOUCH ?= touch
BUILDDIR := build
GITHUB_REPO ?= "https://github.com/trapexit/mergerfs"
DEFAULT_RELEASE_SAMPLE := debian:13.amd64
DEFAULT_TARGET := debian:13.amd64
ifndef GIT_REPO
ifneq ($(shell $(GIT) --version 2> /dev/null),)
@@ -48,11 +50,7 @@ endif
endif
USE_XATTR ?= 1
UGID_USE_RWLOCK ?= 0
ifdef NDEBUG
OPT_FLAGS := -O2 -DNDEBUG
else
ifdef SANITIZE
ifeq ($(SANITIZE),1)
override SANITIZE := -fsanitize=address,undefined,leak
@@ -60,14 +58,22 @@ else ifeq ($(filter -fsanitize=%,$(SANITIZE)),)
override SANITIZE := -fsanitize=$(SANITIZE)
endif
endif
OPT_FLAGS := -O0 \
-g \
-fno-omit-frame-pointer \
$(SANITIZE) \
-fstack-protector-strong \
-Wextra \
-Wno-unused-parameter \
-DDEBUG
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 ?= $(DEBUG_OPT_FLAGS) $(COMMON_OPT_FLAGS)
endif
ifdef STATIC
@@ -117,7 +123,7 @@ CXXFLAGS ?= \
-Wno-unused-result \
-pipe
override CXXFLAGS += \
-std=c++17 \
-std=c++20 \
-MMD \
-MP
override INC_FLAGS := \
@@ -125,8 +131,7 @@ override INC_FLAGS := \
-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 \
@@ -198,7 +203,7 @@ libfuse: $(LIBFUSE)
$(MAKE) -C vendored/libfuse
$(LIBFUSE):
$(MAKE) -C vendored/libfuse $(BUILDDIR)/libfuse.a
$(MAKE) -C vendored/libfuse $(BUILDDIR)/libfuse.a RELEASE=$(RELEASE)
tests: $(BUILDDIR)/tests
@@ -340,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
@@ -362,14 +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:
$(if $(RELEASE_SAMPLE),,$(eval RELEASE_SAMPLE := $(shell ./buildtools/detect-local-target 2>/dev/null)))
$(call build_release,$(if $(RELEASE_SAMPLE),$(RELEASE_SAMPLE),$(DEFAULT_RELEASE_SAMPLE)))
$(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:
@@ -382,6 +389,8 @@ 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))
+15 -3
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: $@"
@@ -28,10 +41,9 @@ mkdir "/build"
if [ -e /usr/bin/apt-get ]; then
make deb
elif [ -e /usr/bin/dnf ]; then
make rpm
make RPMBUILD_FLAGS="$(rpmbuild_flags)" rpm
elif [ -e /usr/bin/yum ]; then
. /opt/rh/devtoolset-9/enable
make rpm
make RPMBUILD_FLAGS="$(rpmbuild_flags)" rpm
elif [ -e /sbin/apk ]; then
echo "NOT YET SUPPORTED"
exit 1
+32 -4
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 += ['-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)
@@ -66,6 +66,30 @@ def 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():
@@ -92,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()
@@ -112,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/ /
+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
@@ -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/ /
+126 -3
View File
@@ -1,39 +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
+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
+2
View File
@@ -15,6 +15,8 @@ function or have been replaced. **They should not be used.**
* **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.
+5 -6
View File
@@ -213,12 +213,11 @@ config file.
* **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)
* **remember-nodes=INT**: The number of seconds to keep the internal
representation of a file once the OS tells mergerfs it is no longer
needed. Really only needed for [exporting mergerfs via
NFS](../remote_filesystems.md) (default: 0)
* **never-forget-nodes=BOOL**: Sets `remember-nodes` to
infinity. Formerly `noforget`. (default: false)
* **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
+25 -1
View File
@@ -2,6 +2,30 @@
## mergerfs
### Advisory file locking not currently supported
mergerfs does not implement POSIX or BSD advisory file locks as
provided by FUSE. Doing so is complex because, as a union filesystem,
it would need to forward lock requests to the underlying branches,
which could result in blocking calls. Locally this is not a major
concern—the kernel still manages locks for applications that all go
through the mergerfs mount. However, if an application locks a file
via the mergerfs mount while another locks the same file directly on
the underlying filesystem, the locks will not interact as one might
expect. That usage pattern is uncommon, so it is generally not a
problem.
The issue becomes more significant if you rely on locks over a remote
filesystem like NFS across multiple hosts, because mergerfs never
actually makes a lock call to the underlying branch. While network
locks are possible, they are generally discouraged, so this may not be
a huge loss either.
That said, there is a strategy that could add lock support if there is
sufficient demand, though it would probably be opt-in for performance
and resource-usage reasons.
### FreeBSD version
* [https://wiki.freebsd.org/FUSEFS](https://wiki.freebsd.org/FUSEFS)
@@ -27,7 +51,7 @@
* ...
#### Host vs Container identity
### Host vs Container identity
While **not** a bug some users have found when using containers that
supplemental groups defined inside the container don't work as
+5 -6
View File
@@ -57,7 +57,7 @@ NFS export settings:
* `softreval`: NFS client will serve up cached data after `retrans`
attempts to revalidate the data. Helps with intermittent network
issues.
* `timeo=150`: Timeout till retrying request.
* `timeo=150`: Timeout till retrying request.
* `retrans=3`: Number of retrying a request.
* `rsize=131072`: Read size.
* `wsize=131072`: Write size.
@@ -76,12 +76,11 @@ but if you run into problems it may be worth trying Samba/SMB.
**mergerfs settings:**
* `never-forget-nodes=true` or `remember-nodes=SECONDS`
* `never-forget-nodes=true`
* `inodecalc=path-hash`
* `lazy-umount-mountpoint=false`
`never-forget-nodes=true` (or `remember-nodes` with a large value such
as `86400` for a full day) is needed because NFS uses the
`never-forget-nodes=true` is needed because NFS uses the
`name_to_handle_at` and `open_by_handle_at` functions which allow a
program to keep a reference to a file without technically having it
open in the typical sense. The problem is that FUSE has no way to know
@@ -89,8 +88,8 @@ that NFS has a handle that it will later use to open the file
again. As a result, it is possible for the kernel to tell mergerfs to
forget about the file node and should NFS ever ask for that node's
details in the future it would have nothing to respond with. Keeping
nodes around forever (or however long is set with `remember-nodes`) is
not ideal but at the moment the only way to manage the situation.
nodes around until they are unlinked, removed, or replaced is not ideal
but at the moment the only way to manage the situation.
`inodecalc=path-hash` is needed because NFS is sensitive to
out-of-band changes. FUSE doesn't care if a file's inode value changes
+1 -1
View File
@@ -12,7 +12,7 @@
depending on [fuse-msg-size](config/fuse-msg-size.md)
* buffers allocated temporarily for reading directories
* FUSE nodes
* never-forget-nodes=true nodes
* never-forget-nodes=true retained nodes, until unlinked or removed
## Management
+1 -2
View File
@@ -20,7 +20,6 @@
#include "base_types.h"
#include "strvec.hpp"
#include "fs_path.hpp"
#include <cstdint>
#include <memory>
@@ -43,7 +42,7 @@ public:
public:
std::variant<u64,const u64*> _minfreespace;
Mode mode;
fs::path path;
std::string path;
public:
Branch();
+89 -35
View File
@@ -28,9 +28,14 @@
#include "str.hpp"
#include "syslog.hpp"
#include <string>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <sys/stat.h>
#include <string.h>
#include <fnmatch.h>
@@ -223,13 +228,24 @@ namespace l
fs::realpathize(&paths);
for(auto &path : paths)
{
std::error_code ec;
struct stat st;
int rv;
if(!std::filesystem::exists(path,ec))
rv = ::stat(path.c_str(),&st);
if(rv < 0)
{
SysLog::notice("branch `{}` does not currently exist",path);
// Distinguish "doesn't exist" (the common case where the
// backing storage isn't mounted yet) from harder errors
// like permission, name-too-long, ELOOP, etc., which
// suggest a config problem rather than transient absence.
if(errno == ENOENT)
SysLog::notice("branch `{}` does not currently exist",path);
else
SysLog::warning("branch `{}` could not be stat'd: {}",
path,
strerror(errno));
}
else if(!std::filesystem::is_directory(path,ec))
else if(!S_ISDIR(st.st_mode))
{
SysLog::warning("branch `{}` is not a directory, skipping",path);
continue;
@@ -415,14 +431,14 @@ Branches::Impl::to_paths(StrVec &paths_) const
{
for(auto &branch : *this)
{
paths_.push_back(branch.path);
paths_.emplace_back(branch.path);
}
}
std::vector<fs::path>
std::vector<std::string>
Branches::Impl::to_paths() const
{
std::vector<fs::path> vp;
std::vector<std::string> vp;
for(const auto &branch : *this)
vp.emplace_back(branch.path);
@@ -437,24 +453,44 @@ Branches::from_string(const std::string_view str_)
Branches::Ptr impl;
Branches::Ptr new_impl;
impl = std::atomic_load(&_impl);
new_impl = std::make_shared<Branches::Impl>(&minfreespace);
*new_impl = *impl;
rv = new_impl->from_string(str_);
if(rv < 0)
return rv;
for(;;)
{
{
std::shared_lock<std::shared_mutex> lk(_mutex);
impl = _impl;
}
std::atomic_store(&_impl,new_impl);
*new_impl = *impl;
return 0;
rv = new_impl->from_string(str_);
if(rv < 0)
return rv;
{
std::unique_lock<std::shared_mutex> lk(_mutex);
if(impl != _impl)
continue;
_impl = std::move(new_impl);
}
return 0;
}
}
std::string
Branches::to_string(void) const
{
return std::atomic_load(&_impl)->to_string();
Branches::Ptr impl;
{
std::shared_lock<std::shared_mutex> lk(_mutex);
impl = _impl;
}
return impl->to_string();
}
void
@@ -462,32 +498,50 @@ Branches::find_and_set_mode_ro()
{
Branches::Ptr impl;
Branches::Ptr new_impl;
std::vector<std::string> ro_paths;
impl = std::atomic_load(&_impl);
new_impl = std::make_shared<Branches::Impl>(&minfreespace);
*new_impl = *impl;
bool changed = false;
for(auto &branch : *new_impl)
for(;;)
{
if(branch.mode != Branch::Mode::RW)
continue;
ro_paths.clear();
if(!fs::is_rofs_but_not_mounted_ro(branch.path))
continue;
{
std::shared_lock<std::shared_mutex> lk(_mutex);
impl = _impl;
}
SysLog::warning("branch `{}` found to be readonly - setting its mode=RO",
branch.path.string());
*new_impl = *impl;
branch.mode = Branch::Mode::RO;
changed = true;
for(auto &branch : *new_impl)
{
if(branch.mode != Branch::Mode::RW)
continue;
if(!fs::is_rofs_but_not_mounted_ro(branch.path))
continue;
branch.mode = Branch::Mode::RO;
ro_paths.emplace_back(branch.path);
}
if(ro_paths.empty())
return;
{
std::unique_lock<std::shared_mutex> lk(_mutex);
if(impl != _impl)
continue;
_impl = std::move(new_impl);
}
for(const auto &path : ro_paths)
SysLog::warning("branch `{}` found to be readonly - setting its mode=RO",
path);
return;
}
if(not changed)
return;
std::atomic_store(&_impl,new_impl);
}
SrcMounts::SrcMounts(Branches &b_)
+15 -6
View File
@@ -19,13 +19,12 @@
#pragma once
#include "branch.hpp"
#include "fs_path.hpp"
#include "strvec.hpp"
#include "tofrom_string.hpp"
#include <atomic>
#include <cstdint>
#include <memory>
#include <shared_mutex>
#include <string>
#include <vector>
@@ -54,7 +53,7 @@ public:
public:
const u64 &minfreespace(void) const;
void to_paths(StrVec &strvec) const;
std::vector<fs::path> to_paths() const;
std::vector<std::string> to_paths() const;
public:
Impl& operator=(Impl &impl_);
@@ -69,7 +68,8 @@ public:
u64 minfreespace = MINFREESPACE_DEFAULT;
private:
mutable std::shared_ptr<Impl> _impl;
Branches::Ptr _impl;
mutable std::shared_mutex _mutex;
public:
Branches()
@@ -82,8 +82,17 @@ public:
std::string to_string(void) const final;
public:
operator Ptr() const { return std::atomic_load(&_impl); }
Ptr operator->() const { return std::atomic_load(&_impl); }
operator Ptr() const
{
std::shared_lock<std::shared_mutex> lk(_mutex);
return _impl;
}
Ptr operator->() const
{
std::shared_lock<std::shared_mutex> lk(_mutex);
return _impl;
}
public:
void find_and_set_mode_ro();
+18 -17
View File
@@ -46,16 +46,16 @@ int
Config::CfgConfigFile::from_string(const std::string_view s_)
{
int rv;
fs::path cfg_file;
std::string cfg_file;
if(_depth > 5)
return -ELOOP;
_depth++;
struct DepthGuard { int &d; ~DepthGuard() { --d; } } guard{_depth};
cfg_file = (s_.empty() ? _cfg_file : s_);
cfg_file = (s_.empty() ? std::string_view(_cfg_file) : s_);
rv = cfg.from_file(cfg_file);
rv = cfg.from_file(cfg_file.c_str());
if(rv == 0)
_cfg_file = cfg_file;
@@ -135,10 +135,6 @@ Config::Config()
_max_background(fuse_cfg.max_background),
_mount(mountpoint),
_mountpoint(mountpoint),
_never_forget_nodes(),
_noforget(),
_remember(fuse_cfg.remember_nodes),
_remember_nodes(fuse_cfg.remember_nodes),
_srcmounts(branches),
_threads(fuse_cfg.read_thread_count),
_uid(fuse_cfg.uid),
@@ -147,13 +143,22 @@ Config::Config()
_initialized(false)
{
allow_idmap.ro =
_congestion_threshold.ro =
_max_background.ro =
_mount.ro =
_mountpoint.ro =
_never_forget_nodes.ro =
_noforget.ro =
_remember.ro =
_remember_nodes.ro =
_srcmounts.ro =
_version.ro =
allow_idmap.ro =
async_read.ro =
branches_mount_timeout.ro =
branches_mount_timeout_fail.ro =
cache_symlinks.ro =
cache_writeback.ro =
_congestion_threshold.ro =
direct_io_allow_mmap.ro =
export_support.ro =
fsname.ro =
@@ -161,9 +166,6 @@ Config::Config()
handle_killpriv.ro =
handle_killpriv_v2.ro =
kernel_permissions_check.ro =
_max_background.ro =
_mount.ro =
_mountpoint.ro =
nullrw.ro =
pid.ro =
pin_threads.ro =
@@ -172,8 +174,6 @@ Config::Config()
process_thread_queue_depth.ro =
read_thread_count.ro =
scheduling_priority.ro =
_srcmounts.ro =
_version.ro =
true;
_congestion_threshold.display =
_gid.display =
@@ -181,6 +181,7 @@ Config::Config()
_mount.display =
_noforget.display =
_remember.display =
_remember_nodes.display =
_srcmounts.display =
_threads.display =
_uid.display =
@@ -425,7 +426,7 @@ Config::from_stream(std::istream &istrm_)
}
int
Config::from_file(const std::string &filepath_)
Config::from_file(const char *filepath_)
{
std::ifstream ifstrm;
@@ -448,13 +449,13 @@ Config::finish_initializing()
}
bool
Config::is_rootdir(const fs::path &fusepath_)
Config::is_rootdir(const fs::relpath &fusepath_)
{
return fusepath_.empty();
}
bool
Config::is_ctrl_file(const fs::path &fusepath_)
Config::is_ctrl_file(const fs::relpath &fusepath_)
{
return (fusepath_ == ".mergerfs");
}
+10 -10
View File
@@ -67,7 +67,7 @@ typedef ToFromWrapper<u64> ConfigU64;
typedef ToFromWrapper<s64> ConfigS64;
typedef ToFromWrapper<int> ConfigINT;
typedef ToFromWrapper<std::string> ConfigSTR;
typedef ToFromWrapper<fs::path> ConfigPath;
typedef ToFromWrapper<std::string> ConfigPath;
typedef std::map<std::string,ToFromString*> Str2TFStrMap;
typedef ROToFromWrapper<std::string> ConfigROSTR;
@@ -92,7 +92,7 @@ public:
class CfgConfigFile : public ToFromString
{
private:
fs::path _cfg_file;
std::string _cfg_file;
static thread_local int _depth;
public:
@@ -141,7 +141,7 @@ public:
LinkEXDEV link_exdev;
LogFile log_file;
TFSRef<u64> minfreespace;
fs::path mountpoint;
std::string mountpoint;
MoveOnENOSPC moveonenospc;
NFSOpenHack nfsopenhack;
ConfigBOOL nullrw;
@@ -172,12 +172,12 @@ private:
CfgDummy _dummy;
TFSRef<s64> _gid;
TFSRef<int> _max_background;
TFSRef<fs::path> _mount;
TFSRef<fs::path> _mountpoint;
TFSRef<std::string> _mount;
TFSRef<std::string> _mountpoint;
CfgNoforget _never_forget_nodes;
CfgNoforget _noforget;
TFSRef<s64> _remember;
TFSRef<s64> _remember_nodes;
CfgNoforget _remember;
CfgNoforget _remember_nodes;
SrcMounts _srcmounts;
TFSRef<int> _threads;
TFSRef<s64> _uid;
@@ -207,11 +207,11 @@ public:
public:
int from_stream(std::istream &istrm);
int from_file(const std::string &filepath);
int from_file(const char *filepath);
public:
static bool is_rootdir(const fs::path &fusepath);
static bool is_ctrl_file(const fs::path &fusepath);
static bool is_rootdir(const fs::relpath &fusepath);
static bool is_ctrl_file(const fs::relpath &fusepath);
static bool is_mergerfs_xattr(const char *attrname);
static bool is_cmd_xattr(const std::string_view &attrname);
static std::string_view prune_ctrl_xattr(const std::string_view s);
+15 -9
View File
@@ -32,18 +32,24 @@ public:
{
int rv;
bool b = false;
s64 i = 0;
rv = str::from(s_,&b);
if((b == true) || s_.empty())
if(s_.empty())
{
fuse_cfg.remember_nodes = -1;
return 0;
b = true;
}
else if((rv = str::from(s_,&i)) == 0)
{
b = (i != 0);
}
else
{
rv = str::from(s_,&b);
if(rv)
return rv;
}
if(rv)
return rv;
fuse_cfg.remember_nodes = 0;
fuse_cfg.remember_nodes = b;
return 0;
}
@@ -51,7 +57,7 @@ public:
std::string
to_string() const
{
if(fuse_cfg.remember_nodes == -1)
if(fuse_cfg.remember_nodes)
return "true";
return "false";
}
+1 -1
View File
@@ -34,7 +34,7 @@ public:
u64 to_fh() const;
public:
DirInfo(const fs::path &fusepath_)
DirInfo(const fs::relpath &fusepath_)
: FH(fusepath_)
{
}
+2 -2
View File
@@ -24,12 +24,12 @@
class FH
{
public:
FH(const fs::path &fusepath_)
FH(const fs::relpath &fusepath_)
: fusepath(fusepath_)
{
}
public:
fs::path fusepath;
fs::relpath fusepath;
};
+14 -5
View File
@@ -48,10 +48,10 @@
#include "fh.hpp"
#include "fs_path.hpp"
#include "mutex.hpp"
#include "base_types.h"
#include <shared_mutex>
class FileInfo : public FH
{
@@ -62,7 +62,7 @@ public:
public:
FileInfo(const int fd_,
const Branch *branch_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const bool direct_io_)
: FH(fusepath_),
fd(fd_),
@@ -73,7 +73,7 @@ public:
FileInfo(const int fd_,
const Branch &branch_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const bool direct_io_)
: FH(fusepath_),
fd(fd_),
@@ -97,7 +97,16 @@ public:
int fd;
Branch branch;
u32 direct_io:1;
Mutex mutex;
// Serializes the fd state across concurrent writes on the same open
// file. Concurrent writes happen with:
// 1) writeback-cache + page-cache mode
// 2) parallel_direct_writes + direct_io=true
//
// Common case (plain pwrite on stable fd) only needs a shared lock
// since the kernel already serializes fd mutations. The danger is
// the fs::dup2() rebinding when moveonenospc activates. Writers
// take shared lock; move takes unique.
std::shared_mutex mutex;
};
inline
-9
View File
@@ -206,12 +206,3 @@ str::from(const std::string_view value_,
{
return -EINVAL;
}
int
str::from(const std::string_view value_,
fs::path *path_)
{
*path_ = value_;
return 0;
}
-2
View File
@@ -19,7 +19,6 @@
#pragma once
#include "base_types.h"
#include "fs_path.hpp"
#include <string>
#include <string_view>
@@ -33,5 +32,4 @@ namespace str
int from(const std::string_view, s64 *);
int from(const std::string_view, std::string *);
int from(const std::string_view, const std::string *);
int from(const std::string_view, fs::path *);
}
+2 -3
View File
@@ -21,16 +21,15 @@
#include "fs_lgetxattr.hpp"
#include "fs_path.hpp"
#include <filesystem>
constexpr const char POSIX_ACL_DEFAULT_XATTR[] = "system.posix_acl_default";
bool
fs::acl::dir_has_defaults(const fs::path &fullpath_)
fs::acl::dir_has_defaults(const fs::relpath &fullpath_)
{
int rv;
fs::path dirpath;
fs::relpath dirpath;
dirpath = fullpath_.parent_path();
+1 -1
View File
@@ -26,6 +26,6 @@ namespace fs
namespace acl
{
bool
dir_has_defaults(const fs::path &fullpath);
dir_has_defaults(const fs::relpath &fullpath);
}
}
+8 -8
View File
@@ -44,8 +44,8 @@ _get_fs_ioc_flags(const int fd,
static
int
_get_fs_ioc_flags(const string &file,
int &flags)
_get_fs_ioc_flags(const char *file,
int &flags)
{
int fd;
int rv;
@@ -81,8 +81,8 @@ _set_fs_ioc_flags(const int fd,
static
int
_set_fs_ioc_flags(const string &file,
const int flags)
_set_fs_ioc_flags(const char *file,
const int flags)
{
int fd;
int rv;
@@ -121,15 +121,15 @@ fs::attr::copy(const int fdin,
}
int
fs::attr::copy(const string &from,
const string &to)
fs::attr::copy(const std::string &from_,
const std::string &to_)
{
int rv;
int flags;
rv = ::_get_fs_ioc_flags(from,flags);
rv = ::_get_fs_ioc_flags(from_.c_str(),flags);
if(rv < 0)
return rv;
return ::_set_fs_ioc_flags(to,flags);
return ::_set_fs_ioc_flags(to_.c_str(),flags);
}
+27 -15
View File
@@ -16,25 +16,37 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fs_cleanpath.hpp"
#include "to_neg_errno.hpp"
#include <sys/file.h>
#include <string>
namespace fs
void
fs::cleanpath(std::string *path_)
{
static
inline
int
flock(const int fd_,
const int operation_)
{
int rv;
std::string &s = *path_;
std::size_t w = 0;
bool prev_slash = false;
rv = ::flock(fd_,operation_);
for(std::size_t r = 0; r < s.size(); r++)
{
const char c = s[r];
if(c == '/')
{
if(prev_slash)
continue;
prev_slash = true;
}
else
{
prev_slash = false;
}
s[w++] = c;
}
return ::to_neg_errno(rv);
}
// Strip trailing '/' but leave a bare "/" intact.
if(w > 1 && s[w - 1] == '/')
w--;
s.resize(w);
}
+2 -2
View File
@@ -18,10 +18,10 @@
#pragma once
#include <filesystem>
#include <string>
namespace fs
{
using path = std::filesystem::path;
void cleanpath(std::string *path);
}
+71 -47
View File
@@ -61,60 +61,84 @@ _ignorable_error(const int err_)
The directories which already exist are left alone.
The new directories have metadata set to match the original if
possible. Optionally ignore errors on metadata copies.
Iterative root-to-leaf walk. Stack usage is O(1) in component count.
*/
int
fs::clonepath(const fs::path &srcpath_,
const fs::path &dstpath_,
const fs::path &relpath_,
const bool return_metadata_errors_)
fs::clonepath(const std::string_view srcpath_,
const std::string_view dstpath_,
const fs::relpath &relpath_,
const bool return_metadata_errors_)
{
int rv;
struct stat st;
fs::path dstpath;
fs::path srcpath;
fs::path dirname;
if(relpath_.empty())
return 0;
dirname = relpath_.parent_path();
if(!dirname.empty())
std::string srcpath;
std::string dstpath;
srcpath.reserve(srcpath_.size() + relpath_.size() + 1);
dstpath.reserve(dstpath_.size() + relpath_.size() + 1);
srcpath = srcpath_;
dstpath = dstpath_;
std::string_view rel = relpath_.native();
std::size_t pos = 0;
while(pos < rel.size())
{
rv = fs::clonepath(srcpath_,dstpath_,dirname,return_metadata_errors_);
std::size_t next = rel.find('/',pos);
if(next == std::string_view::npos)
next = rel.size();
std::string_view component = rel.substr(pos,next - pos);
pos = next + 1;
// Defensive: canonical rel form should not produce empty
// segments, but skip them if the input is malformed (e.g. a
// double slash) rather than appending nothing and re-stat'ing
// the same path.
if(component.empty())
continue;
srcpath += '/';
srcpath.append(component);
dstpath += '/';
dstpath.append(component);
int rv;
struct stat st;
rv = fs::lstat(srcpath,&st);
if(rv < 0)
return rv;
if(!S_ISDIR(st.st_mode))
return -ENOTDIR;
rv = fs::mkdir(dstpath,st.st_mode);
if(rv < 0 && rv != -EEXIST)
return rv;
// EEXIST: directory already there (could be from a prior clone
// or a race). Leave it alone, do NOT re-copy metadata, matching
// the recursive version's behavior when its mkdir hit EEXIST.
if(rv == -EEXIST)
continue;
// it may not support it... it's fine...
rv = fs::attr::copy(srcpath,dstpath);
if(return_metadata_errors_ && (rv < 0) && !::_ignorable_error(-rv))
return rv;
// it may not support it... it's fine...
rv = fs::xattr::copy(srcpath,dstpath);
if(return_metadata_errors_ && (rv < 0) && !::_ignorable_error(-rv))
return rv;
rv = fs::lchown_check_on_error(dstpath,st);
if(return_metadata_errors_ && (rv < 0))
return rv;
rv = fs::lutimens(dstpath,st);
if(return_metadata_errors_ && (rv < 0))
return rv;
}
srcpath = srcpath_ / relpath_;
rv = fs::lstat(srcpath,&st);
if(rv < 0)
return rv;
else if(!S_ISDIR(st.st_mode))
return -ENOTDIR;
dstpath = dstpath_ / relpath_;
rv = fs::mkdir(dstpath,st.st_mode);
if(rv < 0)
return ((rv == -EEXIST) ? 0 : rv);
// it may not support it... it's fine...
rv = fs::attr::copy(srcpath,dstpath);
if(return_metadata_errors_ && (rv < 0) && !::_ignorable_error(-rv))
return rv;
// it may not support it... it's fine...
rv = fs::xattr::copy(srcpath,dstpath);
if(return_metadata_errors_ && (rv < 0) && !::_ignorable_error(-rv))
return rv;
rv = fs::lchown_check_on_error(dstpath,st);
if(return_metadata_errors_ && (rv < 0))
return rv;
rv = fs::lutimens(dstpath,st);
if(return_metadata_errors_ && (rv < 0))
return rv;
return 0;
}
@@ -122,10 +146,10 @@ fs::clonepath(const fs::path &srcpath_,
// WORK IN PROGRESS
static
int
_clonepath2(const int srcfd_,
const int dstfd_,
const fs::path &dirname_,
const bool return_metadata_errors_)
_clonepath2(const int srcfd_,
const int dstfd_,
const fs::relpath &dirname_,
const bool return_metadata_errors_)
{
int rv;
int srcdirfd;
+9 -4
View File
@@ -20,11 +20,16 @@
#include "fs_path.hpp"
#include <string_view>
namespace fs
{
int clonepath(const fs::path &srcpath,
const fs::path &dstpath,
const fs::path &relpath,
const bool return_metadata_errors = false);
// srcpath / dstpath are absolute base paths; relpath is the rel-form
// chain to walk and create. Accepts string_view so an fs::relpath
// holding an absolute fullpath also works without conversion.
int clonepath(const std::string_view srcpath,
const std::string_view dstpath,
const fs::relpath &relpath,
const bool return_metadata_errors = false);
}
+4 -4
View File
@@ -98,7 +98,7 @@ fs::copyfile(const int src_fd_,
s64
fs::copyfile(const int src_fd_,
const fs::path &dst_filepath_,
const char *dst_filepath_,
const fs::CopyFileFlags &flags_)
{
s64 rv;
@@ -155,7 +155,7 @@ fs::copyfile(const int src_fd_,
continue;
}
rv = fs::rename(dst_tmppath,dst_filepath_);
rv = fs::rename(dst_tmppath.c_str(),dst_filepath_);
if((rv < 0) && (flags_.cleanup_failure))
fs::unlink(dst_tmppath);
break;
@@ -170,8 +170,8 @@ fs::copyfile(const int src_fd_,
#endif
s64
fs::copyfile(const fs::path &src_filepath_,
const fs::path &dst_filepath_,
fs::copyfile(const char *src_filepath_,
const char *dst_filepath_,
const fs::CopyFileFlags &flags_)
{
int src_fd;
+15 -4
View File
@@ -19,7 +19,8 @@
#pragma once
#include "base_types.h"
#include "fs_path.hpp"
#include <string>
#include <sys/stat.h>
@@ -40,11 +41,21 @@ namespace fs
s64
copyfile(const int src_fd,
const fs::path &dst_filepath,
const char *dst_filepath,
const CopyFileFlags &flags);
s64
copyfile(const fs::path &src_filepath,
const fs::path &dst_filepath,
copyfile(const char *src_filepath,
const char *dst_filepath,
const CopyFileFlags &flags);
static
inline
s64
copyfile(const std::string &src_filepath,
const std::string &dst_filepath,
const CopyFileFlags &flags)
{
return fs::copyfile(src_filepath.c_str(),dst_filepath.c_str(),flags);
}
}
+3 -3
View File
@@ -53,8 +53,8 @@ fs::cow::is_eligible(const int flags_,
}
bool
fs::cow::is_eligible(const fs::path &fullpath_,
const int flags_)
fs::cow::is_eligible(const std::string &fullpath_,
const int flags_)
{
int rv;
struct stat st;
@@ -70,7 +70,7 @@ fs::cow::is_eligible(const fs::path &fullpath_,
}
int
fs::cow::break_link(const fs::path &src_filepath_)
fs::cow::break_link(const std::string &src_filepath_)
{
return fs::copyfile(src_filepath_,
src_filepath_,
+3 -4
View File
@@ -18,8 +18,7 @@
#pragma once
#include "fs_path.hpp"
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
@@ -32,8 +31,8 @@ namespace fs
bool is_eligible(const struct stat &st);
bool is_eligible(const int flags, const struct stat &st);
bool is_eligible(const fs::path &fullpath, const int flags);
bool is_eligible(const std::string &fullpath, const int flags);
int break_link(const fs::path &fullpath);
int break_link(const std::string &fullpath);
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ namespace fs
static
inline
int
eaccess(const fs::path &path_,
eaccess(const fs::relpath &path_,
const int mode_)
{
return fs::eaccess(path_.c_str(),mode_);
+65 -28
View File
@@ -27,8 +27,8 @@ namespace fs
static
inline
bool
exists(const fs::path &path_,
struct stat *st_)
exists(const char *path_,
struct stat *st_)
{
int rv;
@@ -40,7 +40,7 @@ namespace fs
static
inline
bool
exists(const fs::path &path_)
exists(const char *path_)
{
struct stat st;
@@ -50,13 +50,49 @@ namespace fs
static
inline
bool
exists(const fs::path &basepath_,
const char *relpath_,
struct stat *st_)
exists(const std::string &path_,
struct stat *st_)
{
fs::path fullpath;
return fs::exists(path_.c_str(),st_);
}
fullpath = basepath_ / relpath_;
static
inline
bool
exists(const std::string &path_)
{
return fs::exists(path_.c_str());
}
static
inline
bool
exists(const fs::relpath &path_,
struct stat *st_)
{
return fs::exists(path_.c_str(),st_);
}
static
inline
bool
exists(const fs::relpath &path_)
{
return fs::exists(path_.c_str());
}
// Two-arg helpers for the per-branch loop pattern: build
// basepath/'/'/relpath into an fs::relpath and stat it.
static
inline
bool
exists(const std::string &basepath_,
const fs::relpath &relpath_,
struct stat *st_)
{
fs::relpath fullpath;
fullpath.assign_concat(basepath_,relpath_);
return fs::exists(fullpath,st_);
}
@@ -64,36 +100,37 @@ namespace fs
static
inline
bool
exists(const fs::path &basepath_,
const fs::path &relpath_,
struct stat *st_)
{
fs::path fullpath;
fullpath = basepath_ / relpath_;
return fs::exists(fullpath,st_);
}
static
inline
bool
exists(const fs::path &basepath_,
const char *relpath_)
exists(const std::string &basepath_,
const fs::relpath &relpath_)
{
struct stat st;
return fs::exists(basepath_,relpath_,&st);
}
// Hot-path variant for per-branch fan-out loops. The caller builds
// the fullpath once (with the rel portion set) outside the loop, and
// each iteration only rewrites the prefix area via set_prefix --
// skipping the per-iteration rel memmove that the (basepath,
// relpath) overload incurs.
static
inline
bool
exists(const fs::path &basepath_,
const fs::path &relpath_)
exists(fs::relpath &fullpath_,
const std::string &basepath_,
struct stat *st_)
{
struct stat st;
fullpath_.set_prefix(basepath_);
return fs::exists(fullpath_.c_str(),st_);
}
return fs::exists(basepath_,relpath_,&st);
static
inline
bool
exists(fs::relpath &fullpath_,
const std::string &basepath_)
{
fullpath_.set_prefix(basepath_);
return fs::exists(fullpath_.c_str());
}
}
+1 -1
View File
@@ -65,7 +65,7 @@ namespace fs
inline
int
faccessat(const int dirfd_,
const fs::path &path_,
const fs::relpath &path_,
const int mode_,
const int flags_)
{
+1 -1
View File
@@ -67,7 +67,7 @@ namespace fs
inline
int
fchmodat(const int dirfd_,
const fs::path &pathname_,
const fs::relpath &pathname_,
const mode_t mode_,
const int flags_)
{
-1
View File
@@ -21,7 +21,6 @@
#include "to_neg_errno.hpp"
#include "xattr.hpp"
#include <filesystem>
#include <string>
#include <sys/types.h>
+4 -4
View File
@@ -27,18 +27,18 @@
void
fs::findallfiles(const std::vector<std::string> &basepaths_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
std::vector<std::string> *paths_)
{
fs::path fullpath;
fs::relpath fullpath = fusepath_;
for(const auto &basepath : basepaths_)
{
fullpath = basepath / fusepath_;
fullpath.set_prefix(basepath);
if(!fs::exists(fullpath))
continue;
paths_->push_back(fullpath);
paths_->emplace_back(fullpath.native());
}
}
+1 -1
View File
@@ -28,6 +28,6 @@ namespace fs
{
void
findallfiles(const std::vector<std::string> &basepaths,
const fs::path &fusepath,
const fs::relpath &fusepath,
std::vector<std::string> *paths);
}
+14 -4
View File
@@ -30,14 +30,14 @@
static
int
_findonfs(const Branches::Ptr &branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const int fd_,
std::string *basepath_)
{
int rv;
dev_t dev;
struct stat st;
fs::path fullpath;
fs::relpath fullpath = fusepath_;
rv = fs::fstat(fd_,&st);
if(rv < 0)
@@ -46,7 +46,7 @@ _findonfs(const Branches::Ptr &branches_,
dev = st.st_dev;
for(const auto &branch : *branches_)
{
fullpath = branch.path / fusepath_;
fullpath.set_prefix(branch.path);
rv = fs::lstat(fullpath,&st);
if(rv < 0)
@@ -70,5 +70,15 @@ fs::findonfs(const Branches::Ptr &branches_,
const int fd_,
std::string *basepath_)
{
return ::_findonfs(branches_,fusepath_,fd_,basepath_);
// The public API accepts std::string for caller convenience.
// Internally we need a canonical-rel fusepath (no leading '/') so
// the per-branch loop's set_prefix() produces a single separator.
// Strip the leading '/' by hand if present.
std::string_view sv(fusepath_);
if(!sv.empty() && sv.front() == '/')
sv.remove_prefix(1);
fs::relpath fusepath(sv);
return ::_findonfs(branches_,fusepath,fd_,basepath_);
}
+4
View File
@@ -25,6 +25,10 @@
namespace fs
{
// Public API takes std::string for caller convenience. Internally
// we strip any leading '/' so the per-branch loop's set_prefix()
// produces a single separator; this entry point handles the
// conversion.
int
findonfs(const Branches::Ptr &branches,
const std::string &fusepath,
+2 -2
View File
@@ -48,7 +48,7 @@ namespace fs
inline
int
fstatat(const int dirfd_,
const fs::path &pathname_,
const fs::relpath &pathname_,
struct stat *statbuf_,
const int flags_)
{
@@ -75,7 +75,7 @@ namespace fs
inline
int
fstatat_nofollow(const int dirfd_,
const fs::path &pathname_,
const fs::relpath &pathname_,
struct stat *statbuf_)
{
return fs::fstatat(dirfd_,
+5 -5
View File
@@ -37,9 +37,9 @@
static
inline
bool
_can_call_lutimes(const int dirfd_,
const std::string &path_,
const int flags_)
_can_call_lutimes(const int dirfd_,
const char *path_,
const int flags_)
{
return ((flags_ == AT_SYMLINK_NOFOLLOW) &&
((dirfd_ == AT_FDCWD) ||
@@ -117,7 +117,7 @@ static
inline
int
_set_utime_omit_to_current_value(const int dirfd_,
const std::string &path_,
const char *path_,
const struct timespec ts_[2],
struct timeval tv_[2],
const int flags_)
@@ -203,7 +203,7 @@ static
inline
int
_convert_timespec_to_timeval(const int dirfd_,
const std::string &path_,
const char *path_,
const struct timespec ts_[2],
struct timeval tv_[2],
struct timeval **tvp_,
+2 -4
View File
@@ -21,12 +21,10 @@
#include "fs_statvfs.hpp"
#include "statvfs_util.hpp"
#include <string>
bool
fs::has_space(const std::string &str_,
cs64 size_)
fs::has_space(const char *str_,
cs64 size_)
{
int rv;
struct statvfs st;
+11 -2
View File
@@ -26,6 +26,15 @@
namespace fs
{
bool
has_space(const std::string &str,
cs64 size);
has_space(const char *str,
cs64 size);
static
inline
bool
has_space(const std::string &path,
cs64 size)
{
return fs::has_space(path.c_str(),size);
}
}
+3 -3
View File
@@ -32,13 +32,13 @@ using std::string;
int
fs::info(const string &path_,
fs::info_t *info_)
fs::info(const char *path_,
fs::info_t *info_)
{
int rv;
struct statvfs st;
rv = fs::statvfs_cache(path_.c_str(),&st);
rv = fs::statvfs_cache(path_,&st);
if(rv == 0)
{
info_->readonly = StatVFS::readonly(st);
+11 -2
View File
@@ -26,6 +26,15 @@
namespace fs
{
int
info(const std::string &path,
fs::info_t *info);
info(const char *path,
fs::info_t *info);
static
inline
int
info(const std::string &path_,
fs::info_t *info_)
{
return fs::info(path_.c_str(),info_);
}
}
+9 -43
View File
@@ -289,11 +289,11 @@ fs::inode::get_algo(void)
return {};
}
fs::inode::ReaddirCalc::ReaddirCalc(const fs::path &branch_path_,
const fs::path &dirpath_)
fs::inode::ReaddirCalc::ReaddirCalc(const std::string &branch_path_,
const fs::relpath &dirpath_)
: _algo(::_algo_from_func(g_func.load())),
_branch_seed(::_branch_seed(branch_path_.native())),
_fusepath((dirpath_ / "__mergerfs__").native()),
_branch_seed(::_branch_seed(branch_path_)),
_fusepath(fs::relpath::concat(dirpath_,"__mergerfs__")),
_filename_offset(_fusepath.size() - std::string_view("__mergerfs__").size())
{
}
@@ -337,23 +337,11 @@ fs::inode::ReaddirCalc::calc(const char *name_,
u64
fs::inode::calc(const std::string &branch_path_,
const std::string &fusepath_,
const mode_t mode_,
const ino_t ino_)
{
return g_func.load()(branch_path_,
fusepath_,
mode_,
ino_);
}
u64
fs::inode::calc(const fs::path &branch_path_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const mode_t mode_,
const ino_t ino_)
{
return g_func.load()(branch_path_.native(),
return g_func.load()(branch_path_,
fusepath_.native(),
mode_,
ino_);
@@ -361,18 +349,7 @@ fs::inode::calc(const fs::path &branch_path_,
void
fs::inode::calc(const std::string &branch_path_,
const std::string &fusepath_,
struct stat *st_)
{
st_->st_ino = calc(branch_path_,
fusepath_,
st_->st_mode,
st_->st_ino);
}
void
fs::inode::calc(const fs::path &branch_path_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
struct stat *st_)
{
st_->st_ino = calc(branch_path_,
@@ -382,19 +359,8 @@ fs::inode::calc(const fs::path &branch_path_,
}
void
fs::inode::calc(const std::string &branch_path_,
const std::string &fusepath_,
struct fuse_statx *st_)
{
st_->ino = calc(branch_path_,
fusepath_,
st_->mode,
st_->ino);
}
void
fs::inode::calc(const fs::path &branch_path_,
const fs::path &fusepath_,
fs::inode::calc(const std::string &branch_path_,
const fs::relpath &fusepath_,
struct fuse_statx *st_)
{
st_->ino = calc(branch_path_,
+6 -16
View File
@@ -47,8 +47,8 @@ namespace fs
class ReaddirCalc
{
public:
ReaddirCalc(const fs::path &branch_path,
const fs::path &dirpath);
ReaddirCalc(const std::string &branch_path,
const fs::relpath &dirpath);
public:
u64 calc(const char *name,
@@ -59,7 +59,7 @@ namespace fs
private:
Algo _algo;
u64 _branch_seed;
std::string _fusepath;
fs::relpath _fusepath;
std::size_t _filename_offset;
};
@@ -67,25 +67,15 @@ namespace fs
std::string get_algo(void);
u64 calc(const std::string &basepath,
const std::string &fusepath,
const mode_t mode,
const ino_t ino);
u64 calc(const fs::path &basepath,
const fs::path &fusepath,
const fs::relpath &fusepath,
const mode_t mode,
const ino_t ino);
void calc(const std::string &basepath,
const std::string &fusepath,
struct stat *st);
void calc(const fs::path &basepath,
const fs::path &fusepath,
const fs::relpath &fusepath,
struct stat *st);
void calc(const std::string &basepath,
const std::string &fusepath,
struct fuse_statx *st);
void calc(const fs::path &basepath,
const fs::path &fusepath,
const fs::relpath &fusepath,
struct fuse_statx *st);
}
}
+5 -4
View File
@@ -20,11 +20,12 @@
#include "fs_close.hpp"
#include "fs_mktemp.hpp"
#include "fs_path.hpp"
#include "fs_statvfs.hpp"
#include "fs_unlink.hpp"
#include "statvfs_util.hpp"
#include <string>
#include <fcntl.h>
@@ -33,7 +34,7 @@ namespace fs
static
inline
bool
is_mounted_rofs(const fs::path &path_)
is_mounted_rofs(const std::string &path_)
{
int rv;
struct statvfs st;
@@ -46,7 +47,7 @@ namespace fs
static
inline
bool
is_rofs(const fs::path &path_)
is_rofs(const std::string &path_)
{
int fd;
std::string tmp_filepath;
@@ -64,7 +65,7 @@ namespace fs
static
inline
bool
is_rofs_but_not_mounted_ro(const fs::path &path_)
is_rofs_but_not_mounted_ro(const std::string &path_)
{
if(fs::is_mounted_rofs(path_))
return false;
+10 -1
View File
@@ -71,7 +71,7 @@ namespace fs
static
inline
int
lchown_check_on_error(const std::string &path_,
lchown_check_on_error(const char *path_,
const struct stat &st_)
{
int rv;
@@ -94,4 +94,13 @@ namespace fs
return 0;
}
static
inline
int
lchown_check_on_error(const std::string &path_,
const struct stat &st_)
{
return fs::lchown_check_on_error(path_.c_str(),st_);
}
}
+12 -4
View File
@@ -30,14 +30,22 @@ namespace fs
static
inline
int
link(const std::string &oldpath_,
const std::string &newpath_)
link(const char *oldpath_,
const char *newpath_)
{
int rv;
rv = ::link(oldpath_.c_str(),
newpath_.c_str());
rv = ::link(oldpath_,newpath_);
return ::to_neg_errno(rv);
}
static
inline
int
link(const std::string &oldpath_,
const std::string &newpath_)
{
return fs::link(oldpath_.c_str(),newpath_.c_str());
}
}
+12 -3
View File
@@ -29,17 +29,26 @@ namespace fs
static
inline
int
lremovexattr(const std::string &path_,
const char *attrname_)
lremovexattr(const char *path_,
const char *attrname_)
{
#ifdef USE_XATTR
int rv;
rv = ::lremovexattr(path_.c_str(),attrname_);
rv = ::lremovexattr(path_,attrname_);
return ::to_neg_errno(rv);
#else
return -ENOTSUP;
#endif
}
static
inline
int
lremovexattr(const std::string &path_,
const char *attrname_)
{
return fs::lremovexattr(path_.c_str(),attrname_);
}
}
+11 -2
View File
@@ -36,8 +36,8 @@ namespace fs
static
inline
int
lstatvfs(const std::string &path_,
struct statvfs *st_)
lstatvfs(const char *path_,
struct statvfs *st_)
{
int fd;
int rv;
@@ -52,4 +52,13 @@ namespace fs
return rv;
}
static
inline
int
lstatvfs(const std::string &path_,
struct statvfs *st_)
{
return fs::lstatvfs(path_.c_str(),st_);
}
}
+20 -2
View File
@@ -27,7 +27,7 @@ namespace fs
static
inline
int
lutimens(const std::string &path_,
lutimens(const char *path_,
const struct timespec ts_[2])
{
return fs::utimensat(AT_FDCWD,path_,ts_,AT_SYMLINK_NOFOLLOW);
@@ -36,7 +36,16 @@ namespace fs
static
inline
int
lutimens(const std::string &path_,
lutimens(const std::string &path_,
const struct timespec ts_[2])
{
return fs::lutimens(path_.c_str(),ts_);
}
static
inline
int
lutimens(const char *path_,
const struct stat &st_)
{
struct timespec ts[2];
@@ -46,4 +55,13 @@ namespace fs
return fs::lutimens(path_,ts);
}
static
inline
int
lutimens(const std::string &path_,
const struct stat &st_)
{
return fs::lutimens(path_.c_str(),st_);
}
}
+1 -1
View File
@@ -57,7 +57,7 @@ namespace fs
static
inline
int
mkdir(const fs::path &path_,
mkdir(const fs::relpath &path_,
const mode_t mode_)
{
return fs::mkdir(path_.c_str(),
+1 -1
View File
@@ -56,7 +56,7 @@ namespace fs
inline
int
mkdirat(const int dirfd_,
const fs::path &pathname_,
const fs::relpath &pathname_,
const mode_t mode_)
{
return fs::mkdirat(dirfd_,
+31 -21
View File
@@ -28,7 +28,6 @@
#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <string>
#include <tuple>
@@ -39,9 +38,9 @@ static char const CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmno
static size_t const CHARS_SIZE = (sizeof(CHARS) - 1);
static
fs::path
_generate_tmp_path(const fs::path &dirpath_,
const std::string &src_filename_)
std::string
_generate_tmp_path(const std::string &dirpath_,
const std::string_view src_filename_)
{
long name_max;
size_t substr_len;
@@ -54,24 +53,25 @@ _generate_tmp_path(const fs::path &dirpath_,
substr_len = std::min(src_filename_.size(),
(size_t)(name_max - PAD_LEN - 2ULL));
tmp_filename = '.';
tmp_filename += src_filename_.substr(0,substr_len);
tmp_filename = dirpath_;
tmp_filename += "/.";
tmp_filename.append(src_filename_.substr(0,substr_len));
tmp_filename += '_';
for(size_t i = 0; i < PAD_LEN; i++)
tmp_filename += CHARS[RND::rand64(CHARS_SIZE)];
return dirpath_ / tmp_filename;
return tmp_filename;
}
std::tuple<int,fs::path>
fs::mktemp_in_dir(const fs::path &dirpath_,
const fs::path &filename_,
const int flags_)
std::tuple<int,std::string>
fs::mktemp_in_dir(const std::string &dirpath_,
const std::string_view filename_,
const int flags_)
{
int fd;
int count;
int flags;
fs::path tmp_filepath;
std::string tmp_filepath;
count = MAX_ATTEMPTS;
flags = (flags_ | O_EXCL | O_CREAT);
@@ -83,19 +83,29 @@ fs::mktemp_in_dir(const fs::path &dirpath_,
if(fd == -EEXIST)
continue;
if(fd < 0)
return std::make_tuple(fd,fs::path());
return std::make_tuple(fd,std::string());
return std::make_tuple(fd,tmp_filepath);
return std::make_tuple(fd,std::move(tmp_filepath));
}
return std::make_tuple(-EEXIST,fs::path());
return std::make_tuple(-EEXIST,std::string());
}
std::tuple<int,fs::path>
fs::mktemp(const fs::path &filepath_,
const int flags_)
std::tuple<int,std::string>
fs::mktemp(const std::string &filepath_,
const int flags_)
{
return fs::mktemp_in_dir(filepath_.parent_path(),
filepath_.filename(),
flags_);
// Split filepath_ into dirpath and filename without an allocation:
// the dirpath is referenced as a substring; mktemp_in_dir copies
// it into the result. filename_view aliases the suffix.
auto slash = filepath_.rfind('/');
std::string dirpath = (slash == std::string::npos)
? std::string(".")
: filepath_.substr(0,slash);
std::string_view filename_view =
(slash == std::string::npos)
? std::string_view(filepath_)
: std::string_view(filepath_).substr(slash + 1);
return fs::mktemp_in_dir(dirpath,filename_view,flags_);
}
+8 -9
View File
@@ -18,20 +18,19 @@
#pragma once
#include "fs_path.hpp"
#include <string>
#include <string_view>
#include <tuple>
namespace fs
{
std::tuple<int,fs::path>
mktemp_in_dir(const fs::path &dirpath,
const fs::path &filename,
int const flags);
std::tuple<int,std::string>
mktemp_in_dir(const std::string &dirpath,
const std::string_view filename,
int const flags);
std::tuple<int,fs::path>
mktemp(const fs::path &filepath,
const int flags);
std::tuple<int,std::string>
mktemp(const std::string &filepath,
const int flags);
}
+1 -1
View File
@@ -22,7 +22,7 @@
int
fs::mount(const std::string &tgt_)
fs::mount(const char *tgt_)
{
return subprocess::call({"mount",tgt_});
}
+10 -1
View File
@@ -20,7 +20,16 @@
#include <string>
namespace fs
{
int mount(const std::string &tgt);
int mount(const char *tgt);
static
inline
int
mount(const std::string &tgt_)
{
return fs::mount(tgt_.c_str());
}
}
+2 -3
View File
@@ -18,8 +18,7 @@
#pragma once
#include "fs_path.hpp"
#include <string>
#include <vector>
@@ -27,7 +26,7 @@ namespace fs
{
struct Mount
{
fs::path dir;
std::string dir;
std::string fsname;
std::string type;
std::string opts;
+17 -15
View File
@@ -63,22 +63,19 @@ static
int
_movefile_and_open(const Policy::Create &createFunc_,
const Branches::Ptr &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const std::string &branchpath_,
const fs::relpath &fusepath_,
int origfd_)
{
int rv;
int dstfd_flags;
int origfd_flags;
s64 src_size;
fs::path fusedir;
fs::path src_branch;
fs::path src_filepath;
fs::path dst_filepath;
fs::relpath fusedir;
fs::relpath src_filepath;
fs::relpath dst_filepath;
std::vector<Branch*> dst_branch;
src_branch = branchpath_;
rv = createFunc_(branches_,fusepath_,dst_branch);
if(rv < 0)
return rv;
@@ -98,12 +95,17 @@ _movefile_and_open(const Policy::Create &createFunc_,
fusedir = fusepath_.parent_path();
rv = fs::clonepath(src_branch,dst_branch[0]->path,fusedir);
rv = fs::clonepath(branchpath_,dst_branch[0]->path,fusedir);
if(rv < 0)
return -ENOSPC;
src_filepath = src_branch / fusepath_;
dst_filepath = dst_branch[0]->path / fusepath_;
// src_filepath / dst_filepath are fs::relpath in the per-branch
// splice form: rel anchored, prefix written via set_prefix() so the
// syscall sees branch + '/' + fusepath as a single string.
src_filepath = fusepath_;
src_filepath.set_prefix(branchpath_);
dst_filepath = fusepath_;
dst_filepath.set_prefix(dst_branch[0]->path);
rv = fs::copyfile(src_filepath,dst_filepath,{.cleanup_failure = true});
if(rv < 0)
@@ -122,8 +124,8 @@ _movefile_and_open(const Policy::Create &createFunc_,
int
fs::movefile_and_open(const Policy::Create &policy_,
const Branches::Ptr &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const std::string &branchpath_,
const fs::relpath &fusepath_,
const int origfd_)
{
return ::_movefile_and_open(policy_,
@@ -136,8 +138,8 @@ fs::movefile_and_open(const Policy::Create &policy_,
int
fs::movefile_and_open_as_root(const Policy::Create &policy_,
const Branches::Ptr &branches_,
const fs::path &branchpath_,
const fs::path &fusepath_,
const std::string &branchpath_,
const fs::relpath &fusepath_,
const int origfd_)
{
return fs::movefile_and_open(policy_,
+4 -4
View File
@@ -30,14 +30,14 @@ namespace fs
int
movefile_and_open(const Policy::Create &policy,
const Branches::Ptr &branches_,
const fs::path &branchpath,
const fs::path &fusepath,
const std::string &branchpath,
const fs::relpath &fusepath,
const int origfd);
int
movefile_and_open_as_root(const Policy::Create &policy,
const Branches::Ptr &branches_,
const fs::path &branchpath,
const fs::path &fusepath,
const std::string &branchpath,
const fs::relpath &fusepath,
int origfd);
}
+9 -1
View File
@@ -78,8 +78,16 @@ namespace fs
static
inline
int
open_dir_ro(const std::string &path_)
open_dir_ro(const char *path_)
{
return fs::open(path_,O_RDONLY|O_DIRECTORY);
}
static
inline
int
open_dir_ro(const std::string &path_)
{
return fs::open(path_.c_str(),O_RDONLY|O_DIRECTORY);
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ namespace fs
inline
int
open_as(const ugid_t ugid_,
const fs::path &path_,
const fs::relpath &path_,
const int flags_,
const mode_t mode_)
{
@@ -54,7 +54,7 @@ namespace fs
inline
int
open_as(const ugid_t ugid_,
const fs::path &path_,
const fs::relpath &path_,
const int flags_,
const mode_t mode_)
{
+1 -1
View File
@@ -62,7 +62,7 @@ namespace fs
inline
int
openat(const int dirfd_,
const fs::path &pathname_,
const fs::relpath &pathname_,
const int flags_,
const mode_t mode_ = 0)
{
-19
View File
@@ -1,19 +0,0 @@
/*
ISC License
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
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 "fs_path.hpp"
+2 -2
View File
@@ -73,8 +73,8 @@ fs::readahead(cu64 dev_,
}
int
fs::readahead(const std::string path_,
cu64 size_in_kb_)
fs::readahead(const char *path_,
cu64 size_in_kb_)
{
int rv;
struct stat st;
+2 -4
View File
@@ -20,8 +20,6 @@
#include "base_types.h"
#include <string>
namespace fs
{
@@ -35,6 +33,6 @@ namespace fs
cu64 size_in_kb);
int
readahead(const std::string path,
cu64 size_in_kb);
readahead(const char *path,
cu64 size_in_kb);
}
+15 -5
View File
@@ -27,6 +27,20 @@
namespace fs
{
static
inline
ssize_t
readlink(const char *path_,
char *buf_,
const size_t bufsiz_)
{
ssize_t rv;
rv = ::readlink(path_,buf_,bufsiz_);
return ::to_neg_errno(rv);
}
static
inline
ssize_t
@@ -34,10 +48,6 @@ namespace fs
char *buf_,
const size_t bufsiz_)
{
ssize_t rv;
rv = ::readlink(path_.c_str(),buf_,bufsiz_);
return ::to_neg_errno(rv);
return fs::readlink(path_.c_str(),buf_,bufsiz_);
}
}
+4 -7
View File
@@ -18,9 +18,6 @@
#pragma once
#include <string>
#include <limits.h>
#include <stdlib.h>
@@ -29,16 +26,16 @@ namespace fs
static
inline
char *
realpath(const std::string &path_,
char *resolved_path_)
realpath(const char *path_,
char *resolved_path_)
{
return ::realpath(path_.c_str(),resolved_path_);
return ::realpath(path_,resolved_path_);
}
static
inline
char *
realpath(const std::string &path_)
realpath(const char *path_)
{
return fs::realpath(path_,NULL);
}
+5 -5
View File
@@ -23,6 +23,8 @@
#include <string>
#include <vector>
#include <limits.h>
void
fs::realpathize(std::vector<std::string> *strs_)
@@ -32,10 +34,8 @@ fs::realpathize(std::vector<std::string> *strs_)
for(size_t i = 0; i < strs_->size(); i++)
{
rv = fs::realpath((*strs_)[i],resolved_path);
if(rv == NULL)
continue;
(*strs_)[i] = rv;
rv = fs::realpath((*strs_)[i].c_str(),resolved_path);
if(rv != NULL)
(*strs_)[i] = rv;
}
}
+56 -25
View File
@@ -21,8 +21,8 @@
#include "fs_statvfs.hpp"
#include "statvfs_util.hpp"
#include "mutex.hpp"
#include <mutex>
#include <shared_mutex>
#include <string>
#include <unordered_map>
@@ -36,11 +36,32 @@ struct Element
struct statvfs st;
};
typedef std::unordered_map<std::string,Element> statvfs_cache;
// Transparent hash / equality so we can look up by std::string_view
// (or const char*) without constructing a std::string. Requires
// unordered_map's Hash and KeyEqual to be transparent (C++20). The
// insert path still constructs a string for the key when missing,
// but the hot read path is alloc-free.
struct string_hash
{
using is_transparent = void;
std::size_t operator()(const std::string &s) const noexcept { return std::hash<std::string_view>{}(s); }
std::size_t operator()(std::string_view s) const noexcept { return std::hash<std::string_view>{}(s); }
std::size_t operator()(const char *s) const noexcept { return std::hash<std::string_view>{}(s); }
};
static u64 g_timeout = 0;
static statvfs_cache g_cache;
static Mutex g_cache_lock;
struct string_eq
{
using is_transparent = void;
bool operator()(std::string_view a, std::string_view b) const noexcept { return a == b; }
};
typedef std::unordered_map<std::string,Element,string_hash,string_eq> statvfs_cache;
// Cache is read heavy. Use a shared mutex so concurrent reads can
// work in parallel and only inserts or refreshes take a unique lock.
static std::shared_mutex g_cache_mutex;
static u64 g_timeout = 0;
static statvfs_cache g_cache;
static
@@ -67,39 +88,49 @@ fs::statvfs_cache_timeout(cu64 timeout_)
}
int
fs::statvfs_cache(const std::string &path_,
struct statvfs *st_)
fs::statvfs_cache(const char *path_,
struct statvfs *st_)
{
int rv;
Element *e;
u64 now;
if(g_timeout == 0)
return fs::statvfs(path_,st_);
rv = 0;
now = ::_get_time();
mutex_lock(g_cache_lock);
std::string_view key(path_);
{
std::shared_lock<std::shared_mutex> lk(g_cache_mutex);
e = &g_cache[path_];
auto it = g_cache.find(key);
if((it != g_cache.end()) && ((now - it->second.time) <= g_timeout))
{
*st_ = it->second.st;
return 0;
}
}
if((now - e->time) > g_timeout)
int rv = 0;
std::unique_lock<std::shared_mutex> lk(g_cache_mutex);
// operator[] requires a string key (no transparent insert overload),
// so this path does construct a std::string. Only happens on insert /
// refresh -- the read-only fast path above is alloc-free.
Element &e = g_cache[std::string(key)];
if((now - e.time) > g_timeout)
{
e->time = now;
rv = fs::statvfs(path_,&e->st);
e.time = now;
rv = fs::statvfs(path_,&e.st);
}
*st_ = e->st;
mutex_unlock(g_cache_lock);
*st_ = e.st;
return rv;
}
int
fs::statvfs_cache_readonly(const std::string &path_,
bool *readonly_)
fs::statvfs_cache_readonly(const char *path_,
bool *readonly_)
{
int rv;
struct statvfs st;
@@ -112,8 +143,8 @@ fs::statvfs_cache_readonly(const std::string &path_,
}
int
fs::statvfs_cache_spaceavail(const std::string &path_,
u64 *spaceavail_)
fs::statvfs_cache_spaceavail(const char *path_,
u64 *spaceavail_)
{
int rv;
struct statvfs st;
@@ -126,8 +157,8 @@ fs::statvfs_cache_spaceavail(const std::string &path_,
}
int
fs::statvfs_cache_spaceused(const std::string &path_,
u64 *spaceused_)
fs::statvfs_cache_spaceused(const char *path_,
u64 *spaceused_)
{
int rv;
struct statvfs st;
+44 -8
View File
@@ -33,18 +33,54 @@ namespace fs
statvfs_cache_timeout(cu64 timeout);
int
statvfs_cache(const std::string &path,
struct statvfs *st);
statvfs_cache(const char *path,
struct statvfs *st);
static
inline
int
statvfs_cache(const std::string &path_,
struct statvfs *st_)
{
return fs::statvfs_cache(path_.c_str(),st_);
}
int
statvfs_cache_readonly(const std::string &path,
bool *readonly);
statvfs_cache_readonly(const char *path,
bool *readonly);
static
inline
int
statvfs_cache_readonly(const std::string &path_,
bool *readonly_)
{
return fs::statvfs_cache_readonly(path_.c_str(),readonly_);
}
int
statvfs_cache_spaceavail(const std::string &path,
u64 *spaceavail);
statvfs_cache_spaceavail(const char *path,
u64 *spaceavail);
static
inline
int
statvfs_cache_spaceavail(const std::string &path_,
u64 *spaceavail_)
{
return fs::statvfs_cache_spaceavail(path_.c_str(),spaceavail_);
}
int
statvfs_cache_spaceused(const std::string &path,
u64 *spaceused);
statvfs_cache_spaceused(const char *path,
u64 *spaceused);
static
inline
int
statvfs_cache_spaceused(const std::string &path_,
u64 *spaceused_)
{
return fs::statvfs_cache_spaceused(path_.c_str(),spaceused_);
}
}
+14 -5
View File
@@ -21,6 +21,8 @@
#include "errno.hpp"
#include "to_neg_errno.hpp"
#include <string>
#ifdef __FreeBSD__
# include <sys/param.h>
# include <sys/mount.h>
@@ -30,19 +32,18 @@
# include <sys/mount.h>
#endif
#include <string>
namespace fs
{
static
inline
int
umount2(const std::string target_,
const int flags_)
umount2(const char *target_,
const int flags_)
{
int rv;
rv = ::umount2(target_.c_str(),
rv = ::umount2(target_,
flags_);
return ::to_neg_errno(rv);
@@ -51,8 +52,16 @@ namespace fs
static
inline
int
umount_lazy(const std::string target_)
umount_lazy(const char *target_)
{
return fs::umount2(target_,MNT_DETACH);
}
static
inline
int
umount_lazy(const std::string &target_)
{
return fs::umount_lazy(target_.c_str());
}
}
+17 -6
View File
@@ -40,9 +40,9 @@
static
inline
bool
_can_call_lutimes(const int dirfd_,
const std::string &path_,
const int flags_)
_can_call_lutimes(const int dirfd_,
const char *path_,
const int flags_)
{
return ((flags_ == AT_SYMLINK_NOFOLLOW) &&
((dirfd_ == AT_FDCWD) ||
@@ -120,7 +120,7 @@ static
inline
int
_set_utime_omit_to_current_value(const int dirfd_,
const std::string &path_,
const char *path_,
const struct timespec ts_[2],
struct timeval tv_[2],
const int flags_)
@@ -206,7 +206,7 @@ static
inline
int
_convert_timespec_to_timeval(const int dirfd_,
const std::string &path_,
const char *path_,
const struct timespec ts_[2],
struct timeval tv_[2],
struct timeval **tvp_,
@@ -265,7 +265,7 @@ namespace fs
inline
int
utimensat(const int dirfd_,
const std::string &path_,
const char *path_,
const struct timespec ts_[2],
const int flags_)
{
@@ -291,4 +291,15 @@ namespace fs
return -ENOTSUP;
}
static
inline
int
utimensat(const int dirfd_,
const std::string &path_,
const struct timespec ts_[2],
const int flags_)
{
return fs::utimensat(dirfd_,path_.c_str(),ts_,flags_);
}
}
+21 -21
View File
@@ -36,17 +36,17 @@ constexpr std::chrono::milliseconds SLEEP_DURATION = std::chrono::milliseconds(3
static
bool
_branch_is_mounted(const struct stat &src_st_,
const fs::path &branch_path_)
const std::string &branch_path_)
{
int rv;
struct stat st;
fs::path filepath;
std::string filepath;
rv = fs::lgetxattr(branch_path_,"user.mergerfs.branch",NULL,0);
if(rv >= 0)
return true;
filepath = branch_path_ / ".mergerfs.branch";
filepath = branch_path_ + "/.mergerfs.branch";
rv = fs::exists(filepath);
if(rv)
return true;
@@ -55,7 +55,7 @@ _branch_is_mounted(const struct stat &src_st_,
if(rv >= 0)
return false;
filepath = branch_path_ / ".mergerfs.branch_mounts_here";
filepath = branch_path_ + "/.mergerfs.branch_mounts_here";
rv = fs::exists(filepath);
if(rv)
return false;
@@ -69,13 +69,13 @@ _branch_is_mounted(const struct stat &src_st_,
static
void
_check_mounted(const struct stat &src_st_,
const std::set<fs::path> &tgt_paths_,
std::vector<fs::path> *successes_,
std::vector<fs::path> *failures_)
_check_mounted(const struct stat &src_st_,
const std::set<std::string> &tgt_paths_,
std::vector<std::string> *successes_,
std::vector<std::string> *failures_)
{
std::vector<fs::path> &successes = *successes_;
std::vector<fs::path> &failures = *failures_;
std::vector<std::string> &successes = *successes_;
std::vector<std::string> &failures = *failures_;
for(auto const &tgt_path : tgt_paths_)
{
@@ -92,13 +92,13 @@ _check_mounted(const struct stat &src_st_,
static
int
_wait_for_mount(const struct stat &src_st_,
const std::vector<fs::path> &tgt_paths_,
const std::vector<std::string> &tgt_paths_,
const std::chrono::milliseconds &timeout_)
{
bool first_loop;
std::vector<fs::path> successes;
std::vector<fs::path> failures;
std::set<fs::path> tgt_paths;
std::vector<std::string> successes;
std::vector<std::string> failures;
std::set<std::string> tgt_paths;
std::chrono::time_point<std::chrono::steady_clock> now;
std::chrono::time_point<std::chrono::steady_clock> deadline;
@@ -120,13 +120,13 @@ _wait_for_mount(const struct stat &src_st_,
for(const auto &path : successes)
{
tgt_paths.erase(path);
SysLog::info("{} is ready",path.string());
SysLog::info("{} is ready",path);
}
if(first_loop)
{
for(const auto &path : failures)
SysLog::notice("{} is not ready, waiting",path.string());
SysLog::notice("{} is not ready, waiting",path);
first_loop = false;
}
@@ -135,14 +135,14 @@ _wait_for_mount(const struct stat &src_st_,
}
for(const auto &path : failures)
SysLog::notice("{} not ready within timeout",path.string());
SysLog::notice("{} not ready within timeout",path);
return failures.size();
}
int
fs::wait_for_mount(const fs::path &src_path_,
const std::vector<fs::path> &tgt_paths_,
fs::wait_for_mount(const std::string &src_path_,
const std::vector<std::string> &tgt_paths_,
const std::chrono::milliseconds &timeout_)
{
int rv;
@@ -151,7 +151,7 @@ fs::wait_for_mount(const fs::path &src_path_,
rv = fs::stat(src_path_,&src_st);
if(rv < 0)
SysLog::error("Error stat'ing mount path: {} ({})",
src_path_.string(),
src_path_,
::strerror(-rv));
for(auto &tgt_path : tgt_paths_)
@@ -163,7 +163,7 @@ fs::wait_for_mount(const fs::path &src_path_,
rv = fs::mount(tgt_path);
SysLog::notice("mount {}: {}",
tgt_path.string(),
tgt_path,
((rv == 0) ? "success" : "fail"));
}
+3 -4
View File
@@ -17,16 +17,15 @@
#pragma once
#include "fs_path.hpp"
#include <chrono>
#include <string>
#include <vector>
namespace fs
{
int
wait_for_mount(const fs::path &srcpath,
const std::vector<fs::path> &tgtpaths,
wait_for_mount(const std::string &srcpath,
const std::vector<std::string> &tgtpaths,
const std::chrono::milliseconds &timeout);
}
+5 -6
View File
@@ -31,12 +31,12 @@ static
int
_access(const Policy::Search &searchFunc_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const int mask_)
{
int rv;
StrVec basepaths;
fs::path fullpath;
fs::relpath fullpath;
std::vector<Branch*> branches;
rv = searchFunc_(branches_,fusepath_,branches);
@@ -45,7 +45,7 @@ _access(const Policy::Search &searchFunc_,
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
fullpath.assign_concat(branches[0]->path,fusepath_);
rv = fs::eaccess(fullpath,mask_);
@@ -54,13 +54,12 @@ _access(const Policy::Search &searchFunc_,
int
FUSE::access(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
const fs::relpath &fusepath_,
int mask_)
{
const fs::path fusepath{fusepath_};
return ::_access(cfg.func.access.policy,
cfg.branches,
fusepath,
fusepath_,
mask_);
}
+2 -1
View File
@@ -19,11 +19,12 @@
#pragma once
#include "fuse_req_ctx.h"
#include "fs_path.hpp"
namespace FUSE
{
int
access(const fuse_req_ctx_t *ctx,
const char *fusepath,
const fs::relpath &fusepath,
int mask);
}
+1 -1
View File
@@ -23,7 +23,7 @@
int
FUSE::bmap(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
const fs::relpath &fusepath_,
size_t blocksize_,
u64 *idx_)
{
+2 -1
View File
@@ -20,6 +20,7 @@
#include "base_types.h"
#include "fuse_req_ctx.h"
#include "fs_path.hpp"
#include <cstddef>
@@ -28,7 +29,7 @@ namespace FUSE
{
int
bmap(const fuse_req_ctx_t *ctx,
const char *fusepath,
const fs::relpath &fusepath,
size_t blocksize,
u64 *idx);
}
+14 -24
View File
@@ -30,33 +30,24 @@
#include <string>
static
void
_chmod_loop_core(const std::string &basepath_,
const fs::path &fusepath_,
const mode_t mode_,
PolicyRV *prv_)
{
int rv;
fs::path fullpath;
fullpath = basepath_ / fusepath_;
rv = fs::lchmod(fullpath,mode_);
prv_->insert(rv,basepath_);
}
static
void
_chmod_loop(const std::vector<Branch*> &branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const mode_t mode_,
PolicyRV *prv_)
{
fs::relpath fullpath = fusepath_;
for(auto &branch : branches_)
{
::_chmod_loop_core(branch->path,fusepath_,mode_,prv_);
int rv;
fullpath.set_prefix(branch->path);
rv = fs::lchmod(fullpath,mode_);
prv_->insert(rv,branch->path);
}
}
@@ -65,7 +56,7 @@ int
_chmod(const Policy::Action &actionFunc_,
const Policy::Search &searchFunc_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const mode_t mode_)
{
int rv;
@@ -96,7 +87,7 @@ _chmod(const Policy::Action &actionFunc_,
static
int
_chmod(const fs::path &fusepath_,
_chmod(const fs::relpath &fusepath_,
const mode_t mode_)
{
@@ -109,10 +100,9 @@ _chmod(const fs::path &fusepath_,
int
FUSE::chmod(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
const fs::relpath &fusepath_,
mode_t mode_)
{
const fs::path fusepath{fusepath_};
return ::_chmod(fusepath,mode_);
return ::_chmod(fusepath_,mode_);
}
+2 -1
View File
@@ -19,6 +19,7 @@
#pragma once
#include "fuse_req_ctx.h"
#include "fs_path.hpp"
#include <sys/stat.h>
@@ -27,6 +28,6 @@ namespace FUSE
{
int
chmod(const fuse_req_ctx_t *ctx,
const char *fusepath,
const fs::relpath &fusepath,
mode_t mode);
}
+13 -24
View File
@@ -30,35 +30,25 @@
#include <vector>
static
void
_chown_loop_core(const fs::path &basepath_,
const fs::path &fusepath_,
const uid_t uid_,
const gid_t gid_,
PolicyRV *prv_)
{
int rv;
fs::path fullpath;
fullpath = basepath_ / fusepath_;
rv = fs::lchown(fullpath,uid_,gid_);
prv_->insert(rv,basepath_);
}
static
void
_chown_loop(const std::vector<Branch*> &branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const uid_t uid_,
const gid_t gid_,
PolicyRV *prv_)
{
fs::relpath fullpath = fusepath_;
for(const auto &branch : branches_)
{
::_chown_loop_core(branch->path,fusepath_,uid_,gid_,prv_);
int rv;
fullpath.set_prefix(branch->path);
rv = fs::lchown(fullpath,uid_,gid_);
prv_->insert(rv,branch->path);
}
}
@@ -67,7 +57,7 @@ int
_chown(const Policy::Action &actionFunc_,
const Policy::Search &searchFunc_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
const uid_t uid_,
const gid_t gid_)
{
@@ -100,16 +90,15 @@ _chown(const Policy::Action &actionFunc_,
int
FUSE::chown(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
const fs::relpath &fusepath_,
uid_t uid_,
gid_t gid_)
{
const fs::path fusepath{fusepath_};
return ::_chown(cfg.func.chown.policy,
cfg.func.getattr.policy,
cfg.branches,
fusepath,
fusepath_,
uid_,
gid_);
}
+2 -1
View File
@@ -19,6 +19,7 @@
#pragma once
#include "fuse_req_ctx.h"
#include "fs_path.hpp"
#include <unistd.h>
@@ -27,7 +28,7 @@ namespace FUSE
{
int
chown(const fuse_req_ctx_t *ctx,
const char *fusepath,
const fs::relpath &fusepath,
uid_t uid,
gid_t gid);
}
+11 -12
View File
@@ -165,7 +165,7 @@ _config_to_ffi_flags(const int tid_,
static
int
_create_core(const ugid_t ugid_,
const fs::path &fullpath_,
const fs::relpath &fullpath_,
mode_t mode_,
const mode_t umask_,
const int flags_)
@@ -180,16 +180,16 @@ static
int
_create_core(const ugid_t ugid_,
const Branch *branch_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
const mode_t umask_)
{
int rv;
FileInfo *fi;
fs::path fullpath;
fs::relpath fullpath;
fullpath = branch_->path / fusepath_;
fullpath.assign_concat(branch_->path,fusepath_);
rv = ::_create_core(ugid_,fullpath,mode_,umask_,ffi_->flags);
if(rv < 0)
@@ -208,14 +208,14 @@ _create(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
const mode_t umask_)
{
int rv;
fs::path fullpath;
fs::path fusedirpath;
fs::relpath fullpath;
fs::relpath fusedirpath;
std::vector<Branch*> createpaths;
std::vector<Branch*> existingpaths;
@@ -258,7 +258,7 @@ _(const PassthroughIOEnum e_,
static
int
_create(const fuse_req_ctx_t *ctx_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
mode_t mode_,
fuse_file_info_t *ffi_)
{
@@ -334,7 +334,7 @@ _create(const fuse_req_ctx_t *ctx_,
delete fi;
ffi_->fh = 0;
SysLog::crit(msg);
SysLog::crit("{}",msg);
fmt::println(stderr,"{}",msg);
return -EIO;
@@ -345,11 +345,10 @@ _create(const fuse_req_ctx_t *ctx_,
int
FUSE::create(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
const fs::relpath &fusepath_,
mode_t mode_,
fuse_file_info_t *ffi_)
{
const fs::path fusepath{fusepath_};
return ::_create(ctx_,fusepath,mode_,ffi_);
return ::_create(ctx_,fusepath_,mode_,ffi_);
}
+1 -1
View File
@@ -27,7 +27,7 @@ namespace FUSE
{
int
create(const fuse_req_ctx_t *ctx,
const char *fusepath,
const fs::relpath &fusepath,
mode_t mode,
fuse_file_info_t *ffi);
}
-60
View File
@@ -1,60 +0,0 @@
/*
ISC License
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
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_flock.hpp"
#include "errno.hpp"
#include "fileinfo.hpp"
#include "fs_flock.hpp"
#include "state.hpp"
#include "fuse.h"
static
int
_flock(const int fd_,
const int operation_)
{
int rv;
rv = fs::flock(fd_,operation_);
return rv;
}
// In cases where the node was opened but unlinked there will be no
// path and no guarentee of a `fh`. It could always do the lookup but
// why bother if the kernel has provided it to us? The reason the
// function doesn't need to run within the visit lambda is because
// since the request is outstanding the kernel won't be releasing the
// node and therefore the entry will be valid over the lifetime of
// this function.
int
FUSE::flock(const fuse_req_ctx_t *ctx_,
const fuse_file_info_t *ffi_,
int op_)
{
FileInfo *fi;
fi = state.get_fi(ctx_,ffi_->fh);
if(not fi)
return -EBADF;
return ::_flock(fi->fd,op_);
}
-30
View File
@@ -1,30 +0,0 @@
/*
ISC License
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
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 "fuse.h"
namespace FUSE
{
int
flock(const fuse_req_ctx_t *ctx,
const fuse_file_info_t *ffi,
int op);
}
+8 -19
View File
@@ -37,7 +37,7 @@
static
void
_set_stat_if_leads_to_dir(const fs::path &path_,
_set_stat_if_leads_to_dir(const fs::relpath &path_,
struct stat *st_)
{
int rv;
@@ -55,7 +55,7 @@ _set_stat_if_leads_to_dir(const fs::path &path_,
static
void
_set_stat_if_leads_to_reg(const fs::path &path_,
_set_stat_if_leads_to_reg(const fs::relpath &path_,
struct stat *st_)
{
int rv;
@@ -129,14 +129,14 @@ static
int
_getattr(const Policy::Search &searchFunc_,
const Branches::Ptr branches_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
struct stat *st_,
const bool symlinkify_,
const time_t symlinkify_timeout_,
FollowSymlinks followsymlinks_)
{
int rv;
fs::path fullpath;
fs::relpath fullpath;
std::vector<Branch*> branches;
rv = searchFunc_(branches_,fusepath_,branches);
@@ -145,7 +145,7 @@ _getattr(const Policy::Search &searchFunc_,
if(branches.empty())
return -ENOENT;
fullpath = branches[0]->path / fusepath_;
fullpath.assign_concat(branches[0]->path,fusepath_);
switch(followsymlinks_)
{
@@ -183,7 +183,7 @@ _getattr(const Policy::Search &searchFunc_,
}
int
_getattr(const fs::path &fusepath_,
_getattr(const fs::relpath &fusepath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
@@ -209,18 +209,7 @@ _getattr(const fs::path &fusepath_,
int
FUSE::getattr(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
const fs::path fusepath{fusepath_};
return FUSE::getattr(ctx_,fusepath,st_,timeout_);
}
int
FUSE::getattr(const fuse_req_ctx_t *ctx_,
const fs::path &fusepath_,
const fs::relpath &fusepath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
@@ -228,7 +217,7 @@ FUSE::getattr(const fuse_req_ctx_t *ctx_,
}
int
FUSE::getattr(const fs::path &fusepath_,
FUSE::getattr(const fs::relpath &fusepath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{

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