From fa3266ca7fe8f0b6a2e06e72d88c62f64e9203d2 Mon Sep 17 00:00:00 2001 From: trapexit Date: Thu, 8 May 2025 00:00:53 -0500 Subject: [PATCH] Add podman release build tooling + misc build fixes (#1455) --- Makefile | 48 +- buildtools/build-mergerfs | 49 + buildtools/build-release | 101 + buildtools/containerfiles/debian.10.amd64 | 9 + buildtools/containerfiles/debian.10.arm64 | 9 + buildtools/containerfiles/debian.10.armhf | 9 + buildtools/containerfiles/debian.10.i386 | 9 + buildtools/containerfiles/debian.11.amd64 | 9 + buildtools/containerfiles/debian.11.arm64 | 9 + buildtools/containerfiles/debian.11.armhf | 9 + buildtools/containerfiles/debian.11.i386 | 9 + buildtools/containerfiles/debian.12.amd64 | 9 + buildtools/containerfiles/debian.12.arm64 | 9 + buildtools/containerfiles/debian.12.armhf | 9 + buildtools/containerfiles/debian.12.i386 | 9 + buildtools/containerfiles/debian.9.amd64 | 9 + buildtools/containerfiles/debian.9.arm64 | 9 + buildtools/containerfiles/debian.9.armhf | 9 + buildtools/containerfiles/debian.9.i386 | 9 + buildtools/containerfiles/fedora.37.amd64 | 9 + buildtools/containerfiles/fedora.37.arm64 | 9 + buildtools/containerfiles/fedora.37.armhf | 9 + buildtools/containerfiles/fedora.37.i386 | 9 + buildtools/containerfiles/fedora.38.amd64 | 9 + buildtools/containerfiles/fedora.38.arm64 | 9 + buildtools/containerfiles/fedora.38.armhf | 9 + buildtools/containerfiles/fedora.38.i386 | 9 + buildtools/containerfiles/fedora.39.amd64 | 9 + buildtools/containerfiles/fedora.40.amd64 | 9 + buildtools/containerfiles/fedora.41.amd64 | 9 + buildtools/containerfiles/fedora.42.amd64 | 9 + buildtools/containerfiles/rockylinux.8.amd64 | 9 + buildtools/containerfiles/rockylinux.8.arm64 | 9 + buildtools/containerfiles/rockylinux.8.armhf | 9 + buildtools/containerfiles/rockylinux.8.i386 | 9 + buildtools/containerfiles/rockylinux.9.amd64 | 9 + buildtools/containerfiles/rockylinux.9.arm64 | 9 + buildtools/containerfiles/rockylinux.9.armhf | 9 + buildtools/containerfiles/rockylinux.9.i386 | 9 + buildtools/containerfiles/static.amd64 | 13 + buildtools/containerfiles/static.arm64 | 13 + buildtools/containerfiles/static.armhf | 13 + buildtools/containerfiles/static.i386 | 13 + buildtools/containerfiles/static.installer | 10 + buildtools/containerfiles/tarball | 13 + buildtools/containerfiles/ubuntu.18.04.amd64 | 9 + buildtools/containerfiles/ubuntu.18.04.arm64 | 9 + buildtools/containerfiles/ubuntu.18.04.armhf | 9 + buildtools/containerfiles/ubuntu.18.04.i386 | 9 + buildtools/containerfiles/ubuntu.20.04.amd64 | 9 + buildtools/containerfiles/ubuntu.20.04.arm64 | 9 + buildtools/containerfiles/ubuntu.20.04.armhf | 9 + buildtools/containerfiles/ubuntu.20.04.i386 | 9 + buildtools/containerfiles/ubuntu.22.04.amd64 | 9 + buildtools/containerfiles/ubuntu.22.04.arm64 | 9 + buildtools/containerfiles/ubuntu.22.04.armhf | 9 + buildtools/containerfiles/ubuntu.22.04.i386 | 9 + buildtools/containerfiles/ubuntu.24.04.amd64 | 9 + buildtools/containerfiles/ubuntu.24.04.arm64 | 9 + buildtools/containerfiles/ubuntu.24.04.armhf | 9 + buildtools/containerfiles/ubuntu.24.04.i386 | 9 + buildtools/git2debcl | 2 +- buildtools/install-build-pkgs | 31 +- libfuse/Makefile | 9 +- libfuse/lib/fuse.cpp | 122 +- libfuse/lib/helper.c | 6 - libfuse/util/fusermount.c | 3 +- man/mergerfs.1 | 3384 +----------------- src/enum.hpp | 4 +- src/fs_attr.cpp | 4 +- src/fs_copy_file_range.cpp | 4 +- src/fs_fadvise.cpp | 4 +- src/fs_ficlone.cpp | 4 +- src/fs_futimens.hpp | 6 +- src/fs_futimesat.cpp | 4 +- src/fs_sendfile.cpp | 4 +- src/fs_statx.hpp | 6 +- src/fs_utimensat.hpp | 6 +- src/fuse_statx.cpp | 191 +- src/fuse_statx_supported.icpp | 186 + src/fuse_statx_unsupported.icpp | 21 + src/mergerfs.cpp | 8 +- src/num.cpp | 18 +- src/policy_eprand.cpp | 17 +- src/policy_rand.cpp | 18 +- src/procfs_get_name.cpp | 18 +- src/rnd.hpp | 19 +- src/to_string.cpp | 14 +- src/tofrom_wrapper.hpp | 8 +- src/ugid.hpp | 4 +- 90 files changed, 1208 insertions(+), 3658 deletions(-) create mode 100755 buildtools/build-mergerfs create mode 100755 buildtools/build-release create mode 100644 buildtools/containerfiles/debian.10.amd64 create mode 100644 buildtools/containerfiles/debian.10.arm64 create mode 100644 buildtools/containerfiles/debian.10.armhf create mode 100644 buildtools/containerfiles/debian.10.i386 create mode 100644 buildtools/containerfiles/debian.11.amd64 create mode 100644 buildtools/containerfiles/debian.11.arm64 create mode 100644 buildtools/containerfiles/debian.11.armhf create mode 100644 buildtools/containerfiles/debian.11.i386 create mode 100644 buildtools/containerfiles/debian.12.amd64 create mode 100644 buildtools/containerfiles/debian.12.arm64 create mode 100644 buildtools/containerfiles/debian.12.armhf create mode 100644 buildtools/containerfiles/debian.12.i386 create mode 100644 buildtools/containerfiles/debian.9.amd64 create mode 100644 buildtools/containerfiles/debian.9.arm64 create mode 100644 buildtools/containerfiles/debian.9.armhf create mode 100644 buildtools/containerfiles/debian.9.i386 create mode 100644 buildtools/containerfiles/fedora.37.amd64 create mode 100644 buildtools/containerfiles/fedora.37.arm64 create mode 100644 buildtools/containerfiles/fedora.37.armhf create mode 100644 buildtools/containerfiles/fedora.37.i386 create mode 100644 buildtools/containerfiles/fedora.38.amd64 create mode 100644 buildtools/containerfiles/fedora.38.arm64 create mode 100644 buildtools/containerfiles/fedora.38.armhf create mode 100644 buildtools/containerfiles/fedora.38.i386 create mode 100644 buildtools/containerfiles/fedora.39.amd64 create mode 100644 buildtools/containerfiles/fedora.40.amd64 create mode 100644 buildtools/containerfiles/fedora.41.amd64 create mode 100644 buildtools/containerfiles/fedora.42.amd64 create mode 100644 buildtools/containerfiles/rockylinux.8.amd64 create mode 100644 buildtools/containerfiles/rockylinux.8.arm64 create mode 100644 buildtools/containerfiles/rockylinux.8.armhf create mode 100644 buildtools/containerfiles/rockylinux.8.i386 create mode 100644 buildtools/containerfiles/rockylinux.9.amd64 create mode 100644 buildtools/containerfiles/rockylinux.9.arm64 create mode 100644 buildtools/containerfiles/rockylinux.9.armhf create mode 100644 buildtools/containerfiles/rockylinux.9.i386 create mode 100644 buildtools/containerfiles/static.amd64 create mode 100644 buildtools/containerfiles/static.arm64 create mode 100644 buildtools/containerfiles/static.armhf create mode 100644 buildtools/containerfiles/static.i386 create mode 100644 buildtools/containerfiles/static.installer create mode 100644 buildtools/containerfiles/tarball create mode 100644 buildtools/containerfiles/ubuntu.18.04.amd64 create mode 100644 buildtools/containerfiles/ubuntu.18.04.arm64 create mode 100644 buildtools/containerfiles/ubuntu.18.04.armhf create mode 100644 buildtools/containerfiles/ubuntu.18.04.i386 create mode 100644 buildtools/containerfiles/ubuntu.20.04.amd64 create mode 100644 buildtools/containerfiles/ubuntu.20.04.arm64 create mode 100644 buildtools/containerfiles/ubuntu.20.04.armhf create mode 100644 buildtools/containerfiles/ubuntu.20.04.i386 create mode 100644 buildtools/containerfiles/ubuntu.22.04.amd64 create mode 100644 buildtools/containerfiles/ubuntu.22.04.arm64 create mode 100644 buildtools/containerfiles/ubuntu.22.04.armhf create mode 100644 buildtools/containerfiles/ubuntu.22.04.i386 create mode 100644 buildtools/containerfiles/ubuntu.24.04.amd64 create mode 100644 buildtools/containerfiles/ubuntu.24.04.arm64 create mode 100644 buildtools/containerfiles/ubuntu.24.04.armhf create mode 100644 buildtools/containerfiles/ubuntu.24.04.i386 create mode 100644 src/fuse_statx_supported.icpp create mode 100644 src/fuse_statx_unsupported.icpp diff --git a/Makefile b/Makefile index a12854b2..df0e489a 100644 --- a/Makefile +++ b/Makefile @@ -23,9 +23,7 @@ FIND = find INSTALL = install MKTEMP = mktemp STRIP = strip -PANDOC = pandoc SED = sed -RPMBUILD = rpmbuild GIT2DEBCL = ./buildtools/git2debcl PKGCONFIG = pkg-config @@ -39,10 +37,10 @@ endif USE_XATTR = 1 UGID_USE_RWLOCK = 0 -ifeq ($(DEBUG),1) -OPT_FLAGS := -O0 -g -fsanitize=undefined -else +ifeq ($(NDEBUG),1) OPT_FLAGS := -O2 -DNDEBUG +else +OPT_FLAGS := -O0 -DDEBUG -g -fsanitize=undefined endif ifeq ($(STATIC),1) @@ -187,7 +185,7 @@ install-mount-tools: install-base $(MKDIR) -p "$(INSTALLBINDIR)" $(MAKE) -C libfuse install -install-man: $(MANPAGE) +install-man: man/$(MANPAGE) $(MKDIR) -p "$(INSTALLMAN1DIR)" $(INSTALL) -v -m 0644 "man/$(MANPAGE)" "$(INSTALLMAN1DIR)/$(MANPAGE)" @@ -210,17 +208,8 @@ uninstall-mount.mergerfs: uninstall-man: $(RM) -f "$(INSTALLMAN1DIR)/$(MANPAGE)" -$(MANPAGE): README.md -ifneq ($(shell $(PANDOC) --version 2> /dev/null),) - $(PANDOC) -s -t man -o "man/$(MANPAGE)" README.md -else - $(warning "pandoc does not appear available: unable to build manpage") -endif - -man: $(MANPAGE) - .PHONY: tarball -tarball: man changelog version +tarball: changelog version $(eval VERSION := $(shell cat VERSION)) $(eval VERSION := $(subst -,_,$(VERSION))) $(eval FILENAME := mergerfs-$(VERSION)) @@ -260,7 +249,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 -ba rpmbuild/SOURCES/mergerfs.spec \ --define "_topdir $(CURDIR)/rpmbuild" .PHONY: install-build-pkgs @@ -269,6 +258,29 @@ install-build-pkgs: .PHONY: libfuse libfuse: - $(MAKE) DEBUG=$(DEBUG) -C libfuse + $(MAKE) NDEBUG=$(NDEBUG) -C libfuse + +.PHONY: release release-amd64 +release: + ./buildtools/build-release \ + --target=all \ + --cleanup \ + --branch=$(shell git branch --show-current) +release-amd64: + ./buildtools/build-release \ + --target=amd64 \ + --cleanup \ + --branch=$(shell git branch --show-current) +release-arm64: + ./buildtools/build-release \ + --target=arm64 \ + --cleanup \ + --branch=$(shell git branch --show-current) +release-static: + ./buildtools/build-release \ + --target=static \ + --cleanup \ + --branch=$(shell git branch --show-current) + -include $(DEPS) diff --git a/buildtools/build-mergerfs b/buildtools/build-mergerfs new file mode 100755 index 00000000..6b6eb3b4 --- /dev/null +++ b/buildtools/build-mergerfs @@ -0,0 +1,49 @@ +#!/bin/sh + +BRANCH="${1:-master}" +SRCDIR="/tmp/mergerfs" + +git clone https://github.com/trapexit/mergerfs "${SRCDIR}" -b "${BRANCH}" + +cd "${SRCDIR}" + +git log HEAD^1.. + +mkdir /build +if [ -e /usr/bin/apt-get ]; then + make deb + if [ $? -ne 0 ]; then + . /etc/lsb-release + cp -v /etc/lsb-release "/build/${DISTRIB_ID}.${DISTRIB_CODENAME}.${DISTRIB_RELEASE}.$(arch).FAILED" + fi + find /tmp/ \ + -type f \ + -name "*.deb" \ + -not -name "*dbgsym*" \ + -exec cp -v {} /build/ \; +elif [ -e /usr/bin/dnf ]; then + make rpm + if [ $? -ne 0 ]; then + . /etc/lsb-release + cp -v /etc/lsb-release "/build/${DISTRIB_ID}.${DISTRIB_CODENAME}.${DISTRIB_RELEASE}.$(arch).FAILED" + fi + find /tmp/ \ + -type f \ + -name "*.rpm" \ + -not -name "*sym*" \ + -not -name "*src.rpm" \ + -exec cp -v {} /build/ \; +elif [ -e /usr/bin/yum ]; then + . /opt/rh/devtoolset-9/enable + make rpm + find /tmp/ \ + -type f \ + -name "*.rpm" \ + -not -name "*sym*" \ + -not -name "*src.rpm" \ + -exec cp -v {} /build/ \; +elif [ -e /sbin/apk ]; then + echo "NOT YET SUPPORTED" +elif [ -e /usr/sbin/pkg ]; then + echo "NOT YET SUPPORTED" +fi diff --git a/buildtools/build-release b/buildtools/build-release new file mode 100755 index 00000000..d43ecaeb --- /dev/null +++ b/buildtools/build-release @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +import subprocess +import sys +import os +import argparse +import time + + +def build(containerfile, + pkgdirpath, + branch): + timestamp = time.time_ns() + args = ['podman', + 'build', + '--pull=always', + '--force-rm', + '-o',pkgdirpath, + '-f',containerfile, + f'--build-arg=BRANCH={branch}', + f'--build-arg=BUILD_TIMESTAMP={timestamp}', + 'buildtools/'] + print(args) + subprocess.run(args) + + +def setup(): + args = ['sudo', + 'apt-get', + 'install', + '-fy', + 'qemu-user-static', + 'qemu-user-binfmt'] + print(args) + subprocess.run(args) + + +def podman_cleanup(): + args = ['podman', + 'system', + 'prune', + '-af'] + print(args) + subprocess.run(args) + + +def parse_args(): + p = argparse.ArgumentParser() + p.add_argument('--target', + default='debian.12.amd64') + p.add_argument('--pkgdirpath', + default='build/pkgs/') + p.add_argument('--branch', + default='master') + p.add_argument('--setup', + required=False, + action='store_true') + p.add_argument('--cleanup', + required=False, + action='store_true') + + return p.parse_args() + + +def main(): + args = parse_args() + print(args) + + if args.setup: + setup() + sys.exit(0) + + containerfiles = [] + basepath = 'buildtools/containerfiles' + if os.path.exists(f'{basepath}/{args.target}'): + containerfiles.append(f'{basepath}/{args.target}') + elif args.target == 'all': + for root,dirnames,filenames in os.walk(basepath): + for filename in filenames: + containerfile = f'buildtools/containerfiles/{filename}' + containerfiles.append(containerfile) + else: + for root,dirnames,filenames in os.walk(basepath): + for filename in filenames: + if args.target not in filename: + continue + containerfile = f'buildtools/containerfiles/{filename}' + containerfiles.append(containerfile) + + for containerfile in containerfiles: + if args.cleanup: + podman_cleanup() + build(containerfile, + args.pkgdirpath, + args.branch) + + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/buildtools/containerfiles/debian.10.amd64 b/buildtools/containerfiles/debian.10.amd64 new file mode 100644 index 00000000..38797450 --- /dev/null +++ b/buildtools/containerfiles/debian.10.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 debian:10 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.10.arm64 b/buildtools/containerfiles/debian.10.arm64 new file mode 100644 index 00000000..6107b012 --- /dev/null +++ b/buildtools/containerfiles/debian.10.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 debian:10 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.10.armhf b/buildtools/containerfiles/debian.10.armhf new file mode 100644 index 00000000..27ce2b26 --- /dev/null +++ b/buildtools/containerfiles/debian.10.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf debian:10 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.10.i386 b/buildtools/containerfiles/debian.10.i386 new file mode 100644 index 00000000..5c24e26b --- /dev/null +++ b/buildtools/containerfiles/debian.10.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 debian:10 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.11.amd64 b/buildtools/containerfiles/debian.11.amd64 new file mode 100644 index 00000000..f200f774 --- /dev/null +++ b/buildtools/containerfiles/debian.11.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 debian:11 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.11.arm64 b/buildtools/containerfiles/debian.11.arm64 new file mode 100644 index 00000000..873378d7 --- /dev/null +++ b/buildtools/containerfiles/debian.11.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 debian:11 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.11.armhf b/buildtools/containerfiles/debian.11.armhf new file mode 100644 index 00000000..61aa8fb2 --- /dev/null +++ b/buildtools/containerfiles/debian.11.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf debian:11 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.11.i386 b/buildtools/containerfiles/debian.11.i386 new file mode 100644 index 00000000..3d2458c1 --- /dev/null +++ b/buildtools/containerfiles/debian.11.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 debian:11 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.12.amd64 b/buildtools/containerfiles/debian.12.amd64 new file mode 100644 index 00000000..ca428a90 --- /dev/null +++ b/buildtools/containerfiles/debian.12.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 debian:12 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.12.arm64 b/buildtools/containerfiles/debian.12.arm64 new file mode 100644 index 00000000..b26a1949 --- /dev/null +++ b/buildtools/containerfiles/debian.12.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 debian:12 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.12.armhf b/buildtools/containerfiles/debian.12.armhf new file mode 100644 index 00000000..2793487e --- /dev/null +++ b/buildtools/containerfiles/debian.12.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf debian:12 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.12.i386 b/buildtools/containerfiles/debian.12.i386 new file mode 100644 index 00000000..2118d8a1 --- /dev/null +++ b/buildtools/containerfiles/debian.12.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 debian:12 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.9.amd64 b/buildtools/containerfiles/debian.9.amd64 new file mode 100644 index 00000000..9e583999 --- /dev/null +++ b/buildtools/containerfiles/debian.9.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 debian:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.9.arm64 b/buildtools/containerfiles/debian.9.arm64 new file mode 100644 index 00000000..f937c27b --- /dev/null +++ b/buildtools/containerfiles/debian.9.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 debian:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.9.armhf b/buildtools/containerfiles/debian.9.armhf new file mode 100644 index 00000000..0ad6ec72 --- /dev/null +++ b/buildtools/containerfiles/debian.9.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf debian:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/debian.9.i386 b/buildtools/containerfiles/debian.9.i386 new file mode 100644 index 00000000..9f159ec6 --- /dev/null +++ b/buildtools/containerfiles/debian.9.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 debian:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.37.amd64 b/buildtools/containerfiles/fedora.37.amd64 new file mode 100644 index 00000000..01edb437 --- /dev/null +++ b/buildtools/containerfiles/fedora.37.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 fedora:37 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.37.arm64 b/buildtools/containerfiles/fedora.37.arm64 new file mode 100644 index 00000000..4f93e1e5 --- /dev/null +++ b/buildtools/containerfiles/fedora.37.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 fedora:37 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.37.armhf b/buildtools/containerfiles/fedora.37.armhf new file mode 100644 index 00000000..c118bd81 --- /dev/null +++ b/buildtools/containerfiles/fedora.37.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf fedora:37 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.37.i386 b/buildtools/containerfiles/fedora.37.i386 new file mode 100644 index 00000000..db3327ca --- /dev/null +++ b/buildtools/containerfiles/fedora.37.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 fedora:37 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.38.amd64 b/buildtools/containerfiles/fedora.38.amd64 new file mode 100644 index 00000000..7a855521 --- /dev/null +++ b/buildtools/containerfiles/fedora.38.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 fedora:38 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.38.arm64 b/buildtools/containerfiles/fedora.38.arm64 new file mode 100644 index 00000000..d6f9d8f7 --- /dev/null +++ b/buildtools/containerfiles/fedora.38.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 fedora:38 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.38.armhf b/buildtools/containerfiles/fedora.38.armhf new file mode 100644 index 00000000..b5607b5d --- /dev/null +++ b/buildtools/containerfiles/fedora.38.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf fedora:38 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.38.i386 b/buildtools/containerfiles/fedora.38.i386 new file mode 100644 index 00000000..23a7ef45 --- /dev/null +++ b/buildtools/containerfiles/fedora.38.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 fedora:38 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.39.amd64 b/buildtools/containerfiles/fedora.39.amd64 new file mode 100644 index 00000000..4c043579 --- /dev/null +++ b/buildtools/containerfiles/fedora.39.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 fedora:39 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.40.amd64 b/buildtools/containerfiles/fedora.40.amd64 new file mode 100644 index 00000000..f858b260 --- /dev/null +++ b/buildtools/containerfiles/fedora.40.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 fedora:40 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.41.amd64 b/buildtools/containerfiles/fedora.41.amd64 new file mode 100644 index 00000000..40f7d4c9 --- /dev/null +++ b/buildtools/containerfiles/fedora.41.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 fedora:41 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/fedora.42.amd64 b/buildtools/containerfiles/fedora.42.amd64 new file mode 100644 index 00000000..9be9784b --- /dev/null +++ b/buildtools/containerfiles/fedora.42.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 fedora:42 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.8.amd64 b/buildtools/containerfiles/rockylinux.8.amd64 new file mode 100644 index 00000000..569d897d --- /dev/null +++ b/buildtools/containerfiles/rockylinux.8.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 rockylinux:8 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.8.arm64 b/buildtools/containerfiles/rockylinux.8.arm64 new file mode 100644 index 00000000..f95e5a5f --- /dev/null +++ b/buildtools/containerfiles/rockylinux.8.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 rockylinux:8 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.8.armhf b/buildtools/containerfiles/rockylinux.8.armhf new file mode 100644 index 00000000..e6408a45 --- /dev/null +++ b/buildtools/containerfiles/rockylinux.8.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf rockylinux:8 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.8.i386 b/buildtools/containerfiles/rockylinux.8.i386 new file mode 100644 index 00000000..ce688393 --- /dev/null +++ b/buildtools/containerfiles/rockylinux.8.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 rockylinux:8 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.9.amd64 b/buildtools/containerfiles/rockylinux.9.amd64 new file mode 100644 index 00000000..5d517c79 --- /dev/null +++ b/buildtools/containerfiles/rockylinux.9.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 rockylinux:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.9.arm64 b/buildtools/containerfiles/rockylinux.9.arm64 new file mode 100644 index 00000000..a85cd179 --- /dev/null +++ b/buildtools/containerfiles/rockylinux.9.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 rockylinux:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.9.armhf b/buildtools/containerfiles/rockylinux.9.armhf new file mode 100644 index 00000000..122009d3 --- /dev/null +++ b/buildtools/containerfiles/rockylinux.9.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf rockylinux:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/rockylinux.9.i386 b/buildtools/containerfiles/rockylinux.9.i386 new file mode 100644 index 00000000..9171dfc8 --- /dev/null +++ b/buildtools/containerfiles/rockylinux.9.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 rockylinux:9 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/static.amd64 b/buildtools/containerfiles/static.amd64 new file mode 100644 index 00000000..0f859ac3 --- /dev/null +++ b/buildtools/containerfiles/static.amd64 @@ -0,0 +1,13 @@ +ARG BUILD_TIMESTAMP=0 +FROM --platform=linux/amd64 alpine:latest as build +COPY install-build-pkgs /tmp/ +RUN /tmp/install-build-pkgs +ARG BRANCH=master +RUN git clone https://github.com/trapexit/mergerfs /tmp/mergerfs -b "${BRANCH}" +WORKDIR /tmp/mergerfs +RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install +RUN mkdir /build +RUN tar cvfz /build/mergerfs-static-linux_amd64.tar.gz --directory=/tmp usr sbin + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/static.arm64 b/buildtools/containerfiles/static.arm64 new file mode 100644 index 00000000..6043beba --- /dev/null +++ b/buildtools/containerfiles/static.arm64 @@ -0,0 +1,13 @@ +ARG BUILD_TIMESTAMP=0 +FROM --platform=linux/arm64 alpine:latest as build +COPY install-build-pkgs /tmp/ +RUN /tmp/install-build-pkgs +ARG BRANCH=master +RUN git clone https://github.com/trapexit/mergerfs /tmp/mergerfs -b "${BRANCH}" +WORKDIR /tmp/mergerfs +RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install +RUN mkdir /build +RUN tar cvfz /build/mergerfs-static-linux_arm64.tar.gz --directory=/tmp usr sbin + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/static.armhf b/buildtools/containerfiles/static.armhf new file mode 100644 index 00000000..c3ee863b --- /dev/null +++ b/buildtools/containerfiles/static.armhf @@ -0,0 +1,13 @@ +ARG BUILD_TIMESTAMP=0 +FROM --platform=linux/armhf alpine:latest as build +COPY install-build-pkgs /tmp/ +RUN /tmp/install-build-pkgs +ARG BRANCH=master +RUN git clone https://github.com/trapexit/mergerfs /tmp/mergerfs -b "${BRANCH}" +WORKDIR /tmp/mergerfs +RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install +RUN mkdir /build +RUN tar cvfz /build/mergerfs-static-linux_armhf.tar.gz --directory=/tmp usr sbin + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/static.i386 b/buildtools/containerfiles/static.i386 new file mode 100644 index 00000000..baf44102 --- /dev/null +++ b/buildtools/containerfiles/static.i386 @@ -0,0 +1,13 @@ +ARG BUILD_TIMESTAMP=0 +FROM --platform=linux/i386 alpine:latest as build +COPY install-build-pkgs /tmp/ +RUN /tmp/install-build-pkgs +ARG BRANCH=master +RUN git clone https://github.com/trapexit/mergerfs /tmp/mergerfs -b "${BRANCH}" +WORKDIR /tmp/mergerfs +RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install +RUN mkdir /build +RUN tar cvfz /build/mergerfs-static-linux_i386.tar.gz --directory=/tmp usr sbin + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/static.installer b/buildtools/containerfiles/static.installer new file mode 100644 index 00000000..77cea23b --- /dev/null +++ b/buildtools/containerfiles/static.installer @@ -0,0 +1,10 @@ +ARG BUILD_TIMESTAMP=0 +FROM --platform=linux/amd64 ubuntu:latest as build +RUN apt-get update && apt-get install -y 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 makeself /tmp/mergerfs/ /build/mergerfs-installer.run "mergerfs" echo "foo" + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/tarball b/buildtools/containerfiles/tarball new file mode 100644 index 00000000..04a12b9c --- /dev/null +++ b/buildtools/containerfiles/tarball @@ -0,0 +1,13 @@ +FROM debian:buster as tarball +COPY install-build-pkgs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN git clone https://github.com/trapexit/mergerfs /tmp/mergerfs -b "${BRANCH}" +RUN cd /tmp/mergerfs && make version tarball +RUN mkdir /build +RUN cp -v /tmp/mergerfs/*.tar.gz /build/ + + +FROM scratch +COPY --from=tarball /build/ / diff --git a/buildtools/containerfiles/ubuntu.18.04.amd64 b/buildtools/containerfiles/ubuntu.18.04.amd64 new file mode 100644 index 00000000..d7926fcd --- /dev/null +++ b/buildtools/containerfiles/ubuntu.18.04.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 ubuntu:18.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.18.04.arm64 b/buildtools/containerfiles/ubuntu.18.04.arm64 new file mode 100644 index 00000000..a60a6aa6 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.18.04.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 ubuntu:18.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.18.04.armhf b/buildtools/containerfiles/ubuntu.18.04.armhf new file mode 100644 index 00000000..806c9eb3 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.18.04.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf ubuntu:18.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.18.04.i386 b/buildtools/containerfiles/ubuntu.18.04.i386 new file mode 100644 index 00000000..f859d1fc --- /dev/null +++ b/buildtools/containerfiles/ubuntu.18.04.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 ubuntu:18.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.20.04.amd64 b/buildtools/containerfiles/ubuntu.20.04.amd64 new file mode 100644 index 00000000..40c38788 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.20.04.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 ubuntu:20.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.20.04.arm64 b/buildtools/containerfiles/ubuntu.20.04.arm64 new file mode 100644 index 00000000..7e0d8aa9 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.20.04.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 ubuntu:20.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.20.04.armhf b/buildtools/containerfiles/ubuntu.20.04.armhf new file mode 100644 index 00000000..082b01b0 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.20.04.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf ubuntu:20.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.20.04.i386 b/buildtools/containerfiles/ubuntu.20.04.i386 new file mode 100644 index 00000000..53da35fd --- /dev/null +++ b/buildtools/containerfiles/ubuntu.20.04.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 ubuntu:20.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.22.04.amd64 b/buildtools/containerfiles/ubuntu.22.04.amd64 new file mode 100644 index 00000000..1acdbf3b --- /dev/null +++ b/buildtools/containerfiles/ubuntu.22.04.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 ubuntu:22.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.22.04.arm64 b/buildtools/containerfiles/ubuntu.22.04.arm64 new file mode 100644 index 00000000..2a9cd535 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.22.04.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 ubuntu:22.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.22.04.armhf b/buildtools/containerfiles/ubuntu.22.04.armhf new file mode 100644 index 00000000..76753bb1 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.22.04.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf ubuntu:22.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.22.04.i386 b/buildtools/containerfiles/ubuntu.22.04.i386 new file mode 100644 index 00000000..a7e036d5 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.22.04.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 ubuntu:22.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.24.04.amd64 b/buildtools/containerfiles/ubuntu.24.04.amd64 new file mode 100644 index 00000000..f3cf97f7 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.24.04.amd64 @@ -0,0 +1,9 @@ +FROM --platform=linux/amd64 ubuntu:24.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.24.04.arm64 b/buildtools/containerfiles/ubuntu.24.04.arm64 new file mode 100644 index 00000000..a8da15a3 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.24.04.arm64 @@ -0,0 +1,9 @@ +FROM --platform=linux/arm64 ubuntu:24.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.24.04.armhf b/buildtools/containerfiles/ubuntu.24.04.armhf new file mode 100644 index 00000000..04c369b2 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.24.04.armhf @@ -0,0 +1,9 @@ +FROM --platform=linux/armhf ubuntu:24.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/containerfiles/ubuntu.24.04.i386 b/buildtools/containerfiles/ubuntu.24.04.i386 new file mode 100644 index 00000000..76b751d6 --- /dev/null +++ b/buildtools/containerfiles/ubuntu.24.04.i386 @@ -0,0 +1,9 @@ +FROM --platform=linux/i386 ubuntu:24.04 as build +COPY install-build-pkgs build-mergerfs /tmp/ +RUN /tmp/install-build-pkgs +ARG BUILD_TIMESTAMP=0 +ARG BRANCH=master +RUN /tmp/build-mergerfs $BRANCH + +FROM scratch +COPY --from=build /build/ / diff --git a/buildtools/git2debcl b/buildtools/git2debcl index 8d0423ce..051055c9 100755 --- a/buildtools/git2debcl +++ b/buildtools/git2debcl @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (c) 2016, Antonio SJ Musumeci diff --git a/buildtools/install-build-pkgs b/buildtools/install-build-pkgs index f426b025..8dfce8cf 100755 --- a/buildtools/install-build-pkgs +++ b/buildtools/install-build-pkgs @@ -7,34 +7,37 @@ if [ -e /usr/bin/apt-get ]; then install \ ca-certificates \ build-essential \ + dpkg \ git \ g++ \ debhelper \ - automake \ - fakeroot \ - libtool \ - lsb-release - apt-get -qy --no-install-suggests --no-install-recommends install python - apt-get -qy --no-install-suggests --no-install-recommends install python3 + lsb-release \ + fakeroot + apt-get -qy --force-yes --no-install-suggests --no-install-recommends install python + apt-get -qy --force-yes --no-install-suggests --no-install-recommends install python3 elif [ -e /usr/bin/dnf ]; then dnf -y update dnf -y install \ - git rpm-build gcc-c++ make which python3 automake \ - libtool gettext-devel + git rpm-build gcc-c++ make which python3 elif [ -e /usr/bin/yum ]; then yum -y update yum -y install \ git rpm-build gcc-c++ make which \ - python python-argparse \ - automake libtool gettext-devel + python python-argparse + yum -y install centos-release-scl + yum -y install devtoolset-9-gcc\* +elif [ -e /usr/bin/zypper ]; then + zypper update -y + zypper install -y \ + git rpm-build gcc-c++ make which \ + python elif [ -e /sbin/apk ]; then apk add \ - abuild git gcc g++ make autoconf \ - automake libtool gettext-dev linux-headers + abuild git gcc g++ make \ + linux-headers elif [ -e /usr/sbin/pkg ]; then pkg install \ - git gmake gcc autoconf automake libtool \ - gettext-tools + git gmake gcc fi if [ ! -e /usr/bin/python ]; then diff --git a/libfuse/Makefile b/libfuse/Makefile index 3565141a..f68ecb33 100644 --- a/libfuse/Makefile +++ b/libfuse/Makefile @@ -9,10 +9,10 @@ UTILS := INSTALLUTILS := endif -ifeq ($(DEBUG),1) -OPT_FLAGS := -O0 -g -fsanitize=undefined +ifeq ($(NDEBUG),1) +OPT_FLAGS := -O2 -DNDEBUG else -OPT_FLAGS := -O2 +OPT_FLAGS := -O0 -DDEBUG -g -fsanitize=undefined endif ifeq ($(LTO),1) @@ -41,7 +41,6 @@ SRC_C = \ lib/buffer.c \ lib/crc32b.c \ lib/debug.c \ - lib/fuse.c \ lib/fuse_dirents.c \ lib/fuse_lowlevel.c \ lib/node.c \ @@ -52,6 +51,7 @@ SRC_C = \ lib/helper.c \ lib/mount.c SRC_CPP = \ + lib/fuse.cpp \ lib/cpu.cpp \ lib/fuse_config.cpp \ lib/fuse_loop.cpp \ @@ -87,7 +87,6 @@ FUSE_FLAGS = \ -Ibuild \ -D_REENTRANT \ -D_FILE_OFFSET_BITS=64 \ - -DPACKAGE_VERSION=\"$(VERSION)\" \ -DFUSERMOUNT_DIR=\"$(FUSERMOUNT_DIR)\" LDFLAGS := \ ${LDFLAGS} \ diff --git a/libfuse/lib/fuse.cpp b/libfuse/lib/fuse.cpp index 4a14d535..c309ebe0 100644 --- a/libfuse/lib/fuse.cpp +++ b/libfuse/lib/fuse.cpp @@ -1617,6 +1617,14 @@ fuse_lib_lookup(fuse_req_t req, reply_entry(req,&e,err); } +static +void +fuse_lib_lseek(fuse_req_t req_, + struct fuse_in_header *hdr_) +{ + fuse_reply_err(req_,ENOSYS); +} + static void fuse_lib_forget(fuse_req_t req, @@ -2179,6 +2187,24 @@ fuse_lib_rename(fuse_req_t req, fuse_reply_err(req,err); } +static +void +fuse_lib_rename2(fuse_req_t req_, + struct fuse_in_header *hdr_) +{ + fuse_reply_err(req_,ENOSYS); +} + +static +void +fuse_lib_retrieve_reply(fuse_req_t req_, + void *cookie_, + uint64_t ino_, + off_t offset_) +{ + +} + static void fuse_lib_link(fuse_req_t req, @@ -3674,53 +3700,55 @@ fuse_prune_remembered_nodes(struct fuse *f_) static struct fuse_lowlevel_ops fuse_path_ops = { - .access = fuse_lib_access, - .bmap = fuse_lib_bmap, - .copy_file_range = fuse_lib_copy_file_range, - .create = fuse_lib_create, - .destroy = fuse_lib_destroy, - .fallocate = fuse_lib_fallocate, - .flock = fuse_lib_flock, - .flush = fuse_lib_flush, - .forget = fuse_lib_forget, - .forget_multi = fuse_lib_forget_multi, - .fsync = fuse_lib_fsync, - .fsyncdir = fuse_lib_fsyncdir, - .getattr = fuse_lib_getattr, - .getlk = fuse_lib_getlk, - .getxattr = fuse_lib_getxattr, - .init = fuse_lib_init, - .ioctl = fuse_lib_ioctl, - .link = fuse_lib_link, - .listxattr = fuse_lib_listxattr, - .lookup = fuse_lib_lookup, - .mkdir = fuse_lib_mkdir, - .mknod = fuse_lib_mknod, - .open = fuse_lib_open, - .opendir = fuse_lib_opendir, - .poll = fuse_lib_poll, - .read = fuse_lib_read, - .readdir = fuse_lib_readdir, - .readdir_plus = fuse_lib_readdir_plus, - .readlink = fuse_lib_readlink, - .release = fuse_lib_release, - .releasedir = fuse_lib_releasedir, - .removemapping = fuse_lib_removemapping, - .removexattr = fuse_lib_removexattr, - .rename = fuse_lib_rename, - .retrieve_reply = NULL, - .rmdir = fuse_lib_rmdir, - .setattr = fuse_lib_setattr, - .setlk = fuse_lib_setlk, - .setupmapping = fuse_lib_setupmapping, - .setxattr = fuse_lib_setxattr, - .statfs = fuse_lib_statfs, - .statx = fuse_lib_statx, - .symlink = fuse_lib_symlink, - .syncfs = fuse_lib_syncfs, - .tmpfile = fuse_lib_tmpfile, - .unlink = fuse_lib_unlink, - .write = fuse_lib_write, + .access = fuse_lib_access, + .bmap = fuse_lib_bmap, + .copy_file_range = fuse_lib_copy_file_range, + .create = fuse_lib_create, + .destroy = fuse_lib_destroy, + .fallocate = fuse_lib_fallocate, + .flock = fuse_lib_flock, + .flush = fuse_lib_flush, + .forget = fuse_lib_forget, + .forget_multi = fuse_lib_forget_multi, + .fsync = fuse_lib_fsync, + .fsyncdir = fuse_lib_fsyncdir, + .getattr = fuse_lib_getattr, + .getlk = fuse_lib_getlk, + .getxattr = fuse_lib_getxattr, + .init = fuse_lib_init, + .ioctl = fuse_lib_ioctl, + .link = fuse_lib_link, + .listxattr = fuse_lib_listxattr, + .lookup = fuse_lib_lookup, + .lseek = fuse_lib_lseek, + .mkdir = fuse_lib_mkdir, + .mknod = fuse_lib_mknod, + .open = fuse_lib_open, + .opendir = fuse_lib_opendir, + .poll = fuse_lib_poll, + .read = fuse_lib_read, + .readdir = fuse_lib_readdir, + .readdir_plus = fuse_lib_readdir_plus, + .readlink = fuse_lib_readlink, + .release = fuse_lib_release, + .releasedir = fuse_lib_releasedir, + .removemapping = fuse_lib_removemapping, + .removexattr = fuse_lib_removexattr, + .rename = fuse_lib_rename, + .rename2 = fuse_lib_rename2, + .retrieve_reply = fuse_lib_retrieve_reply, + .rmdir = fuse_lib_rmdir, + .setattr = fuse_lib_setattr, + .setlk = fuse_lib_setlk, + .setupmapping = fuse_lib_setupmapping, + .setxattr = fuse_lib_setxattr, + .statfs = fuse_lib_statfs, + .statx = fuse_lib_statx, + .symlink = fuse_lib_symlink, + .syncfs = fuse_lib_syncfs, + .tmpfile = fuse_lib_tmpfile, + .unlink = fuse_lib_unlink, + .write = fuse_lib_write, }; int diff --git a/libfuse/lib/helper.c b/libfuse/lib/helper.c index 09681504..66662d07 100644 --- a/libfuse/lib/helper.c +++ b/libfuse/lib/helper.c @@ -77,11 +77,6 @@ static void helper_help(void) ); } -static void helper_version(void) -{ - fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION); -} - static int fuse_helper_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) { @@ -97,7 +92,6 @@ static int fuse_helper_opt_proc(void *data, const char *arg, int key, return fuse_opt_add_arg(outargs, "-h"); case KEY_VERSION: - helper_version(); return 1; case FUSE_OPT_KEY_NONOPT: diff --git a/libfuse/util/fusermount.c b/libfuse/util/fusermount.c index 1451dda2..d714e3f8 100644 --- a/libfuse/util/fusermount.c +++ b/libfuse/util/fusermount.c @@ -1165,8 +1165,7 @@ static void usage(void) static void show_version(void) { - printf("fusermount version: %s\n", PACKAGE_VERSION); - exit(0); + exit(0); } int main(int argc, char *argv[]) diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 3e5bf19f..bdedb914 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -1,58 +1,84 @@ -.\"t -.\" Automatically generated by Pandoc 2.9.2.1 -.\" -.TH "mergerfs" "1" "" "mergerfs user manual" "" -.hy +.TH MERGERFS 1 "May 2025" "mergerfs" "User Commands" .SH NAME -.PP -mergerfs - a featureful union filesystem +mergerfs \- a featureful union filesystem .SH SYNOPSIS -.PP -mergerfs -o +.B mergerfs +[options] .SH DESCRIPTION -.PP -\f[B]mergerfs\f[R] is a union filesystem geared towards simplifying -storage and management of files across numerous commodity storage -devices. -It is similar to \f[B]mhddfs\f[R], \f[B]unionfs\f[R], and -\f[B]aufs\f[R]. +.B mergerfs +is a FUSE-based union filesystem geared towards simplifying storage and management of files across numerous commodity storage devices. It is similar to +.BR mhddfs , +.BR unionfs , +and +.BR aufs . +.SH QUICKSTART +.UR https://trapexit.github.io/mergerfs/quickstart +.LI https://trapexit.github.io/mergerfs/quickstart +.UE +.SH DOCUMENTATION +.UR https://trapexit.github.io/mergerfs +.LI https://trapexit.github.io/mergerfs +.UE +.SH SUPPORT +.UR https://trapexit.github.io/mergerfs/support +.LI https://trapexit.github.io/mergerfs/support +.UE .SH FEATURES .IP \[bu] 2 -Configurable behaviors / file placement -.IP \[bu] 2 -Ability to add or remove filesystems at will -.IP \[bu] 2 -Resistance to individual filesystem failure -.IP \[bu] 2 -Support for extended attributes (xattrs) -.IP \[bu] 2 -Support for file attributes (chattr) -.IP \[bu] 2 -Runtime configurable (via xattrs) -.IP \[bu] 2 -Works with heterogeneous filesystem types -.IP \[bu] 2 -Moving of file when filesystem runs out of space while writing -.IP \[bu] 2 +Logically combine numerous filesystems/paths into a single mount point +.IP \[bu] +Combine paths of the same or different filesystems +.IP \[bu] +Ability to add or remove filesystems/paths without impacting the rest of the data +.IP \[bu] +Unaffected by individual filesystem failure +.IP \[bu] +Configurable file selection and creation placement +.IP \[bu] +Works with filesystems of any size +.IP \[bu] +Works with filesystems of almost any type +.IP \[bu] Ignore read-only filesystems when creating files -.IP \[bu] 2 -Turn read-only files into symlinks to underlying file -.IP \[bu] 2 +.IP \[bu] Hard link copy-on-write / CoW -.IP \[bu] 2 +.IP \[bu] +Runtime configurable +.IP \[bu] +Support for extended attributes (xattrs) +.IP \[bu] +Support for file attributes (chattr) +.IP \[bu] Support for POSIX ACLs -.IP \[bu] 2 -Misc other things +.SH NON-FEATURES +.IP \[bu] 2 +Read/write overlay on top of read-only filesystem like OverlayFS +.IP \[bu] +File whiteout +.IP \[bu] +RAID-like parity calculation +.IP \[bu] +Redundancy +.IP \[bu] +Splitting of files across branches .SH HOW IT WORKS -.PP -mergerfs logically merges multiple paths together. -Think a union of sets. -The file/s or directory/s acted on or presented through mergerfs are -based on the policy chosen for that particular action. -Read more about policies below. -.IP +.B mergerfs +logically merges multiple filesystem paths together. It acts as a proxy to the underlying filesystem paths, combining the behaviors of some functions and being a selector for others. + +When the contents of a directory are requested, +.B mergerfs +combines the list of files from each directory, deduplicating entries, and returns that list. + +When a file or directory is created, a policy is first run to determine which branch will be selected for the creation. + +For functions which change attributes or remove the file, the behavior may be applied to all instances found. + +Read more about policies here: +.UR https://trapexit.github.io/mergerfs/config/functions_categories_and_policies +.LI https://trapexit.github.io/mergerfs/config/functions_categories_and_policies +.UE +.SH VISUALIZATION .nf -\f[C] A + B = C /disk1 /disk2 /merged | | | @@ -71,3258 +97,24 @@ A + B = C | +-- file5 | +-- file6 -\f[R] -.fi -.PP -mergerfs does \f[B]not\f[R] support the copy-on-write (CoW) or whiteout -behaviors found in \f[B]aufs\f[R] and \f[B]overlayfs\f[R]. -You can \f[B]not\f[R] mount a read-only filesystem and write to it. -However, mergerfs will ignore read-only filesystems when creating new -files so you can mix read-write and read-only filesystems. -It also does \f[B]not\f[R] split data across filesystems. -It is not RAID0 / striping. -It is simply a union of other filesystems. -.SH TERMINOLOGY -.IP \[bu] 2 -branch: A base path used in the pool. -.IP \[bu] 2 -pool: The mergerfs mount. -The union of the branches. -.IP \[bu] 2 -relative path: The path in the pool relative to the branch and mount. -.IP \[bu] 2 -function: A filesystem call (open, unlink, create, getattr, rmdir, etc.) -.IP \[bu] 2 -category: A collection of functions based on basic behavior (action, -create, search). -.IP \[bu] 2 -policy: The algorithm used to select a file when performing a function. -.IP \[bu] 2 -path preservation: Aspect of some policies which includes checking the -path for which a file would be created. -.SH BASIC SETUP -.PP -If you don\[cq]t already know that you have a special use case then just -start with one of the following option sets. -.SS You need \f[C]mmap\f[R] (used by rtorrent and many sqlite3 base software) -.PP -\f[C]cache.files=auto-full,dropcacheonclose=true,category.create=mfs\f[R] -.PP -or if you are on a Linux kernel >= 6.6.x mergerfs will enable a mode -that allows shared mmap when \f[C]cache.files=off\f[R]. -To be sure of the best performance between \f[C]cache.files=off\f[R] and -\f[C]cache.files=auto-full\f[R] you\[cq]ll need to do your own -benchmarking but often \f[C]off\f[R] is faster. -.SS You don\[cq]t need \f[C]mmap\f[R] -.PP -\f[C]cache.files=off,dropcacheonclose=true,category.create=mfs\f[R] -.SS Command Line -.PP -\f[C]mergerfs -o cache.files=auto-full,dropcacheonclose=true,category.create=mfs /mnt/hdd0:/mnt/hdd1 /media\f[R] -.SS /etc/fstab -.PP -\f[C]/mnt/hdd0:/mnt/hdd1 /media mergerfs cache.files=auto-full,dropcacheonclose=true,category.create=mfs 0 0\f[R] -.SS systemd mount -.PP -https://github.com/trapexit/mergerfs/wiki/systemd -.IP -.nf -\f[C] -[Unit] -Description=mergerfs service - -[Service] -Type=simple -KillMode=none -ExecStart=/usr/bin/mergerfs \[rs] - -f \[rs] - -o cache.files=auto-full \[rs] - -o dropcacheonclose=true \[rs] - -o category.create=mfs \[rs] - /mnt/hdd0:/mnt/hdd1 \[rs] - /media -ExecStop=/bin/fusermount -uz /media -Restart=on-failure - -[Install] -WantedBy=default.target -\f[R] -.fi -.PP -See the mergerfs wiki for real world -deployments (https://github.com/trapexit/mergerfs/wiki/Real-World-Deployments) -for comparisons / ideas. -.SH OPTIONS -.PP -These options are the same regardless of whether you use them with the -\f[C]mergerfs\f[R] commandline program, in fstab, or in a config file. -.SS mount options -.IP \[bu] 2 -\f[B]config\f[R]: Path to a config file. -Same arguments as below in key=val / ini style format. -.IP \[bu] 2 -\f[B]branches\f[R]: Colon delimited list of branches. -.IP \[bu] 2 -\f[B]minfreespace=SIZE\f[R]: The minimum space value used for creation -policies. -Can be overridden by branch specific option. -Understands `K', `M', and `G' to represent kilobyte, megabyte, and -gigabyte respectively. -(default: 4G) -.IP \[bu] 2 -\f[B]moveonenospc=BOOL|POLICY\f[R]: When enabled if a \f[B]write\f[R] -fails with \f[B]ENOSPC\f[R] (no space left on device) or -\f[B]EDQUOT\f[R] (disk quota exceeded) the policy selected will run to -find a new location for the file. -An attempt to move the file to that branch will occur (keeping all -metadata possible) and if successful the original is unlinked and the -write retried. -(default: false, true = mfs) -.IP \[bu] 2 -\f[B]inodecalc=passthrough|path-hash|devino-hash|hybrid-hash\f[R]: -Selects the inode calculation algorithm. -(default: hybrid-hash) -.IP \[bu] 2 -\f[B]dropcacheonclose=BOOL\f[R]: When a file is requested to be closed -call \f[C]posix_fadvise\f[R] on it first to instruct the kernel that we -no longer need the data and it can drop its cache. -Recommended when -\f[B]cache.files=partial|full|auto-full|per-process\f[R] to limit double -caching. -(default: false) -.IP \[bu] 2 -\f[B]direct-io-allow-mmap=BOOL\f[R]: On newer kernels (>= 6.6) it is -possible to disable file page caching while still allowing for shared -mmap support. -mergerfs will enable this feature if available but an option is provided -to turn it off for testing and debugging purposes. -(default: true) -.IP \[bu] 2 -\f[B]symlinkify=BOOL\f[R]: When enabled and a file is not writable and -its mtime or ctime is older than \f[B]symlinkify_timeout\f[R] files will -be reported as symlinks to the original files. -Please read more below before using. -(default: false) -.IP \[bu] 2 -\f[B]symlinkify_timeout=UINT\f[R]: Time to wait, in seconds, to activate -the \f[B]symlinkify\f[R] behavior. -(default: 3600) -.IP \[bu] 2 -\f[B]nullrw=BOOL\f[R]: Turns reads and writes into no-ops. -The request will succeed but do nothing. -Useful for benchmarking mergerfs. -(default: false) -.IP \[bu] 2 -\f[B]lazy-umount-mountpoint=BOOL\f[R]: mergerfs will attempt to -\[lq]lazy umount\[rq] the mountpoint before mounting itself. -Useful when performing live upgrades of mergerfs. -(default: false) -.IP \[bu] 2 -\f[B]ignorepponrename=BOOL\f[R]: Ignore path preserving on rename. -Typically rename and link act differently depending on the policy of -\f[C]create\f[R] (read below). -Enabling this will cause rename and link to always use the non-path -preserving behavior. -This means files, when renamed or linked, will stay on the same -filesystem. -(default: false) -.IP \[bu] 2 -\f[B]export-support=BOOL\f[R]: Sets a low-level FUSE feature intended to -indicate the filesystem can support being exported via NFS. -(default: true) -.IP \[bu] 2 -\f[B]security_capability=BOOL\f[R]: If false return ENOATTR when xattr -security.capability is queried. -(default: true) -.IP \[bu] 2 -\f[B]xattr=passthrough|noattr|nosys\f[R]: Runtime control of xattrs. -Default is to passthrough xattr requests. -`noattr' will short circuit as if nothing exists. -`nosys' will respond with ENOSYS as if xattrs are not supported or -disabled. -(default: passthrough) -.IP \[bu] 2 -\f[B]link_cow=BOOL\f[R]: When enabled if a regular file is opened which -has a link count > 1 it will copy the file to a temporary file and -rename over the original. -Breaking the link and providing a basic copy-on-write function similar -to cow-shell. -(default: false) -.IP \[bu] 2 -\f[B]statfs=base|full\f[R]: Controls how statfs works. -`base' means it will always use all branches in statfs calculations. -`full' is in effect path preserving and only includes branches where the -path exists. -(default: base) -.IP \[bu] 2 -\f[B]statfs_ignore=none|ro|nc\f[R]: `ro' will cause statfs calculations -to ignore available space for branches mounted or tagged as `read-only' -or `no create'. -`nc' will ignore available space for branches tagged as `no create'. -(default: none) -.IP \[bu] 2 -\f[B]nfsopenhack=off|git|all\f[R]: A workaround for exporting mergerfs -over NFS where there are issues with creating files for write while -setting the mode to read-only. -(default: off) -.IP \[bu] 2 -\f[B]branches-mount-timeout=UINT\f[R]: Number of seconds to wait at -startup for branches to be a mount other than the mountpoint\[cq]s -filesystem. -(default: 0) -.IP \[bu] 2 -\f[B]follow-symlinks=never|directory|regular|all\f[R]: Turns symlinks -into what they point to. -(default: never) -.IP \[bu] 2 -\f[B]link-exdev=passthrough|rel-symlink|abs-base-symlink|abs-pool-symlink\f[R]: -When a link fails with EXDEV optionally create a symlink to the file -instead. -.IP \[bu] 2 -\f[B]rename-exdev=passthrough|rel-symlink|abs-symlink\f[R]: When a -rename fails with EXDEV optionally move the file to a special directory -and symlink to it. -.IP \[bu] 2 -\f[B]readahead=UINT\f[R]: Set readahead (in kilobytes) for mergerfs and -branches if greater than 0. -(default: 0) -.IP \[bu] 2 -\f[B]posix_acl=BOOL\f[R]: Enable POSIX ACL support (if supported by -kernel and underlying filesystem). -(default: false) -.IP \[bu] 2 -\f[B]async_read=BOOL\f[R]: Perform reads asynchronously. -If disabled or unavailable the kernel will ensure there is at most one -pending read request per file handle and will attempt to order requests -by offset. -(default: true) -.IP \[bu] 2 -\f[B]fuse_msg_size=UINT\f[R]: Set the max number of pages per FUSE -message. -Only available on Linux >= 4.20 and ignored otherwise. -(min: 1; max: 256; default: 256) -.IP \[bu] 2 -\f[B]threads=INT\f[R]: Number of threads to use. -When used alone (\f[C]process-thread-count=-1\f[R]) it sets the number -of threads reading and processing FUSE messages. -When used together it sets the number of threads reading from FUSE. -When set to zero it will attempt to discover and use the number of -logical cores. -If the thread count is set negative it will look up the number of cores -then divide by the absolute value. -ie. -threads=-2 on an 8 core machine will result in 8 / 2 = 4 threads. -There will always be at least 1 thread. -If set to -1 in combination with \f[C]process-thread-count\f[R] then it -will try to pick reasonable values based on CPU thread count. -NOTE: higher number of threads increases parallelism but usually -decreases throughput. -(default: 0) -.IP \[bu] 2 -\f[B]read-thread-count=INT\f[R]: Alias for \f[C]threads\f[R]. -.IP \[bu] 2 -\f[B]process-thread-count=INT\f[R]: Enables separate thread pool to -asynchronously process FUSE requests. -In this mode \f[C]read-thread-count\f[R] refers to the number of threads -reading FUSE messages which are dispatched to process threads. --1 means disabled otherwise acts like \f[C]read-thread-count\f[R]. -(default: -1) -.IP \[bu] 2 -\f[B]process-thread-queue-depth=UINT\f[R]: Sets the number of requests -any single process thread can have queued up at one time. -Meaning the total memory usage of the queues is queue depth multiplied -by the number of process threads plus read thread count. -0 sets the depth to the same as the process thread count. -(default: 0) -.IP \[bu] 2 -\f[B]pin-threads=STR\f[R]: Selects a strategy to pin threads to CPUs -(default: unset) -.IP \[bu] 2 -\f[B]flush-on-close=never|always|opened-for-write\f[R]: Flush data cache -on file close. -Mostly for when writeback is enabled or merging network filesystems. -(default: opened-for-write) -.IP \[bu] 2 -\f[B]scheduling-priority=INT\f[R]: Set mergerfs\[cq] scheduling -priority. -Valid values range from -20 to 19. -See \f[C]setpriority\f[R] man page for more details. -(default: -10) -.IP \[bu] 2 -\f[B]fsname=STR\f[R]: Sets the name of the filesystem as seen in -\f[B]mount\f[R], \f[B]df\f[R], etc. -Defaults to a list of the source paths concatenated together with the -longest common prefix removed. -.IP \[bu] 2 -\f[B]func.FUNC=POLICY\f[R]: Sets the specific FUSE function\[cq]s -policy. -See below for the list of value types. -Example: \f[B]func.getattr=newest\f[R] -.IP \[bu] 2 -\f[B]func.readdir=seq|cosr|cor|cosr:INT|cor:INT\f[R]: Sets -\f[C]readdir\f[R] policy. -INT value sets the number of threads to use for concurrency. -(default: seq) -.IP \[bu] 2 -\f[B]category.action=POLICY\f[R]: Sets policy of all FUSE functions in -the action category. -(default: epall) -.IP \[bu] 2 -\f[B]category.create=POLICY\f[R]: Sets policy of all FUSE functions in -the create category. -(default: epmfs) -.IP \[bu] 2 -\f[B]category.search=POLICY\f[R]: Sets policy of all FUSE functions in -the search category. -(default: ff) -.IP \[bu] 2 -\f[B]cache.open=UINT\f[R]: `open' policy cache timeout in seconds. -(default: 0) -.IP \[bu] 2 -\f[B]cache.statfs=UINT\f[R]: `statfs' cache timeout in seconds. -(default: -.RS 2 -.RE -.IP \[bu] 2 -\f[B]cache.attr=UINT\f[R]: File attribute cache timeout in seconds. -(default: 1) -.IP \[bu] 2 -\f[B]cache.entry=UINT\f[R]: File name lookup cache timeout in seconds. -(default: 1) -.IP \[bu] 2 -\f[B]cache.negative_entry=UINT\f[R]: Negative file name lookup cache -timeout in seconds. -(default: 0) -.IP \[bu] 2 -\f[B]cache.files=libfuse|off|partial|full|auto-full|per-process\f[R]: -File page caching mode (default: libfuse) -.IP \[bu] 2 -\f[B]cache.files.process-names=LIST\f[R]: A pipe | delimited list of -process comm (https://man7.org/linux/man-pages/man5/proc.5.html) names -to enable page caching for when \f[C]cache.files=per-process\f[R]. -(default: \[lq]rtorrent|qbittorrent-nox\[rq]) -.IP \[bu] 2 -\f[B]cache.writeback=BOOL\f[R]: Enable kernel writeback caching -(default: false) -.IP \[bu] 2 -\f[B]cache.symlinks=BOOL\f[R]: Cache symlinks (if supported by kernel) -(default: false) -.IP \[bu] 2 -\f[B]cache.readdir=BOOL\f[R]: Cache readdir (if supported by kernel) -(default: false) -.IP \[bu] 2 -\f[B]parallel-direct-writes=BOOL\f[R]: Allow the kernel to dispatch -multiple, parallel (non-extending) write requests for files opened with -\f[C]cache.files=per-process\f[R] (if the process is not in -\f[C]process-names\f[R]) or \f[C]cache.files=off\f[R]. -(This requires kernel support, and was added in v6.2) -.IP \[bu] 2 -\f[B]direct_io\f[R]: deprecated - Bypass page cache. -Use \f[C]cache.files=off\f[R] instead. -(default: false) -.IP \[bu] 2 -\f[B]kernel_cache\f[R]: deprecated - Do not invalidate data cache on -file open. -Use \f[C]cache.files=full\f[R] instead. -(default: false) -.IP \[bu] 2 -\f[B]auto_cache\f[R]: deprecated - Invalidate data cache if file mtime -or size change. -Use \f[C]cache.files=auto-full\f[R] instead. -(default: false) -.IP \[bu] 2 -\f[B]async_read\f[R]: deprecated - Perform reads asynchronously. -Use \f[C]async_read=true\f[R] instead. -.IP \[bu] 2 -\f[B]sync_read\f[R]: deprecated - Perform reads synchronously. -Use \f[C]async_read=false\f[R] instead. -.IP \[bu] 2 -\f[B]splice_read\f[R]: deprecated - Does nothing. -.IP \[bu] 2 -\f[B]splice_write\f[R]: deprecated - Does nothing. -.IP \[bu] 2 -\f[B]splice_move\f[R]: deprecated - Does nothing. -.IP \[bu] 2 -\f[B]allow_other\f[R]: deprecated - mergerfs v2.35.0 and newer sets this -FUSE option automatically if running as root. -.IP \[bu] 2 -\f[B]use_ino\f[R]: deprecated - mergerfs should always control inode -calculation so this is enabled all the time. -.PP -\f[B]NOTE:\f[R] Options are evaluated in the order listed so if the -options are \f[B]func.rmdir=rand,category.action=ff\f[R] the -\f[B]action\f[R] category setting will override the \f[B]rmdir\f[R] -setting. -.PP -\f[B]NOTE:\f[R] Always look at the documentation for the version of -mergerfs you\[cq]re using. -Not all features are available in older releases. -Use \f[C]man mergerfs\f[R] or find the docs as linked in the release. -.SS Value Types -.IP \[bu] 2 -BOOL = `true' | `false' -.IP \[bu] 2 -INT = [MIN_INT,MAX_INT] -.IP \[bu] 2 -UINT = [0,MAX_INT] -.IP \[bu] 2 -SIZE = `NNM'; NN = INT, M = `K' | `M' | `G' | `T' -.IP \[bu] 2 -STR = string (may refer to an enumerated value, see details of argument) -.IP \[bu] 2 -FUNC = filesystem function -.IP \[bu] 2 -CATEGORY = function category -.IP \[bu] 2 -POLICY = mergerfs function policy -.SS branches -.PP -The `branches' argument is a colon (`:') delimited list of paths to be -pooled together. -It does not matter if the paths are on the same or different filesystems -nor does it matter the filesystem type (within reason). -Used and available space will not be duplicated for paths on the same -filesystem and any features which aren\[cq]t supported by the underlying -filesystem (such as file attributes or extended attributes) will return -the appropriate errors. -.PP -Branches currently have two options which can be set. -A type which impacts whether or not the branch is included in a policy -calculation and a individual minfreespace value. -The values are set by prepending an \f[C]=\f[R] at the end of a branch -designation and using commas as delimiters. -Example: \f[C]/mnt/drive=RW,1234\f[R] -.SS branch mode -.IP \[bu] 2 -RW: (read/write) - Default behavior. -Will be eligible in all policy categories. -.IP \[bu] 2 -RO: (read-only) - Will be excluded from \f[C]create\f[R] and -\f[C]action\f[R] policies. -Same as a read-only mounted filesystem would be (though faster to -process). -.IP \[bu] 2 -NC: (no-create) - Will be excluded from \f[C]create\f[R] policies. -You can\[cq]t create on that branch but you can change or delete. -.SS minfreespace -.PP -Same purpose and syntax as the global option but specific to the branch. -If not set the global value is used. -.SS globbing -.PP -To make it easier to include multiple branches mergerfs supports -globbing (http://linux.die.net/man/7/glob). -\f[B]The globbing tokens MUST be escaped when using via the shell else -the shell itself will apply the glob itself.\f[R] -.IP -.nf -\f[C] -# mergerfs /mnt/hdd\[rs]*:/mnt/ssd /media -\f[R] -.fi -.PP -The above line will use all mount points in /mnt prefixed with -\f[B]hdd\f[R] and \f[B]ssd\f[R]. -.PP -To have the pool mounted at boot or otherwise accessible from related -tools use \f[B]/etc/fstab\f[R]. -.IP -.nf -\f[C] -# -/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0 -\f[R] -.fi -.PP -\f[B]NOTE:\f[R] the globbing is done at mount or when updated using the -runtime API. -If a new directory is added matching the glob after the fact it will not -be automatically included. -.PP -\f[B]NOTE:\f[R] for mounting via \f[B]fstab\f[R] to work you must have -\f[B]mount.fuse\f[R] installed. -For Ubuntu/Debian it is included in the \f[B]fuse\f[R] package. -.SS inodecalc -.PP -Inodes (st_ino) are unique identifiers within a filesystem. -Each mounted filesystem has device ID (st_dev) as well and together they -can uniquely identify a file on the whole of the system. -Entries on the same device with the same inode are in fact references to -the same underlying file. -It is a many to one relationship between names and an inode. -Directories, however, do not have multiple links on most systems due to -the complexity they add. -.PP -FUSE allows the server (mergerfs) to set inode values but not device -IDs. -Creating an inode value is somewhat complex in mergerfs\[cq] case as -files aren\[cq]t really in its control. -If a policy changes what directory or file is to be selected or -something changes out of band it becomes unclear what value should be -used. -Most software does not to care what the values are but those that do -often break if a value changes unexpectedly. -The tool \f[C]find\f[R] will abort a directory walk if it sees a -directory inode change. -NFS can return stale handle errors if the inode changes out of band. -File dedup tools will usually leverage device ids and inodes as a -shortcut in searching for duplicate files and would resort to full file -comparisons should it find different inode values. -.PP -mergerfs offers multiple ways to calculate the inode in hopes of -covering different usecases. -.IP \[bu] 2 -passthrough: Passes through the underlying inode value. -Mostly intended for testing as using this does not address any of the -problems mentioned above and could confuse file deduplication software -as inodes from different filesystems can be the same. -.IP \[bu] 2 -path-hash: Hashes the relative path of the entry in question. -The underlying file\[cq]s values are completely ignored. -This means the inode value will always be the same for that file path. -This is useful when using NFS and you make changes out of band such as -copy data between branches. -This also means that entries that do point to the same file will not be -recognizable via inodes. -That \f[B]does not\f[R] mean hard links don\[cq]t work. -They will. -.IP \[bu] 2 -path-hash32: 32bit version of path-hash. -.IP \[bu] 2 -devino-hash: Hashes the device id and inode of the underlying entry. -This won\[cq]t prevent issues with NFS should the policy pick a -different file or files move out of band but will present the same inode -for underlying files that do too. -.IP \[bu] 2 -devino-hash32: 32bit version of devino-hash. -.IP \[bu] 2 -hybrid-hash: Performs \f[C]path-hash\f[R] on directories and -\f[C]devino-hash\f[R] on other file types. -Since directories can\[cq]t have hard links the static value won\[cq]t -make a difference and the files will get values useful for finding -duplicates. -Probably the best to use if not using NFS. -As such it is the default. -.IP \[bu] 2 -hybrid-hash32: 32bit version of hybrid-hash. -.PP -32bit versions are provided as there is some software which does not -handle 64bit inodes well. -.PP -While there is a risk of hash collision in tests of a couple million -entries there were zero collisions. -Unlike a typical filesystem FUSE filesystems can reuse inodes and not -refer to the same entry. -The internal identifier used to reference a file in FUSE is different -from the inode value presented. -The former is the \f[C]nodeid\f[R] and is actually a tuple of 2 64bit -values: \f[C]nodeid\f[R] and \f[C]generation\f[R]. -This tuple is not client facing. -The inode that is presented to the client is passed through the kernel -uninterpreted. -.PP -From FUSE docs for \f[C]use_ino\f[R]: -.IP -.nf -\f[C] -Honor the st_ino field in the functions getattr() and -fill_dir(). This value is used to fill in the st_ino field -in the stat(2), lstat(2), fstat(2) functions and the d_ino -field in the readdir(2) function. The filesystem does not -have to guarantee uniqueness, however some applications -rely on this value being unique for the whole filesystem. -Note that this does *not* affect the inode that libfuse -and the kernel use internally (also called the \[dq]nodeid\[dq]). -\f[R] -.fi -.PP -As of version 2.35.0 the \f[C]use_ino\f[R] option has been removed. -mergerfs should always be managing inode values. -.SS pin-threads -.PP -Simple strategies for pinning read and/or process threads. -If process threads are not enabled than the strategy simply works on the -read threads. -Invalid values are ignored. -.IP \[bu] 2 -R1L: All read threads pinned to a single logical CPU. -.IP \[bu] 2 -R1P: All read threads pinned to a single physical CPU. -.IP \[bu] 2 -RP1L: All read and process threads pinned to a single logical CPU. -.IP \[bu] 2 -RP1P: All read and process threads pinned to a single physical CPU. -.IP \[bu] 2 -R1LP1L: All read threads pinned to a single logical CPU, all process -threads pinned to a (if possible) different logical CPU. -.IP \[bu] 2 -R1PP1P: All read threads pinned to a single physical CPU, all process -threads pinned to a (if possible) different logical CPU. -.IP \[bu] 2 -RPSL: All read and process threads are spread across all logical CPUs. -.IP \[bu] 2 -RPSP: All read and process threads are spread across all physical CPUs. -.IP \[bu] 2 -R1PPSP: All read threads are pinned to a single physical CPU while -process threads are spread across all other phsycial CPUs. -.SS fuse_msg_size -.PP -FUSE applications communicate with the kernel over a special character -device: \f[C]/dev/fuse\f[R]. -A large portion of the overhead associated with FUSE is the cost of -going back and forth from user space and kernel space over that device. -Generally speaking the fewer trips needed the better the performance -will be. -Reducing the number of trips can be done a number of ways. -Kernel level caching and increasing message sizes being two significant -ones. -When it comes to reads and writes if the message size is doubled the -number of trips are approximately halved. -.PP -In Linux 4.20 a new feature was added allowing the negotiation of the -max message size. -Since the size is in multiples of -pages (https://en.wikipedia.org/wiki/Page_(computer_memory)) the feature -is called \f[C]max_pages\f[R]. -There is a maximum \f[C]max_pages\f[R] value of 256 (1MiB) and minimum -of 1 (4KiB). -The default used by Linux >=4.20, and hardcoded value used before 4.20, -is 32 (128KiB). -In mergerfs its referred to as \f[C]fuse_msg_size\f[R] to make it clear -what it impacts and provide some abstraction. -.PP -Since there should be no downsides to increasing \f[C]fuse_msg_size\f[R] -/ \f[C]max_pages\f[R], outside a minor bump in RAM usage due to larger -message buffers, mergerfs defaults the value to 256. -On kernels before 4.20 the value has no effect. -The reason the value is configurable is to enable experimentation and -benchmarking. -See the BENCHMARKING section for examples. -.SS follow-symlinks -.PP -This feature, when enabled, will cause symlinks to be interpreted by -mergerfs as their target (depending on the mode). -.PP -When there is a getattr/stat request for a file mergerfs will check if -the file is a symlink and depending on the \f[C]follow-symlinks\f[R] -setting will replace the information about the symlink with that of that -which it points to. -.PP -When unlink\[cq]ing or rmdir\[cq]ing the followed symlink it will remove -the symlink itself and not that which it points to. -.IP \[bu] 2 -never: Behave as normal. -Symlinks are treated as such. -.IP \[bu] 2 -directory: Resolve symlinks only which point to directories. -.IP \[bu] 2 -regular: Resolve symlinks only which point to regular files. -.IP \[bu] 2 -all: Resolve all symlinks to that which they point to. -.PP -Symlinks which do not point to anything are left as is. -.PP -WARNING: This feature works but there might be edge cases yet found. -If you find any odd behaviors please file a ticket on -github (https://github.com/trapexit/mergerfs/issues). -.SS link-exdev -.PP -If using path preservation and a \f[C]link\f[R] fails with EXDEV make a -call to \f[C]symlink\f[R] where the \f[C]target\f[R] is the -\f[C]oldlink\f[R] and the \f[C]linkpath\f[R] is the \f[C]newpath\f[R]. -The \f[C]target\f[R] value is determined by the value of -\f[C]link-exdev\f[R]. -.IP \[bu] 2 -passthrough: Return EXDEV as normal. -.IP \[bu] 2 -rel-symlink: A relative path from the \f[C]newpath\f[R]. -.IP \[bu] 2 -abs-base-symlink: A absolute value using the underlying branch. -.IP \[bu] 2 -abs-pool-symlink: A absolute value using the mergerfs mount point. -.PP -NOTE: It is possible that some applications check the file they link. -In those cases it is possible it will error or complain. -.SS rename-exdev -.PP -If using path preservation and a \f[C]rename\f[R] fails with EXDEV: -.IP "1." 3 -Move file from \f[B]/branch/a/b/c\f[R] to -\f[B]/branch/.mergerfs_rename_exdev/a/b/c\f[R]. -.IP "2." 3 -symlink the rename\[cq]s \f[C]newpath\f[R] to the moved file. -.PP -The \f[C]target\f[R] value is determined by the value of -\f[C]rename-exdev\f[R]. -.IP \[bu] 2 -passthrough: Return EXDEV as normal. -.IP \[bu] 2 -rel-symlink: A relative path from the \f[C]newpath\f[R]. -.IP \[bu] 2 -abs-symlink: A absolute value using the mergerfs mount point. -.PP -NOTE: It is possible that some applications check the file they rename. -In those cases it is possible it will error or complain. -.PP -NOTE: The reason \f[C]abs-symlink\f[R] is not split into two like -\f[C]link-exdev\f[R] is due to the complexities in managing absolute -base symlinks when multiple \f[C]oldpaths\f[R] exist. -.SS symlinkify -.PP -Due to the levels of indirection introduced by mergerfs and the -underlying technology FUSE there can be varying levels of performance -degradation. -This feature will turn non-directories which are not writable into -symlinks to the original file found by the \f[C]readlink\f[R] policy -after the mtime and ctime are older than the timeout. -.PP -\f[B]WARNING:\f[R] The current implementation has a known issue in which -if the file is open and being used when the file is converted to a -symlink then the application which has that file open will receive an -error when using it. -This is unlikely to occur in practice but is something to keep in mind. -.PP -\f[B]WARNING:\f[R] Some backup solutions, such as CrashPlan, do not -backup the target of a symlink. -If using this feature it will be necessary to point any backup software -to the original filesystems or configure the software to follow symlinks -if such an option is available. -Alternatively create two mounts. -One for backup and one for general consumption. -.SS nullrw -.PP -Due to how FUSE works there is an overhead to all requests made to a -FUSE filesystem that wouldn\[cq]t exist for an in kernel one. -Meaning that even a simple passthrough will have some slowdown. -However, generally the overhead is minimal in comparison to the cost of -the underlying I/O. -By disabling the underlying I/O we can test the theoretical performance -boundaries. -.PP -By enabling \f[C]nullrw\f[R] mergerfs will work as it always does -\f[B]except\f[R] that all reads and writes will be no-ops. -A write will succeed (the size of the write will be returned as if it -were successful) but mergerfs does nothing with the data it was given. -Similarly a read will return the size requested but won\[cq]t touch the -buffer. -.PP -See the BENCHMARKING section for suggestions on how to test. -.SS xattr -.PP -Runtime extended attribute support can be managed via the -\f[C]xattr\f[R] option. -By default it will passthrough any xattr calls. -Given xattr support is rarely used and can have significant performance -implications mergerfs allows it to be disabled at runtime. -The performance problems mostly comes when file caching is enabled. -The kernel will send a \f[C]getxattr\f[R] for -\f[C]security.capability\f[R] \f[I]before every single write\f[R]. -It doesn\[cq]t cache the responses to any \f[C]getxattr\f[R]. -This might be addressed in the future but for now mergerfs can really -only offer the following workarounds. -.PP -\f[C]noattr\f[R] will cause mergerfs to short circuit all xattr calls -and return ENOATTR where appropriate. -mergerfs still gets all the requests but they will not be forwarded on -to the underlying filesystems. -The runtime control will still function in this mode. -.PP -\f[C]nosys\f[R] will cause mergerfs to return ENOSYS for any xattr call. -The difference with \f[C]noattr\f[R] is that the kernel will cache this -fact and itself short circuit future calls. -This is more efficient than \f[C]noattr\f[R] but will cause -mergerfs\[cq] runtime control via the hidden file to stop working. -.SS nfsopenhack -.PP -NFS is not fully POSIX compliant and historically certain behaviors, -such as opening files with O_EXCL, are not or not well supported. -When mergerfs (or any FUSE filesystem) is exported over NFS some of -these issues come up due to how NFS and FUSE interact. -.PP -This hack addresses the issue where the creation of a file with a -read-only mode but with a read/write or write only flag. -Normally this is perfectly valid but NFS chops the one open call into -multiple calls. -Exactly how it is translated depends on the configuration and versions -of the NFS server and clients but it results in a permission error -because a normal user is not allowed to open a read-only file as -writable. -.PP -Even though it\[cq]s a more niche situation this hack breaks normal -security and behavior and as such is \f[C]off\f[R] by default. -If set to \f[C]git\f[R] it will only perform the hack when the path in -question includes \f[C]/.git/\f[R]. -\f[C]all\f[R] will result it applying anytime a read-only file which is -empty is opened for writing. -.SS export-support -.PP -In theory this flag should not be exposed to the end user. -It is a low-level FUSE flag which indicates whether or not the kernel -can send certain kinds of messages to it for the purposes of using with -NFS. -mergerfs does support these messages but due bugs and quirks found in -the kernel and mergerfs this option is provided just in case it is -needed for debugging. -.PP -Given that this flag is set when the FUSE connection is first initiated -it is not possible to change during run time. -.SH FUNCTIONS, CATEGORIES and POLICIES -.PP -The POSIX filesystem API is made up of a number of functions. -\f[B]creat\f[R], \f[B]stat\f[R], \f[B]chown\f[R], etc. -For ease of configuration in mergerfs most of the core functions are -grouped into 3 categories: \f[B]action\f[R], \f[B]create\f[R], and -\f[B]search\f[R]. -These functions and categories can be assigned a policy which dictates -which branch is chosen when performing that function. -.PP -Some functions, listed in the category \f[C]N/A\f[R] below, can not be -assigned the normal policies. -These functions work with file handles, rather than file paths, which -were created by \f[C]open\f[R] or \f[C]create\f[R]. -That said many times the current FUSE kernel driver will not always -provide the file handle when a client calls \f[C]fgetattr\f[R], -\f[C]fchown\f[R], \f[C]fchmod\f[R], \f[C]futimens\f[R], -\f[C]ftruncate\f[R], etc. -This means it will call the regular, path based, versions. -\f[C]statfs\f[R]\[cq]s behavior can be modified via other options. -.PP -When using policies which are based on a branch\[cq]s available space -the base path provided is used. -Not the full path to the file in question. -Meaning that mounts in the branch won\[cq]t be considered in the space -calculations. -The reason is that it doesn\[cq]t really work for non-path preserving -policies and can lead to non-obvious behaviors. -.PP -NOTE: While any policy can be assigned to a function or category, some -may not be very useful in practice. -For instance: \f[B]rand\f[R] (random) may be useful for file creation -(create) but could lead to very odd behavior if used for \f[C]chmod\f[R] -if there were more than one copy of the file. -.SS Functions and their Category classifications -.PP -.TS -tab(@); -lw(7.4n) lw(62.6n). -T{ -Category -T}@T{ -FUSE Functions -T} -_ -T{ -action -T}@T{ -chmod, chown, link, removexattr, rename, rmdir, setxattr, truncate, -unlink, utimens -T} -T{ -create -T}@T{ -create, mkdir, mknod, symlink -T} -T{ -search -T}@T{ -access, getattr, getxattr, ioctl (directories), listxattr, open, -readlink -T} -T{ -N/A -T}@T{ -fchmod, fchown, futimens, ftruncate, fallocate, fgetattr, fsync, ioctl -(files), read, readdir, release, statfs, write, copy_file_range -T} -.TE -.PP -In cases where something may be searched for (such as a path to clone) -\f[B]getattr\f[R] will usually be used. -.SS Policies -.PP -A policy is the algorithm used to choose a branch or branches for a -function to work on or generally how the function behaves. -.PP -Any function in the \f[C]create\f[R] category will clone the relative -path if needed. -Some other functions (\f[C]rename\f[R],\f[C]link\f[R],\f[C]ioctl\f[R]) -have special requirements or behaviors which you can read more about -below. -.SS Filtering -.PP -Most policies basically search branches and create a list of files / -paths for functions to work on. -The policy is responsible for filtering and sorting the branches. -Filters include \f[B]minfreespace\f[R], whether or not a branch is -mounted read-only, and the branch tagging (RO,NC,RW). -These filters are applied across most policies. -.IP \[bu] 2 -No \f[B]search\f[R] function policies filter. -.IP \[bu] 2 -All \f[B]action\f[R] function policies filter out branches which are -mounted \f[B]read-only\f[R] or tagged as \f[B]RO (read-only)\f[R]. -.IP \[bu] 2 -All \f[B]create\f[R] function policies filter out branches which are -mounted \f[B]read-only\f[R], tagged \f[B]RO (read-only)\f[R] or \f[B]NC -(no create)\f[R], or has available space less than -\f[C]minfreespace\f[R]. -.PP -Policies may have their own additional filtering such as those that -require existing paths to be present. -.PP -If all branches are filtered an error will be returned. -Typically \f[B]EROFS\f[R] (read-only filesystem) or \f[B]ENOSPC\f[R] (no -space left on device) depending on the most recent reason for filtering -a branch. -\f[B]ENOENT\f[R] will be returned if no eligible branch is found. -.PP -If \f[B]create\f[R], \f[B]mkdir\f[R], \f[B]mknod\f[R], or -\f[B]symlink\f[R] fail with \f[C]EROFS\f[R] or other fundimental errors -then mergerfs will mark any branch found to be read-only as such (IE -will set the mode \f[C]RO\f[R]) and will rerun the policy and try again. -This is mostly for \f[C]ext4\f[R] filesystems that can suddenly become -read-only when it encounters an error. -.SS Path Preservation -.PP -Policies, as described below, are of two basic classifications. -\f[C]path preserving\f[R] and \f[C]non-path preserving\f[R]. -.PP -All policies which start with \f[C]ep\f[R] (\f[B]epff\f[R], -\f[B]eplfs\f[R], \f[B]eplus\f[R], \f[B]epmfs\f[R], \f[B]eprand\f[R]) are -\f[C]path preserving\f[R]. -\f[C]ep\f[R] stands for \f[C]existing path\f[R]. -.PP -A path preserving policy will only consider branches where the relative -path being accessed already exists. -.PP -When using non-path preserving policies paths will be cloned to target -branches as necessary. -.PP -With the \f[C]msp\f[R] or \f[C]most shared path\f[R] policies they are -defined as \f[C]path preserving\f[R] for the purpose of controlling -\f[C]link\f[R] and \f[C]rename\f[R]\[cq]s behaviors since -\f[C]ignorepponrename\f[R] is available to disable that behavior. -.SS Policy descriptions -.PP -A policy\[cq]s behavior differs, as mentioned above, based on the -function it is used with. -Sometimes it really might not make sense to even offer certain policies -because they are literally the same as others but it makes things a bit -more uniform. -.PP -.TS -tab(@); -lw(16.2n) lw(53.8n). -T{ -Policy -T}@T{ -Description -T} -_ -T{ -all -T}@T{ -Search: For \f[B]mkdir\f[R], \f[B]mknod\f[R], and \f[B]symlink\f[R] it -will apply to all branches. -\f[B]create\f[R] works like \f[B]ff\f[R]. -T} -T{ -epall (existing path, all) -T}@T{ -For \f[B]mkdir\f[R], \f[B]mknod\f[R], and \f[B]symlink\f[R] it will -apply to all found. -\f[B]create\f[R] works like \f[B]epff\f[R] (but more expensive because -it doesn\[cq]t stop after finding a valid branch). -T} -T{ -epff (existing path, first found) -T}@T{ -Given the order of the branches, as defined at mount time or configured -at runtime, act on the first one found where the relative path exists. -T} -T{ -eplfs (existing path, least free space) -T}@T{ -Of all the branches on which the relative path exists choose the branch -with the least free space. -T} -T{ -eplus (existing path, least used space) -T}@T{ -Of all the branches on which the relative path exists choose the branch -with the least used space. -T} -T{ -epmfs (existing path, most free space) -T}@T{ -Of all the branches on which the relative path exists choose the branch -with the most free space. -T} -T{ -eppfrd (existing path, percentage free random distribution) -T}@T{ -Like \f[B]pfrd\f[R] but limited to existing paths. -T} -T{ -eprand (existing path, random) -T}@T{ -Calls \f[B]epall\f[R] and then randomizes. -Returns 1. -T} -T{ -ff (first found) -T}@T{ -Given the order of the branches, as defined at mount time or configured -at runtime, act on the first one found. -T} -T{ -lfs (least free space) -T}@T{ -Pick the branch with the least available free space. -T} -T{ -lus (least used space) -T}@T{ -Pick the branch with the least used space. -T} -T{ -mfs (most free space) -T}@T{ -Pick the branch with the most available free space. -T} -T{ -msplfs (most shared path, least free space) -T}@T{ -Like \f[B]eplfs\f[R] but if it fails to find a branch it will try again -with the parent directory. -Continues this pattern till finding one. -T} -T{ -msplus (most shared path, least used space) -T}@T{ -Like \f[B]eplus\f[R] but if it fails to find a branch it will try again -with the parent directory. -Continues this pattern till finding one. -T} -T{ -mspmfs (most shared path, most free space) -T}@T{ -Like \f[B]epmfs\f[R] but if it fails to find a branch it will try again -with the parent directory. -Continues this pattern till finding one. -T} -T{ -msppfrd (most shared path, percentage free random distribution) -T}@T{ -Like \f[B]eppfrd\f[R] but if it fails to find a branch it will try again -with the parent directory. -Continues this pattern till finding one. -T} -T{ -newest -T}@T{ -Pick the file / directory with the largest mtime. -T} -T{ -pfrd (percentage free random distribution) -T}@T{ -Chooses a branch at random with the likelihood of selection based on a -branch\[cq]s available space relative to the total. -T} -T{ -rand (random) -T}@T{ -Calls \f[B]all\f[R] and then randomizes. -Returns 1 branch. -T} -.TE -.PP -\f[B]NOTE:\f[R] If you are using an underlying filesystem that reserves -blocks such as ext2, ext3, or ext4 be aware that mergerfs respects the -reservation by using \f[C]f_bavail\f[R] (number of free blocks for -unprivileged users) rather than \f[C]f_bfree\f[R] (number of free -blocks) in policy calculations. -\f[B]df\f[R] does NOT use \f[C]f_bavail\f[R], it uses \f[C]f_bfree\f[R], -so direct comparisons between \f[B]df\f[R] output and mergerfs\[cq] -policies is not appropriate. -.SS Defaults -.PP -.TS -tab(@); -l l. -T{ -Category -T}@T{ -Policy -T} -_ -T{ -action -T}@T{ -epall -T} -T{ -create -T}@T{ -epmfs -T} -T{ -search -T}@T{ -ff -T} -.TE -.SS func.readdir -.PP -examples: \f[C]func.readdir=seq\f[R], \f[C]func.readdir=cor:4\f[R] -.PP -\f[C]readdir\f[R] has policies to control how it manages reading -directory content. -.PP -.TS -tab(@); -lw(26.7n) lw(43.3n). -T{ -Policy -T}@T{ -Description -T} -_ -T{ -seq -T}@T{ -\[lq]sequential\[rq] : Iterate over branches in the order defined. -This is the default and traditional behavior found prior to the readdir -policy introduction. -T} -T{ -cosr -T}@T{ -\[lq]concurrent open, sequential read\[rq] : Concurrently open branch -directories using a thread pool and process them in order of definition. -This keeps memory and CPU usage low while also reducing the time spent -waiting on branches to respond. -Number of threads defaults to the number of logical cores. -Can be overwritten via the syntax \f[C]func.readdir=cosr:N\f[R] where -\f[C]N\f[R] is the number of threads. -T} -T{ -cor -T}@T{ -\[lq]concurrent open and read\[rq] : Concurrently open branch -directories and immediately start reading their contents using a thread -pool. -This will result in slightly higher memory and CPU usage but reduced -latency. -Particularly when using higher latency / slower speed network filesystem -branches. -Unlike \f[C]seq\f[R] and \f[C]cosr\f[R] the order of files could change -due the async nature of the thread pool. -Number of threads defaults to the number of logical cores. -Can be overwritten via the syntax \f[C]func.readdir=cor:N\f[R] where -\f[C]N\f[R] is the number of threads. -T} -.TE -.PP -Keep in mind that \f[C]readdir\f[R] mostly just provides a list of file -names in a directory and possibly some basic metadata about said files. -To know details about the files, as one would see from commands like -\f[C]find\f[R] or \f[C]ls\f[R], it is required to call \f[C]stat\f[R] on -the file which is controlled by \f[C]fuse.getattr\f[R]. -.SS ioctl -.PP -When \f[C]ioctl\f[R] is used with an open file then it will use the file -handle which was created at the original \f[C]open\f[R] call. -However, when using \f[C]ioctl\f[R] with a directory mergerfs will use -the \f[C]open\f[R] policy to find the directory to act on. -.SS rename & link -.PP -\f[B]NOTE:\f[R] If you\[cq]re receiving errors from software when files -are moved / renamed / linked then you should consider changing the -create policy to one which is \f[B]not\f[R] path preserving, enabling -\f[C]ignorepponrename\f[R], or contacting the author of the offending -software and requesting that \f[C]EXDEV\f[R] (cross device / improper -link) be properly handled. -.PP -\f[C]rename\f[R] and \f[C]link\f[R] are tricky functions in a union -filesystem. -\f[C]rename\f[R] only works within a single filesystem or device. -If a rename can\[cq]t be done atomically due to the source and -destination paths existing on different mount points it will return -\f[B]-1\f[R] with \f[B]errno = EXDEV\f[R] (cross device / improper -link). -So if a \f[C]rename\f[R]\[cq]s source and target are on different -filesystems within the pool it creates an issue. -.PP -Originally mergerfs would return EXDEV whenever a rename was requested -which was cross directory in any way. -This made the code simple and was technically compliant with POSIX -requirements. -However, many applications fail to handle EXDEV at all and treat it as a -normal error or otherwise handle it poorly. -Such apps include: gvfsd-fuse v1.20.3 and prior, Finder / CIFS/SMB -client in Apple OSX 10.9+, NZBGet, Samba\[cq]s recycling bin feature. -.PP -As a result a compromise was made in order to get most software to work -while still obeying mergerfs\[cq] policies. -Below is the basic logic. -.IP \[bu] 2 -If using a \f[B]create\f[R] policy which tries to preserve directory -paths (epff,eplfs,eplus,epmfs) -.RS 2 -.IP \[bu] 2 -Using the \f[B]rename\f[R] policy get the list of files to rename -.IP \[bu] 2 -For each file attempt rename: -.RS 2 -.IP \[bu] 2 -If failure with ENOENT (no such file or directory) run \f[B]create\f[R] -policy -.IP \[bu] 2 -If create policy returns the same branch as currently evaluating then -clone the path -.IP \[bu] 2 -Re-attempt rename -.RE -.IP \[bu] 2 -If \f[B]any\f[R] of the renames succeed the higher level rename is -considered a success -.IP \[bu] 2 -If \f[B]no\f[R] renames succeed the first error encountered will be -returned -.IP \[bu] 2 -On success: -.RS 2 -.IP \[bu] 2 -Remove the target from all branches with no source file -.IP \[bu] 2 -Remove the source from all branches which failed to rename -.RE -.RE -.IP \[bu] 2 -If using a \f[B]create\f[R] policy which does \f[B]not\f[R] try to -preserve directory paths -.RS 2 -.IP \[bu] 2 -Using the \f[B]rename\f[R] policy get the list of files to rename -.IP \[bu] 2 -Using the \f[B]getattr\f[R] policy get the target path -.IP \[bu] 2 -For each file attempt rename: -.RS 2 -.IP \[bu] 2 -If the source branch != target branch: -.RS 2 -.IP \[bu] 2 -Clone target path from target branch to source branch -.RE -.IP \[bu] 2 -Rename -.RE -.IP \[bu] 2 -If \f[B]any\f[R] of the renames succeed the higher level rename is -considered a success -.IP \[bu] 2 -If \f[B]no\f[R] renames succeed the first error encountered will be -returned -.IP \[bu] 2 -On success: -.RS 2 -.IP \[bu] 2 -Remove the target from all branches with no source file -.IP \[bu] 2 -Remove the source from all branches which failed to rename -.RE -.RE -.PP -The the removals are subject to normal entitlement checks. -.PP -The above behavior will help minimize the likelihood of EXDEV being -returned but it will still be possible. -.PP -\f[B]link\f[R] uses the same strategy but without the removals. -.SS statfs / statvfs -.PP -statvfs (http://linux.die.net/man/2/statvfs) normalizes the source -filesystems based on the fragment size and sums the number of adjusted -blocks and inodes. -This means you will see the combined space of all sources. -Total, used, and free. -The sources however are dedupped based on the filesystem so multiple -sources on the same drive will not result in double counting its space. -Other filesystems mounted further down the tree of the branch will not -be included when checking the mount\[cq]s stats. -.PP -The options \f[C]statfs\f[R] and \f[C]statfs_ignore\f[R] can be used to -modify \f[C]statfs\f[R] behavior. -.SS flush-on-close -.PP -https://lkml.kernel.org/linux-fsdevel/20211024132607.1636952-1-amir73il\[at]gmail.com/T/ -.PP -By default FUSE would issue a flush before the release of a file -descriptor. -This was considered a bit aggressive and a feature added to give the -FUSE server the ability to choose when that happens. -.PP -Options: * always * never * opened-for-write -.PP -For now it defaults to \[lq]opened-for-write\[rq] which is less -aggressive than the behavior before this feature was added. -It should not be a problem because the flush is really only relevant -when a file is written to. -Given flush is irrelevant for many filesystems in the future a branch -specific flag may be added so only files opened on a specific branch -would be flushed on close. -.SH ERROR HANDLING -.PP -POSIX filesystem functions offer a single return code meaning that there -is some complication regarding the handling of multiple branches as -mergerfs does. -It tries to handle errors in a way that would generally return -meaningful values for that particular function. -.SS chmod, chown, removexattr, setxattr, truncate, utimens -.IP "1)" 3 -if no error: return 0 (success) -.IP "2)" 3 -if no successes: return first error -.IP "3)" 3 -if one of the files acted on was the same as the related search -function: return its value -.IP "4)" 3 -return 0 (success) -.PP -While doing this increases the complexity and cost of error handling, -particularly step 3, this provides probably the most reasonable return -value. -.SS unlink, rmdir -.IP "1)" 3 -if no errors: return 0 (success) -.IP "2)" 3 -return first error -.PP -Older version of mergerfs would return success if any success occurred -but for unlink and rmdir there are downstream assumptions that, while -not impossible to occur, can confuse some software. -.SS others -.PP -For search functions there is always a single thing acted on and as such -whatever return value that comes from the single function call is -returned. -.PP -For create functions \f[C]mkdir\f[R], \f[C]mknod\f[R], and -\f[C]symlink\f[R] which don\[cq]t return a file descriptor and therefore -can have \f[C]all\f[R] or \f[C]epall\f[R] policies it will return -success if any of the calls succeed and an error otherwise. -.SH INSTALL -.PP -https://github.com/trapexit/mergerfs/releases -.PP -If your distribution\[cq]s package manager includes mergerfs check if -the version is up to date. -If out of date it is recommended to use the latest release found on the -release page. -Details for common distros are below. -.SS Debian -.PP -Most Debian installs are of a stable branch and therefore do not have -the most up to date software. -While mergerfs is available via \f[C]apt\f[R] it is suggested that uses -install the most recent version available from the releases -page (https://github.com/trapexit/mergerfs/releases). -.SS prebuilt deb -.IP -.nf -\f[C] -wget https://github.com/trapexit/mergerfs/releases/download//mergerfs_.debian-_.deb -dpkg -i mergerfs_.debian-_.deb -\f[R] -.fi -.SS apt -.IP -.nf -\f[C] -sudo apt install -y mergerfs -\f[R] -.fi -.SS Ubuntu -.PP -Most Ubuntu installs are of a stable branch and therefore do not have -the most up to date software. -While mergerfs is available via \f[C]apt\f[R] it is suggested that uses -install the most recent version available from the releases -page (https://github.com/trapexit/mergerfs/releases). -.SS prebuilt deb -.IP -.nf -\f[C] -wget https://github.com/trapexit/mergerfs/releases/download//mergerfs_.ubuntu-_.deb -dpkg -i mergerfs_.ubuntu-_.deb -\f[R] .fi -.SS apt -.IP -.nf -\f[C] -sudo apt install -y mergerfs -\f[R] -.fi -.SS Raspberry Pi OS -.PP -Effectively the same as Debian or Ubuntu. -.SS Fedora -.IP -.nf -\f[C] -wget https://github.com/trapexit/mergerfs/releases/download//mergerfs-.fc..rpm -sudo rpm -i mergerfs-.fc..rpm -\f[R] -.fi -.SS CentOS / Rocky -.IP -.nf -\f[C] -wget https://github.com/trapexit/mergerfs/releases/download//mergerfs-.el..rpm -sudo rpm -i mergerfs-.el..rpm -\f[R] -.fi -.SS ArchLinux -.IP "1." 3 -Setup AUR -.IP "2." 3 -Install \f[C]mergerfs\f[R] -.SS Other -.PP -Static binaries are provided for situations where native packages are -unavailable. -.IP -.nf -\f[C] -wget https://github.com/trapexit/mergerfs/releases/download//mergerfs-static-linux_.tar.gz -sudo tar xvf mergerfs-static-linux_.tar.gz -C / -\f[R] -.fi -.SH BUILD -.PP -\f[B]NOTE:\f[R] Prebuilt packages can be found at and recommended for -most users: https://github.com/trapexit/mergerfs/releases -.PP -\f[B]NOTE:\f[R] Only tagged releases are supported. -\f[C]master\f[R] and other branches should be considered works in -progress. -.PP -First get the code from github (https://github.com/trapexit/mergerfs). -.IP -.nf -\f[C] -$ git clone https://github.com/trapexit/mergerfs.git -$ # or -$ wget https://github.com/trapexit/mergerfs/releases/download//mergerfs-.tar.gz -\f[R] -.fi -.SS Debian / Ubuntu -.IP -.nf -\f[C] -$ cd mergerfs -$ sudo tools/install-build-pkgs -$ make deb -$ sudo dpkg -i ../mergerfs__.deb -\f[R] -.fi -.SS RHEL / CentOS / Rocky / Fedora -.IP -.nf -\f[C] -$ su - -# cd mergerfs -# tools/install-build-pkgs -# make rpm -# rpm -i rpmbuild/RPMS//mergerfs-..rpm -\f[R] -.fi -.SS Generic -.PP -Have git, g++, make, python installed. -.IP -.nf -\f[C] -$ cd mergerfs -$ make -$ sudo make install -\f[R] -.fi -.SS Build options -.IP -.nf -\f[C] -$ make help -usage: make +.SH SPONSORSHIP AND DONATIONS +Development and support of a project like +.B mergerfs +require a significant amount of time and effort. The software is released under the very liberal ISC license and is therefore free to use for personal or commercial uses. -make USE_XATTR=0 - build program without xattrs functionality -make STATIC=1 - build static binary -make LTO=1 - build with link time optimization -\f[R] -.fi -.SH UPGRADE -.PP -mergerfs can be upgraded live by mounting on top of the previous -instance. -Simply install the new version of mergerfs and follow the instructions -below. -.PP -Run mergerfs again or if using \f[C]/etc/fstab\f[R] call for it to mount -again. -Existing open files and such will continue to work fine though they -won\[cq]t see runtime changes since any such change would be the new -mount. -If you plan on changing settings with the new mount you should / could -apply those before mounting the new version. -.IP -.nf -\f[C] -$ sudo mount /mnt/mergerfs -$ mount | grep mergerfs -media on /mnt/mergerfs type mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other) -media on /mnt/mergerfs type mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other) -\f[R] -.fi -.PP -A problem with this approach is that the underlying instance will -continue to run even if the software using it stop or are restarted. -To work around this you can use a \[lq]lazy umount\[rq]. -Before mounting over top the mount point with the new instance of -mergerfs issue: \f[C]umount -l \f[R]. -Or you can let mergerfs do it by setting the option -\f[C]lazy-umount-mountpoint=true\f[R]. -.SH RUNTIME INTERFACES -.SS RUNTIME CONFIG -.SS .mergerfs pseudo file -.IP -.nf -\f[C] -/.mergerfs -\f[R] -.fi -.PP -There is a pseudo file available at the mount point which allows for the -runtime modification of certain \f[B]mergerfs\f[R] options. -The file will not show up in \f[B]readdir\f[R] but can be -\f[B]stat\f[R]\[cq]ed and manipulated via -{list,get,set}xattrs (http://linux.die.net/man/2/listxattr) calls. -.PP -Any changes made at runtime are \f[B]not\f[R] persisted. -If you wish for values to persist they must be included as options -wherever you configure the mounting of mergerfs (/etc/fstab). -.SS Keys -.PP -Use \f[C]getfattr -d /mountpoint/.mergerfs\f[R] or -\f[C]xattr -l /mountpoint/.mergerfs\f[R] to see all supported keys. -Some are informational and therefore read-only. -\f[C]setxattr\f[R] will return EINVAL (invalid argument) on read-only -keys. -.SS Values -.PP -Same as the command line. -.SS user.mergerfs.branches -.PP -Used to query or modify the list of branches. -When modifying there are several shortcuts to easy manipulation of the -list. -.PP -.TS -tab(@); -l l. -T{ -Value -T}@T{ -Description -T} -_ -T{ -[list] -T}@T{ -set -T} -T{ -+<[list] -T}@T{ -prepend -T} -T{ -+>[list] -T}@T{ -append -T} -T{ --[list] -T}@T{ -remove all values provided -T} -T{ --< -T}@T{ -remove first in list -T} -T{ --> -T}@T{ -remove last in list -T} -.TE -.PP -\f[C]xattr -w user.mergerfs.branches + userspace -round trips there are kernel side caches for file entries and -attributes. -The entry cache limits the \f[C]lookup\f[R] calls to mergerfs which ask -if a file exists. -The attribute cache limits the need to make \f[C]getattr\f[R] calls to -mergerfs which provide file attributes (mode, size, type, etc.). -As with the page cache these should not be used if the underlying -filesystems are being manipulated at the same time as it could lead to -odd behavior or data corruption. -The options for setting these are \f[C]cache.entry\f[R] and -\f[C]cache.negative_entry\f[R] for the entry cache and -\f[C]cache.attr\f[R] for the attributes cache. -\f[C]cache.negative_entry\f[R] refers to the timeout for negative -responses to lookups (non-existent files). -.SS writeback caching -.PP -When \f[C]cache.files\f[R] is enabled the default is for it to perform -writethrough caching. -This behavior won\[cq]t help improve performance as each write still -goes one for one through the filesystem. -By enabling the FUSE writeback cache small writes may be aggregated by -the kernel and then sent to mergerfs as one larger request. -This can greatly improve the throughput for apps which write to files -inefficiently. -The amount the kernel can aggregate is limited by the size of a FUSE -message. -Read the \f[C]fuse_msg_size\f[R] section for more details. -.PP -There is a small side effect as a result of enabling writeback caching. -Underlying files won\[cq]t ever be opened with O_APPEND or O_WRONLY. -The former because the kernel then manages append mode and the latter -because the kernel may request file data from mergerfs to populate the -write cache. -The O_APPEND change means that if a file is changed outside of mergerfs -it could lead to corruption as the kernel won\[cq]t know the end of the -file has changed. -That said any time you use caching you should keep from using the same -file outside of mergerfs at the same time. -.PP -Note that if an application is properly sizing writes then writeback -caching will have little or no effect. -It will only help with writes of sizes below the FUSE message size (128K -on older kernels, 1M on newer). -.SS statfs caching -.PP -Of the syscalls used by mergerfs in policies the \f[C]statfs\f[R] / -\f[C]statvfs\f[R] call is perhaps the most expensive. -It\[cq]s used to find out the available space of a filesystem and -whether it is mounted read-only. -Depending on the setup and usage pattern these queries can be relatively -costly. -When \f[C]cache.statfs\f[R] is enabled all calls to \f[C]statfs\f[R] by -a policy will be cached for the number of seconds its set to. -.PP -Example: If the create policy is \f[C]mfs\f[R] and the timeout is 60 -then for that 60 seconds the same filesystem will be returned as the -target for creates because the available space won\[cq]t be updated for -that time. -.SS symlink caching -.PP -As of version 4.20 Linux supports symlink caching. -Significant performance increases can be had in workloads which use a -lot of symlinks. -Setting \f[C]cache.symlinks=true\f[R] will result in requesting symlink -caching from the kernel only if supported. -As a result its safe to enable it on systems prior to 4.20. -That said it is disabled by default for now. -You can see if caching is enabled by querying the xattr -\f[C]user.mergerfs.cache.symlinks\f[R] but given it must be requested at -startup you can not change it at runtime. -.SS readdir caching -.PP -As of version 4.20 Linux supports readdir caching. -This can have a significant impact on directory traversal. -Especially when combined with entry (\f[C]cache.entry\f[R]) and -attribute (\f[C]cache.attr\f[R]) caching. -Setting \f[C]cache.readdir=true\f[R] will result in requesting readdir -caching from the kernel on each \f[C]opendir\f[R]. -If the kernel doesn\[cq]t support readdir caching setting the option to -\f[C]true\f[R] has no effect. -This option is configurable at runtime via xattr -\f[C]user.mergerfs.cache.readdir\f[R]. -.SS tiered caching -.PP -Some storage technologies support what some call \[lq]tiered\[rq] -caching. -The placing of usually smaller, faster storage as a transparent cache to -larger, slower storage. -NVMe, SSD, Optane in front of traditional HDDs for instance. -.PP -mergerfs does not natively support any sort of tiered caching. -Most users have no use for such a feature and its inclusion would -complicate the code. -However, there are a few situations where a cache filesystem could help -with a typical mergerfs setup. -.IP "1." 3 -Fast network, slow filesystems, many readers: You\[cq]ve a 10+Gbps -network with many readers and your regular filesystems can\[cq]t keep -up. -.IP "2." 3 -Fast network, slow filesystems, small\[cq]ish bursty writes: You have a -10+Gbps network and wish to transfer amounts of data less than your -cache filesystem but wish to do so quickly. -.PP -With #1 it\[cq]s arguable if you should be using mergerfs at all. -RAID would probably be the better solution. -If you\[cq]re going to use mergerfs there are other tactics that may -help: spreading the data across filesystems (see the mergerfs.dup tool) -and setting \f[C]func.open=rand\f[R], using \f[C]symlinkify\f[R], or -using dm-cache or a similar technology to add tiered cache to the -underlying device. -.PP -With #2 one could use dm-cache as well but there is another solution -which requires only mergerfs and a cronjob. -.IP "1." 3 -Create 2 mergerfs pools. -One which includes just the slow devices and one which has both the fast -devices (SSD,NVME,etc.) and slow devices. -.IP "2." 3 -The `cache' pool should have the cache filesystems listed first. -.IP "3." 3 -The best \f[C]create\f[R] policies to use for the `cache' pool would -probably be \f[C]ff\f[R], \f[C]epff\f[R], \f[C]lfs\f[R], or -\f[C]eplfs\f[R]. -The latter two under the assumption that the cache filesystem(s) are far -smaller than the backing filesystems. -If using path preserving policies remember that you\[cq]ll need to -manually create the core directories of those paths you wish to be -cached. -Be sure the permissions are in sync. -Use \f[C]mergerfs.fsck\f[R] to check / correct them. -You could also set the slow filesystems mode to \f[C]NC\f[R] though -that\[cq]d mean if the cache filesystems fill you\[cq]d get \[lq]out of -space\[rq] errors. -.IP "4." 3 -Enable \f[C]moveonenospc\f[R] and set \f[C]minfreespace\f[R] -appropriately. -To make sure there is enough room on the \[lq]slow\[rq] pool you might -want to set \f[C]minfreespace\f[R] to at least as large as the size of -the largest cache filesystem if not larger. -This way in the worst case the whole of the cache filesystem(s) can be -moved to the other drives. -.IP "5." 3 -Set your programs to use the cache pool. -.IP "6." 3 -Save one of the below scripts or create you\[cq]re own. -.IP "7." 3 -Use \f[C]cron\f[R] (as root) to schedule the command at whatever -frequency is appropriate for your workflow. -.SS time based expiring -.PP -Move files from cache to backing pool based only on the last time the -file was accessed. -Replace \f[C]-atime\f[R] with \f[C]-amin\f[R] if you want minutes rather -than days. -May want to use the \f[C]fadvise\f[R] / \f[C]--drop-cache\f[R] version -of rsync or run rsync with the tool \[lq]nocache\[rq]. -.PP -\f[I]NOTE:\f[R] The arguments to these scripts include the cache -\f[B]filesystem\f[R] itself. -Not the pool with the cache filesystem. -You could have data loss if the source is the cache pool. -.PP -mergerfs.time-based-mover -.SS percentage full expiring -.PP -Move the oldest file from the cache to the backing pool. -Continue till below percentage threshold. -.PP -\f[I]NOTE:\f[R] The arguments to these scripts include the cache -\f[B]filesystem\f[R] itself. -Not the pool with the cache filesystem. -You could have data loss if the source is the cache pool. -.PP -mergerfs.percent-full-mover -.SH PERFORMANCE -.PP -mergerfs is at its core just a proxy and therefore its theoretical max -performance is that of the underlying devices. -However, given it is a FUSE filesystem working from userspace there is -an increase in overhead relative to kernel based solutions. -That said the performance can match the theoretical max but it depends -greatly on the system\[cq]s configuration. -Especially when adding network filesystems into the mix there are many -variables which can impact performance. -Device speeds and latency, network speeds and latency, general -concurrency, read/write sizes, etc. -Unfortunately, given the number of variables it has been difficult to -find a single set of settings which provide optimal performance. -If you\[cq]re having performance issues please look over the suggestions -below (including the benchmarking section.) -.PP -NOTE: be sure to read about these features before changing them to -understand what behaviors it may impact -.IP \[bu] 2 -disable \f[C]security_capability\f[R] and/or \f[C]xattr\f[R] -.IP \[bu] 2 -increase cache timeouts \f[C]cache.attr\f[R], \f[C]cache.entry\f[R], -\f[C]cache.negative_entry\f[R] -.IP \[bu] 2 -enable (or disable) page caching (\f[C]cache.files\f[R]) -.IP \[bu] 2 -enable \f[C]parallel-direct-writes\f[R] -.IP \[bu] 2 -enable \f[C]cache.writeback\f[R] -.IP \[bu] 2 -enable \f[C]cache.statfs\f[R] -.IP \[bu] 2 -enable \f[C]cache.symlinks\f[R] -.IP \[bu] 2 -enable \f[C]cache.readdir\f[R] -.IP \[bu] 2 -change the number of worker threads -.IP \[bu] 2 -disable \f[C]posix_acl\f[R] -.IP \[bu] 2 -disable \f[C]async_read\f[R] -.IP \[bu] 2 -test theoretical performance using \f[C]nullrw\f[R] or mounting a ram -disk -.IP \[bu] 2 -use \f[C]symlinkify\f[R] if your data is largely static and read-only -.IP \[bu] 2 -use tiered cache devices -.IP \[bu] 2 -use LVM and LVM cache to place a SSD in front of your HDDs -.IP \[bu] 2 -increase readahead: \f[C]readahead=1024\f[R] -.PP -If you come across a setting that significantly impacts performance -please contact trapexit so he may investigate further. -Please test both against your normal setup, a singular branch, and with -\f[C]nullrw=true\f[R] -.SH BENCHMARKING -.PP -Filesystems are complicated. -They do many things and many of those are interconnected. -Additionally, the OS, drivers, hardware, etc. -all can impact performance. -Therefore, when benchmarking, it is \f[B]necessary\f[R] that the test -focus as narrowly as possible. -.PP -For most throughput is the key benchmark. -To test throughput \f[C]dd\f[R] is useful but \f[B]must\f[R] be used -with the correct settings in order to ensure the filesystem or device is -actually being tested. -The OS can and will cache data. -Without forcing synchronous reads and writes and/or disabling caching -the values returned will not be representative of the device\[cq]s true -performance. -.PP -When benchmarking through mergerfs ensure you only use 1 branch to -remove any possibility of the policies complicating the situation. -Benchmark the underlying filesystem first and then mount mergerfs over -it and test again. -If you\[cq]re experience speeds below your expectation you will need to -narrow down precisely which component is leading to the slowdown. -Preferably test the following in the order listed (but not combined). -.IP "1." 3 -Enable \f[C]nullrw\f[R] mode with \f[C]nullrw=true\f[R]. -This will effectively make reads and writes no-ops. -Removing the underlying device / filesystem from the equation. -This will give us the top theoretical speeds. -.IP "2." 3 -Mount mergerfs over \f[C]tmpfs\f[R]. -\f[C]tmpfs\f[R] is a RAM disk. -Extremely high speed and very low latency. -This is a more realistic best case scenario. -Example: \f[C]mount -t tmpfs -o size=2G tmpfs /tmp/tmpfs\f[R] -.IP "3." 3 -Mount mergerfs over a local device. -NVMe, SSD, HDD, etc. -If you have more than one I\[cq]d suggest testing each of them as drives -and/or controllers (their drivers) could impact performance. -.IP "4." 3 -Finally, if you intend to use mergerfs with a network filesystem, either -as the source of data or to combine with another through mergerfs, test -each of those alone as above. -.PP -Once you find the component which has the performance issue you can do -further testing with different options to see if they impact -performance. -For reads and writes the most relevant would be: \f[C]cache.files\f[R], -\f[C]async_read\f[R]. -Less likely but relevant when using NFS or with certain filesystems -would be \f[C]security_capability\f[R], \f[C]xattr\f[R], and -\f[C]posix_acl\f[R]. -If you find a specific system, device, filesystem, controller, etc. -that performs poorly contact trapexit so he may investigate further. -.PP -Sometimes the problem is really the application accessing or writing -data through mergerfs. -Some software use small buffer sizes which can lead to more requests and -therefore greater overhead. -You can test this out yourself by replace \f[C]bs=1M\f[R] in the -examples below with \f[C]ibs\f[R] or \f[C]obs\f[R] and using a size of -\f[C]512\f[R] instead of \f[C]1M\f[R]. -In one example test using \f[C]nullrw\f[R] the write speed dropped from -4.9GB/s to 69.7MB/s when moving from \f[C]1M\f[R] to \f[C]512\f[R]. -Similar results were had when testing reads. -Small writes overhead may be improved by leveraging a write cache but in -casual tests little gain was found. -More tests will need to be done before this feature would become -available. -If you have an app that appears slow with mergerfs it could be due to -this. -Contact trapexit so he may investigate further. -.SS write benchmark -.IP -.nf -\f[C] -$ dd if=/dev/zero of=/mnt/mergerfs/1GB.file bs=1M count=1024 oflag=nocache conv=fdatasync status=progress -\f[R] -.fi -.SS read benchmark -.IP -.nf -\f[C] -$ dd if=/mnt/mergerfs/1GB.file of=/dev/null bs=1M count=1024 iflag=nocache conv=fdatasync status=progress -\f[R] -.fi -.SS other benchmarks -.PP -If you are attempting to benchmark other behaviors you must ensure you -clear kernel caches before runs. -In fact it would be a good deal to run before the read and write -benchmarks as well just in case. -.IP -.nf -\f[C] -sync -echo 3 | sudo tee /proc/sys/vm/drop_caches -\f[R] -.fi -.SH TIPS / NOTES -.IP \[bu] 2 -This document is literal and thorough. -If a suspected feature isn\[cq]t mentioned it doesn\[cq]t exist. -If certain libfuse arguments aren\[cq]t listed they probably -shouldn\[cq]t be used. -.IP \[bu] 2 -Ensure you\[cq]re using the latest version. -.IP \[bu] 2 -Run mergerfs as \f[C]root\f[R]. -mergerfs is designed and intended to be run as \f[C]root\f[R] and may -exibit incorrect behavior if run otherwise.. -.IP \[bu] 2 -If you don\[cq]t see some directories and files you expect, policies -seem to skip branches, you get strange permission errors, etc. -be sure the underlying filesystems\[cq] permissions are all the same. -Use \f[C]mergerfs.fsck\f[R] to audit the filesystem for out of sync -permissions. -.IP \[bu] 2 -If you still have permission issues be sure you are using POSIX ACL -compliant filesystems. -mergerfs doesn\[cq]t generally make exceptions for FAT, NTFS, or other -non-POSIX filesystem. -.IP \[bu] 2 -Do \f[B]not\f[R] use \f[C]cache.files=off\f[R] if you expect -applications (such as rtorrent) to use -mmap (http://linux.die.net/man/2/mmap) files. -Shared mmap is not currently supported in FUSE w/ page caching disabled. -Enabling \f[C]dropcacheonclose\f[R] is recommended when -\f[C]cache.files=partial|full|auto-full\f[R]. -.IP \[bu] 2 -Kodi (http://kodi.tv), Plex (http://plex.tv), -Subsonic (http://subsonic.org), etc. -can use directory mtime (http://linux.die.net/man/2/stat) to more -efficiently determine whether to scan for new content rather than simply -performing a full scan. -If using the default \f[B]getattr\f[R] policy of \f[B]ff\f[R] it\[cq]s -possible those programs will miss an update on account of it returning -the first directory found\[cq]s \f[B]stat\f[R] info and it\[cq]s a later -directory on another mount which had the \f[B]mtime\f[R] recently -updated. -To fix this you will want to set \f[B]func.getattr=newest\f[R]. -Remember though that this is just \f[B]stat\f[R]. -If the file is later \f[B]open\f[R]\[cq]ed or \f[B]unlink\f[R]\[cq]ed -and the policy is different for those then a completely different file -or directory could be acted on. -.IP \[bu] 2 -Some policies mixed with some functions may result in strange behaviors. -Not that some of these behaviors and race conditions couldn\[cq]t happen -outside \f[B]mergerfs\f[R] but that they are far more likely to occur on -account of the attempt to merge together multiple sources of data which -could be out of sync due to the different policies. -.IP \[bu] 2 -For consistency its generally best to set \f[B]category\f[R] wide -policies rather than individual \f[B]func\f[R]\[cq]s. -This will help limit the confusion of tools such as -rsync (http://linux.die.net/man/1/rsync). -However, the flexibility is there if needed. -.SH KNOWN ISSUES / BUGS -.SS kernel issues & bugs -.PP - -.SS directory mtime is not being updated -.PP -Remember that the default policy for \f[C]getattr\f[R] is \f[C]ff\f[R]. -The information for the first directory found will be returned. -If it wasn\[cq]t the directory which had been updated then it will -appear outdated. -.PP -The reason this is the default is because any other policy would be more -expensive and for many applications it is unnecessary. -To always return the directory with the most recent mtime or a faked -value based on all found would require a scan of all filesystems. -.PP -If you always want the directory information from the one with the most -recent mtime then use the \f[C]newest\f[R] policy for \f[C]getattr\f[R]. -.SS `mv /mnt/pool/foo /mnt/disk1/foo' removes `foo' -.PP -This is not a bug. -.PP -Run in verbose mode to better understand what\[cq]s happening: -.IP -.nf -\f[C] -$ mv -v /mnt/pool/foo /mnt/disk1/foo -copied \[aq]/mnt/pool/foo\[aq] -> \[aq]/mnt/disk1/foo\[aq] -removed \[aq]/mnt/pool/foo\[aq] -$ ls /mnt/pool/foo -ls: cannot access \[aq]/mnt/pool/foo\[aq]: No such file or directory -\f[R] -.fi -.PP -\f[C]mv\f[R], when working across devices, is copying the source to -target and then removing the source. -Since the source \f[B]is\f[R] the target in this case, depending on the -unlink policy, it will remove the just copied file and other files -across the branches. -.PP -If you want to move files to one filesystem just copy them there and use -mergerfs.dedup to clean up the old paths or manually remove them from -the branches directly. -.SS cached memory appears greater than it should be -.PP -Use \f[C]cache.files=off\f[R] and/or \f[C]dropcacheonclose=true\f[R]. -See the section on page caching. -.SS NFS clients returning ESTALE / Stale file handle -.PP -NFS generally does not like out of band changes. -Take a look at the section on NFS in the #remote-filesystems for more -details. -.SS rtorrent fails with ENODEV (No such device) -.PP -Be sure to set -\f[C]cache.files=partial|full|auto-full|per-processe\f[R]. -rtorrent and some other applications use -mmap (http://linux.die.net/man/2/mmap) to read and write to files and -offer no fallback to traditional methods. -FUSE does not currently support mmap while using \f[C]direct_io\f[R]. -There may be a performance penalty on writes with \f[C]direct_io\f[R] -off as well as the problem of double caching but it\[cq]s the only way -to get such applications to work. -If the performance loss is too high for other apps you can mount -mergerfs twice. -Once with \f[C]direct_io\f[R] enabled and one without it. -Be sure to set \f[C]dropcacheonclose=true\f[R] if not using -\f[C]direct_io\f[R]. -.SS Plex doesn\[cq]t work with mergerfs -.PP -It does. -If you\[cq]re trying to put Plex\[cq]s config / metadata / database on -mergerfs you can\[cq]t set \f[C]cache.files=off\f[R] because Plex is -using sqlite3 with mmap enabled. -Shared mmap is not supported by Linux\[cq]s FUSE implementation when -page caching is disabled. -To fix this place the data elsewhere (preferable) or enable -\f[C]cache.files\f[R] (with \f[C]dropcacheonclose=true\f[R]). -Sqlite3 does not need mmap but the developer needs to fall back to -standard IO if mmap fails. -.PP -This applies to other software: Radarr, Sonarr, Lidarr, Jellyfin, etc. -.PP -I would recommend reaching out to the developers of the software -you\[cq]re having troubles with and asking them to add a fallback to -regular file IO when mmap is unavailable. -.PP -If the issue is that scanning doesn\[cq]t seem to pick up media then be -sure to set \f[C]func.getattr=newest\f[R] though generally a full scan -will pick up all media anyway. -.SS When a program tries to move or rename a file it fails -.PP -Please read the section above regarding rename & link. -.PP -The problem is that many applications do not properly handle -\f[C]EXDEV\f[R] errors which \f[C]rename\f[R] and \f[C]link\f[R] may -return even though they are perfectly valid situations which do not -indicate actual device, filesystem, or OS errors. -The error will only be returned by mergerfs if using a path preserving -policy as described in the policy section above. -If you do not care about path preservation simply change the mergerfs -policy to the non-path preserving version. -For example: \f[C]-o category.create=mfs\f[R] Ideally the offending -software would be fixed and it is recommended that if you run into this -problem you contact the software\[cq]s author and request proper -handling of \f[C]EXDEV\f[R] errors. -.SS my 32bit software has problems -.PP -Some software have problems with 64bit inode values. -The symptoms can include EOVERFLOW errors when trying to list files. -You can address this by setting \f[C]inodecalc\f[R] to one of the 32bit -based algos as described in the relevant section. -.SS Samba: Moving files / directories fails -.PP -Workaround: Copy the file/directory and then remove the original rather -than move. -.PP -This isn\[cq]t an issue with Samba but some SMB clients. -GVFS-fuse v1.20.3 and prior (found in Ubuntu 14.04 among others) failed -to handle certain error codes correctly. -Particularly \f[B]STATUS_NOT_SAME_DEVICE\f[R] which comes from the -\f[B]EXDEV\f[R] which is returned by \f[B]rename\f[R] when the call is -crossing mount points. -When a program gets an \f[B]EXDEV\f[R] it needs to explicitly take an -alternate action to accomplish its goal. -In the case of \f[B]mv\f[R] or similar it tries \f[B]rename\f[R] and on -\f[B]EXDEV\f[R] falls back to a manual copying of data between the two -locations and unlinking the source. -In these older versions of GVFS-fuse if it received \f[B]EXDEV\f[R] it -would translate that into \f[B]EIO\f[R]. -This would cause \f[B]mv\f[R] or most any application attempting to move -files around on that SMB share to fail with a IO error. -.PP -GVFS-fuse v1.22.0 (https://bugzilla.gnome.org/show_bug.cgi?id=734568) -and above fixed this issue but a large number of systems use the older -release. -On Ubuntu the version can be checked by issuing -\f[C]apt-cache showpkg gvfs-fuse\f[R]. -Most distros released in 2015 seem to have the updated release and will -work fine but older systems may not. -Upgrading gvfs-fuse or the distro in general will address the problem. -.PP -In Apple\[cq]s MacOSX 10.9 they replaced Samba (client and server) with -their own product. -It appears their new client does not handle \f[B]EXDEV\f[R] either and -responds similar to older release of gvfs on Linux. -.SS Trashing files occasionally fails -.PP -This is the same issue as with Samba. -\f[C]rename\f[R] returns \f[C]EXDEV\f[R] (in our case that will really -only happen with path preserving policies like \f[C]epmfs\f[R]) and the -software doesn\[cq]t handle the situation well. -This is unfortunately a common failure of software which moves files -around. -The standard indicates that an implementation \f[C]MAY\f[R] choose to -support non-user home directory trashing of files (which is a -\f[C]MUST\f[R]). -The implementation \f[C]MAY\f[R] also support \[lq]top directory -trashes\[rq] which many probably do. -.PP -To create a \f[C]$topdir/.Trash\f[R] directory as defined in the -standard use the -mergerfs-tools (https://github.com/trapexit/mergerfs-tools) tool -\f[C]mergerfs.mktrash\f[R]. -.SS Supplemental user groups -.PP -Due to the overhead of -getgroups/setgroups (http://linux.die.net/man/2/setgroups) mergerfs -utilizes a cache. -This cache is opportunistic and per thread. -Each thread will query the supplemental groups for a user when that -particular thread needs to change credentials and will keep that data -for the lifetime of the thread. -This means that if a user is added to a group it may not be picked up -without the restart of mergerfs. -However, since the high level FUSE API\[cq]s (at least the standard -version) thread pool dynamically grows and shrinks it\[cq]s possible -that over time a thread will be killed and later a new thread with no -cache will start and query the new data. -.PP -The gid cache uses fixed storage to simplify the design and be -compatible with older systems which may not have C++11 compilers. -There is enough storage for 256 users\[cq] supplemental groups. -Each user is allowed up to 32 supplemental groups. -Linux >= 2.6.3 allows up to 65535 groups per user but most other *nixs -allow far less. -NFS allowing only 16. -The system does handle overflow gracefully. -If the user has more than 32 supplemental groups only the first 32 will -be used. -If more than 256 users are using the system when an uncached user is -found it will evict an existing user\[cq]s cache at random. -So long as there aren\[cq]t more than 256 active users this should be -fine. -If either value is too low for your needs you will have to modify -\f[C]gidcache.hpp\f[R] to increase the values. -Note that doing so will increase the memory needed by each thread. -.PP -While not a bug some users have found when using containers that -supplemental groups defined inside the container don\[cq]t work properly -with regard to permissions. -This is expected as mergerfs lives outside the container and therefore -is querying the host\[cq]s group database. -There might be a hack to work around this (make mergerfs read the -/etc/group file in the container) but it is not yet implemented and -would be limited to Linux and the /etc/group DB. -Preferably users would mount in the host group file into the containers -or use a standard shared user & groups technology like NIS or LDAP. -.SH Remote Filesystems -.PP -Many users ask about compatibility with remote filesystems. -This section is to describe any known issues or quirks when using -mergerfs with common remote filesystems. -.PP -Keep in mind that, like with caching, it is not a good idea to change -the contents of the remote filesystem -out-of-band (https://en.wikipedia.org/wiki/Out-of-band). -Meaning that you really shouldn\[cq]t change the contents of the -underlying filesystems or mergerfs on the server hosting the remote -filesystem. -Doing so can lead to weird behavior, inconsistency, errors, and even -data corruption should multiple programs try to write or read the same -data at the same time. -This isn\[cq]t to say you can\[cq]t do it or that data corruption is -likely but it \f[I]could\f[R] happen. -It is better to always use the remote filesystem. -Even on the machine serving it. -.SS NFS -.PP -NFS (https://en.wikipedia.org/wiki/Network_File_System) is a common -remote filesystem on Unix/POSIX systems. -Due to how NFS works there are some settings which need to be set in -order for mergerfs to work with it. -.PP -It should be noted that NFS and FUSE (the technology mergerfs uses) do -not work perfectly with one another due to certain design choices in -FUSE (and mergerfs.) Due to these issues it is generally recommended to -use SMB when possible till situations change. -That said mergerfs should generally work as an export of NFS and issues -discovered should still be reported. -.PP -To ensure compatibility between mergerfs and NFS use the following -settings. -.PP -mergerfs settings: * noforget * inodecalc=path-hash -.PP -NFS export settings: * fsid=UUID * no_root_squash -.PP -\f[C]noforget\f[R] is needed because NFS uses the -\f[C]name_to_handle_at\f[R] and \f[C]open_by_handle_at\f[R] 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 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 node and should NFS ever ask for that node\[cq]s details in -the future it would have nothing to respond with. -Keeping nodes around forever is not ideal but at the moment the only way -to manage the situation. -.PP -\f[C]inodecalc=path-hash\f[R] is needed because NFS is sensitive to -out-of-band changes. -FUSE doesn\[cq]t care if a file\[cq]s inode value changes but NFS, being -stateful, does. -So if you used the default inode calculation algorithm then it is -possible that if you changed a file or updated a directory the file -mergerfs will use will be on a different branch and therefore the inode -would change. -This isn\[cq]t an ideal solution and others are being considered but it -works for most situations. -.PP -\f[C]fsid=UUID\f[R] is needed because FUSE filesystems don\[cq]t have -different \f[C]st_dev\f[R] values which can cause issues when exporting. -The easiest thing to do is set each mergerfs export \f[C]fsid\f[R] to -some random value. -An easy way to generate a random value is to use the command line tool -\f[C]uuid\f[R] or \f[C]uuidgen\f[R] or through a website such as -uuidgenerator.net (https://www.uuidgenerator.net/). -.PP -\f[C]no_root_squash\f[R] is not strictly necessary but can lead to -confusing permission and ownership issues if root squashing is enabled. -.SS SMB / CIFS -.PP -SMB (https://en.wikipedia.org/wiki/Server_Message_Block) is a protocol -most used by Microsoft Windows systems to share file shares, printers, -etc. -However, due to the popularity for Windows, it is also supported on many -other platforms including Linux. -The most popular way of supporting SMB on Linux is via the software -Samba. -.PP -Samba (https://en.wikipedia.org/wiki/Samba_(software)), and other ways -of serving Linux filesystems, via SMB should work fine with mergerfs. -The services do not tend to use the same technologies which NFS uses and -therefore don\[cq]t have the same issues. -There should not be an special settings required to use mergerfs with -Samba. -However, CIFSD (https://en.wikipedia.org/wiki/CIFSD) and other programs -have not been extensively tested. -If you use mergerfs with CIFSD or other SMB servers please submit your -experiences so these docs can be updated. -.SS SSHFS -.PP -SSHFS (https://en.wikipedia.org/wiki/SSHFS) is a FUSE filesystem -leveraging SSH as the connection and transport layer. -While often simpler to setup when compared to NFS or Samba the -performance can be lacking and the project is very much in maintenance -mode. -.PP -There are no known issues using sshfs with mergerfs. -You may want to use the following arguments to improve performance but -your millage may vary. -.IP \[bu] 2 -\f[C]-o Ciphers=arcfour\f[R] -.IP \[bu] 2 -\f[C]-o Compression=no\f[R] -.PP -More info can be found -here (https://ideatrash.net/2016/08/odds-and-ends-optimizing-sshfs-moving.html). -.SS Other -.PP -There are other remote filesystems but none popularly used to serve -mergerfs. -If you use something not listed above feel free to reach out and I will -add it to the list. -.SH FAQ -.SS How well does mergerfs scale? Is it \[lq]production ready?\[rq] -.PP -Users have reported running mergerfs on everything from a Raspberry Pi -to dual socket Xeon systems with >20 cores. -I\[cq]m aware of at least a few companies which use mergerfs in -production. -Open Media Vault (https://www.openmediavault.org) includes mergerfs as -its sole solution for pooling filesystems. -The author of mergerfs had it running for over 300 days managing 16+ -devices with reasonably heavy 24/7 read and write usage. -Stopping only after the machine\[cq]s power supply died. -.PP -Most serious issues (crashes or data corruption) have been due to kernel -bugs (https://github.com/trapexit/mergerfs/wiki/Kernel-Issues-&-Bugs). -All of which are fixed in stable releases. -.SS Can mergerfs be used with filesystems which already have data / are in use? -.PP -Yes. -mergerfs is really just a proxy and does \f[B]NOT\f[R] interfere with -the normal form or function of the filesystems / mounts / paths it -manages. -It is just another userland application that is acting as a -man-in-the-middle. -It can\[cq]t do anything that any other random piece of software -can\[cq]t do. -.PP -mergerfs is \f[B]not\f[R] a traditional filesystem that takes control -over the underlying block device. -mergerfs is \f[B]not\f[R] RAID. -It does \f[B]not\f[R] manipulate the data that passes through it. -It does \f[B]not\f[R] shard data across filesystems. -It merely shards some \f[B]behavior\f[R] and aggregates others. -.SS Can drives/filesystems be removed from the pool at will? -.PP -Yes. -See previous question\[cq]s answer. -.SS Can mergerfs be removed without affecting the data? -.PP -Yes. -See the previous question\[cq]s answer. -.SS Can drives/filesystems be moved to another pool? -.PP -Yes. -See the previous question\[cq]s answer. -.SS How do I migrate data into or out of the pool when adding/removing drives/filesystems? -.PP -You don\[cq]t need to. -See the previous question\[cq]s answer. -.SS How do I remove a drive/filesystem but keep the data in the pool? -.PP -Nothing special needs to be done. -Remove the branch from mergerfs\[cq] config and copy (rsync) the data -from the removed filesystem into the pool. -Effectively the same as if it were you transfering data from one -filesystem to another. -.PP -If you wish to continue using the pool while performing the transfer -simply create another, temporary pool without the filesystem in question -and then copy the data. -It would probably be a good idea to set the branch to \f[C]RO\f[R] prior -to doing this to ensure no new content is written to the filesystem -while performing the copy. -.SS What policies should I use? -.PP -Unless you\[cq]re doing something more niche the average user is -probably best off using \f[C]mfs\f[R] for \f[C]category.create\f[R]. -It will spread files out across your branches based on available space. -Use \f[C]mspmfs\f[R] if you want to try to colocate the data a bit more. -You may want to use \f[C]lus\f[R] if you prefer a slightly different -distribution of data if you have a mix of smaller and larger -filesystems. -Generally though \f[C]mfs\f[R], \f[C]lus\f[R], or even \f[C]rand\f[R] -are good for the general use case. -If you are starting with an imbalanced pool you can use the tool -\f[B]mergerfs.balance\f[R] to redistribute files across the pool. -.PP -If you really wish to try to colocate files based on directory you can -set \f[C]func.create\f[R] to \f[C]epmfs\f[R] or similar and -\f[C]func.mkdir\f[R] to \f[C]rand\f[R] or \f[C]eprand\f[R] depending on -if you just want to colocate generally or on specific branches. -Either way the \f[I]need\f[R] to colocate is rare. -For instance: if you wish to remove the device regularly and want the -data to predictably be on that device or if you don\[cq]t use backup at -all and don\[cq]t wish to replace that data piecemeal. -In which case using path preservation can help but will require some -manual attention. -Colocating after the fact can be accomplished using the -\f[B]mergerfs.consolidate\f[R] tool. -If you don\[cq]t need strict colocation which the \f[C]ep\f[R] policies -provide then you can use the \f[C]msp\f[R] based policies which will -walk back the path till finding a branch that works. -.PP -Ultimately there is no correct answer. -It is a preference or based on some particular need. -mergerfs is very easy to test and experiment with. -I suggest creating a test setup and experimenting to get a sense of what -you want. -.PP -\f[C]epmfs\f[R] is the default \f[C]category.create\f[R] policy because -\f[C]ep\f[R] policies are not going to change the general layout of the -branches. -It won\[cq]t place files/dirs on branches that don\[cq]t already have -the relative branch. -So it keeps the system in a known state. -It\[cq]s much easier to stop using \f[C]epmfs\f[R] or redistribute files -around the filesystem than it is to consolidate them back. -.SS What settings should I use? -.PP -Depends on what features you want. -Generally speaking there are no \[lq]wrong\[rq] settings. -All settings are performance or feature related. -The best bet is to read over the available options and choose what fits -your situation. -If something isn\[cq]t clear from the documentation please reach out and -the documentation will be improved. -.PP -That said, for the average person, the following should be fine: -.PP -\f[C]cache.files=off,dropcacheonclose=true,category.create=mfs\f[R] -.SS Why are all my files ending up on 1 filesystem?! -.PP -Did you start with empty filesystems? -Did you explicitly configure a \f[C]category.create\f[R] policy? -Are you using an \f[C]existing path\f[R] / \f[C]path preserving\f[R] -policy? -.PP -The default create policy is \f[C]epmfs\f[R]. -That is a path preserving algorithm. -With such a policy for \f[C]mkdir\f[R] and \f[C]create\f[R] with a set -of empty filesystems it will select only 1 filesystem when the first -directory is created. -Anything, files or directories, created in that first directory will be -placed on the same branch because it is preserving paths. -.PP -This catches a lot of new users off guard but changing the default would -break the setup for many existing users and this policy is the safest -policy as it will not change the general layout of the existing -filesystems. -If you do not care about path preservation and wish your files to be -spread across all your filesystems change to \f[C]mfs\f[R] or similar -policy as described above. -If you do want path preservation you\[cq]ll need to perform the manual -act of creating paths on the filesystems you want the data to land on -before transferring your data. -Setting \f[C]func.mkdir=epall\f[R] can simplify managing path -preservation for \f[C]create\f[R]. -Or use \f[C]func.mkdir=rand\f[R] if you\[cq]re interested in just -grouping together directory content by filesystem. -.SS Do hardlinks work? -.PP -Yes. -See also the option \f[C]inodecalc\f[R] for how inode values are -calculated. -.PP -What mergerfs does not do is fake hard links across branches. -Read the section \[lq]rename & link\[rq] for how it works. -.PP -Remember that hardlinks will NOT work across devices. -That includes between the original filesystem and a mergerfs pool, -between two separate pools of the same underlying filesystems, or bind -mounts of paths within the mergerfs pool. -The latter is common when using Docker or Podman. -Multiple volumes (bind mounts) to the same underlying filesystem are -considered different devices. -There is no way to link between them. -You should mount in the highest directory in the mergerfs pool that -includes all the paths you need if you want links to work. -.SS Does FICLONE or FICLONERANGE work? -.PP -Unfortunately not. -FUSE, the technology mergerfs is based on, does not support the -\f[C]clone_file_range\f[R] feature needed for it to work. -mergerfs won\[cq]t even know such a request is made. -The kernel will simply return an error back to the application making -the request. -.PP -Should FUSE gain the ability mergerfs will be updated to support it. -.SS Can I use mergerfs without SnapRAID? SnapRAID without mergerfs? -.PP -Yes. -They are completely unrelated pieces of software. -.SS Can mergerfs run via Docker, Podman, Kubernetes, etc. -.PP -Yes. -With Docker you\[cq]ll need to include -\f[C]--cap-add=SYS_ADMIN --device=/dev/fuse --security-opt=apparmor:unconfined\f[R] -or similar with other container runtimes. -You should also be running it as root or given sufficient caps to change -user and group identity as well as have root like filesystem -permissions. -.PP -Keep in mind that you \f[B]MUST\f[R] consider identity when using -containers. -For example: supplemental groups will be picked up from the container -unless you properly manage users and groups by sharing relevant /etc -files or by using some other means to share identity across containers. -Similarly if you use \[lq]rootless\[rq] containers and user namespaces -to do uid/gid translations you \f[B]MUST\f[R] consider that while -managing shared files. -.PP -Also, as mentioned by hotio (https://hotio.dev/containers/mergerfs), -with Docker you should probably be mounting with -\f[C]bind-propagation\f[R] set to \f[C]slave\f[R]. -.SS Does mergerfs support CoW / copy-on-write / writes to read-only filesystems? -.PP -Not in the sense of a filesystem like BTRFS or ZFS nor in the overlayfs -or aufs sense. -It does offer a -cow-shell (http://manpages.ubuntu.com/manpages/bionic/man1/cow-shell.1.html) -like hard link breaking (copy to temp file then rename over original) -which can be useful when wanting to save space by hardlinking duplicate -files but wish to treat each name as if it were a unique and separate -file. -.PP -If you want to write to a read-only filesystem you should look at -overlayfs. -You can always include the overlayfs mount into a mergerfs pool. -.SS Why can\[cq]t I see my files / directories? -.PP -It\[cq]s almost always a permissions issue. -Unlike mhddfs and unionfs-fuse, which runs as root and attempts to -access content as such, mergerfs always changes its credentials to that -of the caller. -This means that if the user does not have access to a file or directory -than neither will mergerfs. -However, because mergerfs is creating a union of paths it may be able to -read some files and directories on one filesystem but not another -resulting in an incomplete set. -.PP -Whenever you run into a split permission issue (seeing some but not all -files) try using -mergerfs.fsck (https://github.com/trapexit/mergerfs-tools) tool to check -for and fix the mismatch. -If you aren\[cq]t seeing anything at all be sure that the basic -permissions are correct. -The user and group values are correct and that directories have their -executable bit set. -A common mistake by users new to Linux is to \f[C]chmod -R 644\f[R] when -they should have \f[C]chmod -R u=rwX,go=rX\f[R]. -.PP -If using a network filesystem such as NFS or SMB (Samba) be sure to pay -close attention to anything regarding permissioning and users. -Root squashing and user translation for instance has bitten a few -mergerfs users. -Some of these also affect the use of mergerfs from container platforms -such as Docker. -.SS Why use FUSE? Why not a kernel based solution? -.PP -As with any solutions to a problem there are advantages and -disadvantages to each one. -.PP -A FUSE based solution has all the downsides of FUSE: -.IP \[bu] 2 -Higher IO latency due to the trips in and out of kernel space -.IP \[bu] 2 -Higher general overhead due to trips in and out of kernel space -.IP \[bu] 2 -Double caching when using page caching -.IP \[bu] 2 -Misc limitations due to FUSE\[cq]s design -.PP -But FUSE also has a lot of upsides: -.IP \[bu] 2 -Easier to offer a cross platform solution -.IP \[bu] 2 -Easier forward and backward compatibility -.IP \[bu] 2 -Easier updates for users -.IP \[bu] 2 -Easier and faster release cadence -.IP \[bu] 2 -Allows more flexibility in design and features -.IP \[bu] 2 -Overall easier to write, secure, and maintain -.IP \[bu] 2 -Much lower barrier to entry (getting code into the kernel takes a lot of -time and effort initially) -.PP -FUSE was chosen because of all the advantages listed above. -The negatives of FUSE do not outweigh the positives. -.SS Is my OS\[cq]s libfuse needed for mergerfs to work? -.PP -No.\ Normally \f[C]mount.fuse\f[R] is needed to get mergerfs (or any -FUSE filesystem to mount using the \f[C]mount\f[R] command but in -vendoring the libfuse library the \f[C]mount.fuse\f[R] app has been -renamed to \f[C]mount.mergerfs\f[R] meaning the filesystem type in -\f[C]fstab\f[R] can simply be \f[C]mergerfs\f[R]. -That said there should be no harm in having it installed and continuing -to using \f[C]fuse.mergerfs\f[R] as the type in \f[C]/etc/fstab\f[R]. -.PP -If \f[C]mergerfs\f[R] doesn\[cq]t work as a type it could be due to how -the \f[C]mount.mergerfs\f[R] tool was installed. -Must be in \f[C]/sbin/\f[R] with proper permissions. -.SS Why was splice support removed? -.PP -After a lot of testing over the years splicing always appeared to be at -best provide equivalent performance and in cases worse performance. -Splice is not supported on other platforms forcing a traditional -read/write fallback to be provided. -The splice code was removed to simplify the codebase. -.SS What should mergerfs NOT be used for? -.IP \[bu] 2 -databases: Even if the database stored data in separate files (mergerfs -wouldn\[cq]t offer much otherwise) the higher latency of the indirection -will kill performance. -If it is a lightly used SQLITE database then it may be fine but -you\[cq]ll need to test. -.IP \[bu] 2 -VM images: For the same reasons as databases. -VM images are accessed very aggressively and mergerfs will introduce too -much latency (if it works at all). -.IP \[bu] 2 -As replacement for RAID: mergerfs is just for pooling branches. -If you need that kind of device performance aggregation or high -availability you should stick with RAID. -.SS Can filesystems be written to directly? Outside of mergerfs while pooled? -.PP -Yes, however it\[cq]s not recommended to use the same file from within -the pool and from without at the same time (particularly writing). -Especially if using caching of any kind (cache.files, cache.entry, -cache.attr, cache.negative_entry, cache.symlinks, cache.readdir, etc.) -as there could be a conflict between cached data and not. -.SS Why do I get an \[lq]out of space\[rq] / \[lq]no space left on device\[rq] / ENOSPC error even though there appears to be lots of space available? -.PP -First make sure you\[cq]ve read the sections above about policies, path -preservation, branch filtering, and the options \f[B]minfreespace\f[R], -\f[B]moveonenospc\f[R], \f[B]statfs\f[R], and \f[B]statfs_ignore\f[R]. -.PP -mergerfs is simply presenting a union of the content within multiple -branches. -The reported free space is an aggregate of space available within the -pool (behavior modified by \f[B]statfs\f[R] and -\f[B]statfs_ignore\f[R]). -It does not represent a contiguous space. -In the same way that read-only filesystems, those with quotas, or -reserved space report the full theoretical space available. -.PP -Due to path preservation, branch tagging, read-only status, and -\f[B]minfreespace\f[R] settings it is perfectly valid that -\f[C]ENOSPC\f[R] / \[lq]out of space\[rq] / \[lq]no space left on -device\[rq] be returned. -It is doing what was asked of it: filtering possible branches due to -those settings. -Only one error can be returned and if one of the reasons for filtering a -branch was \f[B]minfreespace\f[R] then it will be returned as such. -\f[B]moveonenospc\f[R] is only relevant to writing a file which is too -large for the filesystem it\[cq]s currently on. -.PP -It is also possible that the filesystem selected has run out of inodes. -Use \f[C]df -i\f[R] to list the total and available inodes per -filesystem. -.PP -If you don\[cq]t care about path preservation then simply change the -\f[C]create\f[R] policy to one which isn\[cq]t. -\f[C]mfs\f[R] is probably what most are looking for. -The reason it\[cq]s not default is because it was originally set to -\f[C]epmfs\f[R] and changing it now would change people\[cq]s setup. -Such a setting change will likely occur in mergerfs 3. -.SS Why does the total available space in mergerfs not equal outside? -.PP -Are you using ext2/3/4? -With reserve for root? -mergerfs uses available space for statfs calculations. -If you\[cq]ve reserved space for root then it won\[cq]t show up. -.PP -You can remove the reserve by running: \f[C]tune2fs -m 0 \f[R] -.SS I notice massive slowdowns of writes when enabling cache.files. -.PP -When file caching is enabled in any form (\f[C]cache.files!=off\f[R]) it -will issue \f[C]getxattr\f[R] requests for \f[C]security.capability\f[R] -prior to \f[I]every single write\f[R]. -This will usually result in a performance degradation, especially when -using a network filesystem (such as NFS or SMB.) Unfortunately at this -moment the kernel is not caching the response. -.PP -To work around this situation mergerfs offers a few solutions. -.IP "1." 3 -Set \f[C]security_capability=false\f[R]. -It will short circuit any call and return \f[C]ENOATTR\f[R]. -This still means though that mergerfs will receive the request before -every write but at least it doesn\[cq]t get passed through to the -underlying filesystem. -.IP "2." 3 -Set \f[C]xattr=noattr\f[R]. -Same as above but applies to \f[I]all\f[R] calls to getxattr. -Not just \f[C]security.capability\f[R]. -This will not be cached by the kernel either but mergerfs\[cq] runtime -config system will still function. -.IP "3." 3 -Set \f[C]xattr=nosys\f[R]. -Results in mergerfs returning \f[C]ENOSYS\f[R] which \f[I]will\f[R] be -cached by the kernel. -No future xattr calls will be forwarded to mergerfs. -The downside is that also means the xattr based config and query -functionality won\[cq]t work either. -.IP "4." 3 -Disable file caching. -If you aren\[cq]t using applications which use \f[C]mmap\f[R] it\[cq]s -probably simpler to just disable it all together. -The kernel won\[cq]t send the requests when caching is disabled. -.SS It\[cq]s mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them? -.PP -mhddfs (https://github.com/trapexit/mhddfs) manages running as -\f[B]root\f[R] by calling -getuid() (https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319) -and if it returns \f[B]0\f[R] then it will -chown (http://linux.die.net/man/1/chown) the file. -Not only is that a race condition but it doesn\[cq]t handle other -situations. -Rather than attempting to simulate POSIX ACL behavior the proper way to -manage this is to use seteuid (http://linux.die.net/man/2/seteuid) and -setegid (http://linux.die.net/man/2/setegid), in effect becoming the -user making the original call, and perform the action as them. -This is what mergerfs does and why mergerfs should always run as root. -.PP -In Linux setreuid syscalls apply only to the thread. -GLIBC hides this away by using realtime signals to inform all threads to -change credentials. -Taking after \f[B]Samba\f[R], mergerfs uses -\f[B]syscall(SYS_setreuid,\&...)\f[R] to set the callers credentials for -that thread only. -Jumping back to \f[B]root\f[R] as necessary should escalated privileges -be needed (for instance: to clone paths between filesystems). -.PP -For non-Linux systems mergerfs uses a read-write lock and changes -credentials only when necessary. -If multiple threads are to be user X then only the first one will need -to change the processes credentials. -So long as the other threads need to be user X they will take a readlock -allowing multiple threads to share the credentials. -Once a request comes in to run as user Y that thread will attempt a -write lock and change to Y\[cq]s credentials when it can. -If the ability to give writers priority is supported then that flag will -be used so threads trying to change credentials don\[cq]t starve. -This isn\[cq]t the best solution but should work reasonably well -assuming there are few users. -.SH mergerfs versus X -.SS mhddfs -.PP -mhddfs had not been maintained for some time and has some known -stability and security issues. -mergerfs provides a superset of mhddfs\[cq] features and should offer -the same or better performance. -.PP -Below is an example of mhddfs and mergerfs setup to work similarly. -.PP -\f[C]mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool\f[R] -.PP -\f[C]mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool\f[R] -.SS aufs -.PP -aufs is mostly abandoned and no longer available in most Linux distros. -.PP -While aufs can offer better peak performance mergerfs provides more -configurability and is generally easier to use. -mergerfs however does not offer the overlay / copy-on-write (CoW) -features which aufs has. -.SS unionfs-fuse -.PP -unionfs-fuse is more like aufs than mergerfs in that it offers overlay / -copy-on-write (CoW) features. -If you\[cq]re just looking to create a union of filesystems and want -flexibility in file/directory placement then mergerfs offers that -whereas unionfs is more for overlaying read/write filesystems over -read-only ones. -.SS overlayfs -.PP -overlayfs is similar to aufs and unionfs-fuse in that it also is -primarily used to layer a read/write filesystem over one or more -read-only filesystems. -It does not have the ability to spread files/directories across numerous -filesystems. -.SS RAID0, JBOD, drive concatenation, striping -.PP -With simple JBOD / drive concatenation / stripping / RAID0 a single -drive failure will result in full pool failure. -mergerfs performs a similar function without the possibility of -catastrophic failure and the difficulties in recovery. -Drives may fail but all other filesystems and their data will continue -to be accessible. -.PP -The main practical difference with mergerfs is the fact you don\[cq]t -actually have contiguous space as large as if you used those other -technologies. -Meaning you can\[cq]t create a 2TB file on a pool of 2 1TB filesystems. -.PP -When combined with something like SnapRaid (http://www.snapraid.it) -and/or an offsite backup solution you can have the flexibility of JBOD -without the single point of failure. -.SS UnRAID -.PP -UnRAID is a full OS and its storage layer, as I understand, is -proprietary and closed source. -Users who have experience with both have often said they prefer the -flexibility offered by mergerfs and for some the fact it is open source -is important. -.PP -There are a number of UnRAID users who use mergerfs as well though -I\[cq]m not entirely familiar with the use case. -.PP -For semi-static data mergerfs + SnapRaid (http://www.snapraid.it) -provides a similar solution. -.SS ZFS -.PP -mergerfs is very different from ZFS. -mergerfs is intended to provide flexible pooling of arbitrary -filesystems (local or remote), of arbitrary sizes, and arbitrary -filesystems. -For \f[C]write once, read many\f[R] usecases such as bulk media storage. -Where data integrity and backup is managed in other ways. -In those usecases ZFS can introduce a number of costs and limitations as -described -here (http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html), -here (https://markmcb.com/2020/01/07/five-years-of-btrfs/), and -here (https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSWhyNoRealReshaping). -.SS StableBit\[cq]s DrivePool -.PP -DrivePool works only on Windows so not as common an alternative as other -Linux solutions. -If you want to use Windows then DrivePool is a good option. -Functionally the two projects work a bit differently. -DrivePool always writes to the filesystem with the most free space and -later rebalances. -mergerfs does not offer rebalance but chooses a branch at file/directory -create time. -DrivePool\[cq]s rebalancing can be done differently in any directory and -has file pattern matching to further customize the behavior. -mergerfs, not having rebalancing does not have these features, but -similar features are planned for mergerfs v3. -DrivePool has builtin file duplication which mergerfs does not natively -support (but can be done via an external script.) -.PP -There are a lot of misc differences between the two projects but most -features in DrivePool can be replicated with external tools in -combination with mergerfs. -.PP -Additionally DrivePool is a closed source commercial product vs mergerfs -a ISC licensed OSS project. -.SH SUPPORT -.PP -Filesystems are complex and difficult to debug. -mergerfs, while being just a proxy of sorts, can be difficult to debug -given the large number of possible settings it can have itself and the -number of environments it can run in. -When reporting on a suspected issue \f[B]please\f[R] include as much of -the below information as possible otherwise it will be difficult or -impossible to diagnose. -Also please read the above documentation as it provides details on many -previously encountered questions/issues. -.PP -\f[B]Please make sure you are using the latest -release (https://github.com/trapexit/mergerfs/releases) or have tried it -in comparison. Old versions, which are often included in distros like -Debian and Ubuntu, are not ever going to be updated and the issue you -are encountering may have been addressed already.\f[R] -.PP -\f[B]For commercial support or feature requests please contact me -directly. (mailto:support@spawn.link)\f[R] -.SS Information to include in bug reports -.IP \[bu] 2 -Information about the broader problem along with any attempted -solutions. (https://xyproblem.info) -.IP \[bu] 2 -Solution already ruled out and why. -.IP \[bu] 2 -Version of mergerfs: \f[C]mergerfs --version\f[R] -.IP \[bu] 2 -mergerfs settings / arguments: from fstab, systemd unit, command line, -OMV plugin, etc. -.IP \[bu] 2 -Version of the OS: \f[C]uname -a\f[R] and \f[C]lsb_release -a\f[R] -.IP \[bu] 2 -List of branches, their filesystem types, sizes (before and after -issue): \f[C]df -h\f[R] -.IP \[bu] 2 -\f[B]All\f[R] information about the relevant paths and files: -permissions, ownership, etc. -.IP \[bu] 2 -\f[B]All\f[R] information about the client app making the requests: -version, uid/gid -.IP \[bu] 2 -Runtime environment: -.RS 2 -.IP \[bu] 2 -Is mergerfs running within a container? -.IP \[bu] 2 -Are the client apps using mergerfs running in a container? -.RE -.IP \[bu] 2 -A \f[C]strace\f[R] of the app having problems: -.RS 2 -.IP \[bu] 2 -\f[C]strace -fvTtt -s 256 -o /tmp/app.strace.txt \f[R] -.RE -.IP \[bu] 2 -A \f[C]strace\f[R] of mergerfs while the program is trying to do -whatever it is failing to do: -.RS 2 -.IP \[bu] 2 -\f[C]strace -fvTtt -s 256 -p -o /tmp/mergerfs.strace.txt\f[R] -.RE -.IP \[bu] 2 -\f[B]Precise\f[R] directions on replicating the issue. -Do not leave \f[B]anything\f[R] out. -.IP \[bu] 2 -Try to recreate the problem in the simplest way using standard programs: -\f[C]ln\f[R], \f[C]mv\f[R], \f[C]cp\f[R], \f[C]ls\f[R], \f[C]dd\f[R], -etc. -.SS Contact / Issue submission -.IP \[bu] 2 -github.com: https://github.com/trapexit/mergerfs/issues -.IP \[bu] 2 -discord: https://discord.gg/MpAr69V -.IP \[bu] 2 -reddit: https://www.reddit.com/r/mergerfs -.SS Donations -.PP -https://github.com/trapexit/support -.PP -Development and support of a project like mergerfs requires a -significant amount of time and effort. -The software is released under the very liberal ISC license and is -therefore free to use for personal or commercial uses. -.PP -If you are a personal user and find mergerfs and its support valuable -and would like to support the project financially it would be very much -appreciated. -.PP -If you are using mergerfs commercially please consider sponsoring the -project to ensure it continues to be maintained and receive updates. -If custom features are needed feel free to contact me -directly (mailto:support@spawn.link). -.SH LINKS -.IP \[bu] 2 -https://spawn.link -.IP \[bu] 2 -https://github.com/trapexit/mergerfs -.IP \[bu] 2 -https://github.com/trapexit/mergerfs/wiki -.IP \[bu] 2 -https://github.com/trapexit/mergerfs-tools -.IP \[bu] 2 -https://github.com/trapexit/scorch -.IP \[bu] 2 -https://github.com/trapexit/bbf +.UR https://github.com/trapexit/support +.LI https://github.com/trapexit/support +.UE +.SH LICENSE +ISC License +.SH AUTHOR +Antonio SJ Musumeci (aka trapexit) diff --git a/src/enum.hpp b/src/enum.hpp index b4792bb9..8b1a1e12 100644 --- a/src/enum.hpp +++ b/src/enum.hpp @@ -30,11 +30,11 @@ public: typedef E ENUM; public: - Enum() + Enum() { } - Enum(const ENUM data_) + Enum(const ENUM data_) : _data(data_) { } diff --git a/src/fs_attr.cpp b/src/fs_attr.cpp index 47a9291a..120fc4ae 100644 --- a/src/fs_attr.cpp +++ b/src/fs_attr.cpp @@ -15,9 +15,9 @@ */ #ifdef __linux__ -#warning "using fs_attr_linux.icpp" +#pragma message "using fs_attr_linux.icpp" #include "fs_attr_linux.icpp" #else -#warning "using fs_attr_unsupported.icpp" +#pragma message "using fs_attr_unsupported.icpp" #include "fs_attr_unsupported.icpp" #endif diff --git a/src/fs_copy_file_range.cpp b/src/fs_copy_file_range.cpp index 84372ab7..40ec76ce 100644 --- a/src/fs_copy_file_range.cpp +++ b/src/fs_copy_file_range.cpp @@ -17,9 +17,9 @@ */ #ifdef __linux__ -#warning "using fs_copy_file_range_linux.icpp" +#pragma message "using fs_copy_file_range_linux.icpp" #include "fs_copy_file_range_linux.icpp" #else -#warning "using fs_copy_file_range_unsupported.icpp" +#pragma message "using fs_copy_file_range_unsupported.icpp" #include "fs_copy_file_range_unsupported.icpp" #endif diff --git a/src/fs_fadvise.cpp b/src/fs_fadvise.cpp index 630df585..9d5755cb 100644 --- a/src/fs_fadvise.cpp +++ b/src/fs_fadvise.cpp @@ -17,10 +17,10 @@ #include #if _XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L -#warning "using fs_fadvise_posix.icpp" +#pragma message "using fs_fadvise_posix.icpp" #include "fs_fadvise_posix.icpp" #else -#warning "using fs_fadvise_unsupported.icpp" +#pragma message "using fs_fadvise_unsupported.icpp" #include "fs_fadvise_unsupported.icpp" #endif diff --git a/src/fs_ficlone.cpp b/src/fs_ficlone.cpp index 58310e20..4b1273f6 100644 --- a/src/fs_ficlone.cpp +++ b/src/fs_ficlone.cpp @@ -17,9 +17,9 @@ */ #ifdef __linux__ -#warning "using fs_ficlone_linux.icpp" +#pragma message "using fs_ficlone_linux.icpp" #include "fs_ficlone_linux.icpp" #else -#warning "using fs_ficlone_unsupported.icpp" +#pragma message "using fs_ficlone_unsupported.icpp" #include "fs_ficlone_unsupported.icpp" #endif diff --git a/src/fs_futimens.hpp b/src/fs_futimens.hpp index 2e4b8874..84b32a9e 100644 --- a/src/fs_futimens.hpp +++ b/src/fs_futimens.hpp @@ -23,13 +23,13 @@ #include #ifdef __linux__ -#warning "using fs_futimens_linux.hpp" +#pragma message "using fs_futimens_linux.hpp" #include "fs_futimens_linux.hpp" #elif __FreeBSD__ >= 11 -#warning "using fs_futimens_freebsd_11.hpp" +#pragma message "using fs_futimens_freebsd_11.hpp" #include "fs_futimens_freebsd_11.hpp" #else -#warning "using fs_futimens_generic.hpp" +#pragma message "using fs_futimens_generic.hpp" #include "fs_futimens_generic.hpp" #endif diff --git a/src/fs_futimesat.cpp b/src/fs_futimesat.cpp index 67867e15..7f836988 100644 --- a/src/fs_futimesat.cpp +++ b/src/fs_futimesat.cpp @@ -17,9 +17,9 @@ */ #if __APPLE__ -#warning "using fs_futimesat_osx.icpp" +#pragma message "using fs_futimesat_osx.icpp" #include "fs_futimesat_osx.icpp" #else -#warning "using fs_futimesat_generic.icpp" +#pragma message "using fs_futimesat_generic.icpp" #include "fs_futimesat_generic.icpp" #endif diff --git a/src/fs_sendfile.cpp b/src/fs_sendfile.cpp index 3bb52dec..8e334426 100644 --- a/src/fs_sendfile.cpp +++ b/src/fs_sendfile.cpp @@ -15,9 +15,9 @@ */ #ifdef __linux__ -#warning "using fs_sendfile_linux.icpp" +#pragma message "using fs_sendfile_linux.icpp" #include "fs_sendfile_linux.icpp" #else -#warning "using fs_sendfile_unsupported.icpp" +#pragma message "using fs_sendfile_unsupported.icpp" #include "fs_sendfile_unsupported.icpp" #endif diff --git a/src/fs_statx.hpp b/src/fs_statx.hpp index 70c13a2a..8bdde2e3 100644 --- a/src/fs_statx.hpp +++ b/src/fs_statx.hpp @@ -11,6 +11,10 @@ #include #include +#ifdef STATX_TYPE +#define MERGERFS_STATX_SUPPORTED +#endif + namespace fs { static @@ -22,7 +26,7 @@ namespace fs const unsigned int mask_, struct fuse_statx *st_) { -#ifdef STATX_TYPE +#ifdef MERGERFS_STATX_SUPPORTED int rv; rv = ::statx(dirfd_, diff --git a/src/fs_utimensat.hpp b/src/fs_utimensat.hpp index bd44fdf7..eb46cc5b 100644 --- a/src/fs_utimensat.hpp +++ b/src/fs_utimensat.hpp @@ -19,12 +19,12 @@ #pragma once #ifdef __linux__ -#warning "using fs_utimensat_linux.hpp" +#pragma message "using fs_utimensat_linux.hpp" #include "fs_utimensat_linux.hpp" #elif __FreeBSD__ -#warning "using fs_utimensat_freebsd.hpp" +#pragma message "using fs_utimensat_freebsd.hpp" #include "fs_utimensat_freebsd.hpp" #else -#warning "using fs_utimensat_generic.hpp" +#pragma message "using fs_utimensat_generic.hpp" #include "fs_utimensat_generic.hpp" #endif diff --git a/src/fuse_statx.cpp b/src/fuse_statx.cpp index 8e92ed19..bd8af7c8 100644 --- a/src/fuse_statx.cpp +++ b/src/fuse_statx.cpp @@ -1,186 +1,9 @@ -#include "fuse_statx.hpp" - -#include "config.hpp" -#include "errno.hpp" -#include "fileinfo.hpp" -#include "fs_inode.hpp" -#include "fs_path.hpp" #include "fs_statx.hpp" -#include "symlinkify.hpp" -#include "ugid.hpp" - -#include "fmt/core.h" - -#include "fuse.h" - -#include - - -static -void -_set_stat_if_leads_to_dir(const std::string &path_, - struct fuse_statx *st_) -{ - int rv; - struct fuse_statx st; - - rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,st_); - if(rv < 0) - return; - - if(S_ISDIR(st.mode)) - *st_ = st; - - return; -} - -static -void -_set_stat_if_leads_to_reg(const std::string &path_, - struct fuse_statx *st_) -{ - int rv; - struct fuse_statx st; - - rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,st_); - if(rv < 0) - return; - - if(S_ISREG(st.mode)) - *st_ = st; - - return; -} - -static -int -_statx_controlfile(struct fuse_statx *st_) -{ - static const uid_t uid = ::getuid(); - static const gid_t gid = ::getgid(); - static const time_t now = ::time(NULL); - - st_->ino = 0; - st_->mode = (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); - st_->nlink = 1; - st_->uid = uid; - st_->gid = gid; - st_->size = 0; - st_->blocks = 0; - st_->atime.tv_sec = now; - st_->mtime.tv_sec = now; - st_->ctime.tv_sec = now; - - return 0; -} - -static -int -_statx(const Policy::Search &searchFunc_, - const Branches &branches_, - const char *fusepath_, - const uint32_t flags_, - const uint32_t mask_, - struct fuse_statx *st_, - const bool symlinkify_, - const time_t symlinkify_timeout_, - FollowSymlinks followsymlinks_) -{ - int rv; - std::string fullpath; - StrVec basepaths; - - rv = searchFunc_(branches_,fusepath_,&basepaths); - if(rv == -1) - return -errno; - - fullpath = fs::path::make(basepaths[0],fusepath_); - - switch(followsymlinks_) - { - case FollowSymlinks::ENUM::NEVER: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - break; - case FollowSymlinks::ENUM::DIRECTORY: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - if((rv >= 0) && S_ISLNK(st_->mode)) - ::_set_stat_if_leads_to_dir(fullpath,st_); - break; - case FollowSymlinks::ENUM::REGULAR: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - if((rv >= 0) && S_ISLNK(st_->mode)) - ::_set_stat_if_leads_to_reg(fullpath,st_); - break; - case FollowSymlinks::ENUM::ALL: - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_FOLLOW,mask_,st_); - if(rv < 0) - rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); - break; - } - - if(rv < 0) - return rv; - - if(symlinkify_ && symlinkify::can_be_symlink(*st_,symlinkify_timeout_)) - symlinkify::convert(fullpath,st_); - - fs::inode::calc(basepaths[0],fusepath_,st_); - - return 0; -} - -static -int -_statx(const char *fusepath_, - const uint32_t flags_, - const uint32_t mask_, - struct fuse_statx *st_, - fuse_timeouts_t *timeout_) -{ - int rv; - Config::Read cfg; - const fuse_context *fc = fuse_get_context(); - const ugid::Set ugid(fc->uid,fc->gid); - - rv = ::_statx(cfg->func.getattr.policy, - cfg->branches, - fusepath_, - flags_, - mask_, - st_, - cfg->symlinkify, - cfg->symlinkify_timeout, - cfg->follow_symlinks); - - timeout_->entry = ((rv >= 0) ? - cfg->cache_entry : - cfg->cache_negative_entry); - timeout_->attr = cfg->cache_attr; - - return rv; -} - -int -FUSE::statx(const char *fusepath_, - const uint32_t flags_, - const uint32_t mask_, - struct fuse_statx *st_, - fuse_timeouts_t *timeout_) -{ - if(fusepath_ == CONTROLFILE) - return ::_statx_controlfile(st_); - - return ::_statx(fusepath_,flags_|AT_STATX_DONT_SYNC,mask_,st_,timeout_); -} - -int -FUSE::statx_fh(const uint64_t fh_, - const uint32_t flags_, - const uint32_t mask_, - struct fuse_statx *st_, - fuse_timeouts_t *timeout_) -{ - FileInfo *fi = reinterpret_cast(fh_); - return ::_statx(fi->fusepath.c_str(),flags_,mask_,st_,timeout_); -} +#ifdef MERGERFS_STATX_SUPPORTED +#pragma message "statx supported" +# include "fuse_statx_supported.icpp" +#else +#pragma message "statx NOT supported" +# include "fuse_statx_unsupported.icpp" +#endif diff --git a/src/fuse_statx_supported.icpp b/src/fuse_statx_supported.icpp new file mode 100644 index 00000000..8e92ed19 --- /dev/null +++ b/src/fuse_statx_supported.icpp @@ -0,0 +1,186 @@ +#include "fuse_statx.hpp" + +#include "config.hpp" +#include "errno.hpp" +#include "fileinfo.hpp" +#include "fs_inode.hpp" +#include "fs_path.hpp" +#include "fs_statx.hpp" +#include "symlinkify.hpp" +#include "ugid.hpp" + +#include "fmt/core.h" + +#include "fuse.h" + +#include + + +static +void +_set_stat_if_leads_to_dir(const std::string &path_, + struct fuse_statx *st_) +{ + int rv; + struct fuse_statx st; + + rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,st_); + if(rv < 0) + return; + + if(S_ISDIR(st.mode)) + *st_ = st; + + return; +} + +static +void +_set_stat_if_leads_to_reg(const std::string &path_, + struct fuse_statx *st_) +{ + int rv; + struct fuse_statx st; + + rv = fs::statx(AT_FDCWD,path_,AT_SYMLINK_FOLLOW,STATX_TYPE,st_); + if(rv < 0) + return; + + if(S_ISREG(st.mode)) + *st_ = st; + + return; +} + +static +int +_statx_controlfile(struct fuse_statx *st_) +{ + static const uid_t uid = ::getuid(); + static const gid_t gid = ::getgid(); + static const time_t now = ::time(NULL); + + st_->ino = 0; + st_->mode = (S_IFREG|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH); + st_->nlink = 1; + st_->uid = uid; + st_->gid = gid; + st_->size = 0; + st_->blocks = 0; + st_->atime.tv_sec = now; + st_->mtime.tv_sec = now; + st_->ctime.tv_sec = now; + + return 0; +} + +static +int +_statx(const Policy::Search &searchFunc_, + const Branches &branches_, + const char *fusepath_, + const uint32_t flags_, + const uint32_t mask_, + struct fuse_statx *st_, + const bool symlinkify_, + const time_t symlinkify_timeout_, + FollowSymlinks followsymlinks_) +{ + int rv; + std::string fullpath; + StrVec basepaths; + + rv = searchFunc_(branches_,fusepath_,&basepaths); + if(rv == -1) + return -errno; + + fullpath = fs::path::make(basepaths[0],fusepath_); + + switch(followsymlinks_) + { + case FollowSymlinks::ENUM::NEVER: + rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + break; + case FollowSymlinks::ENUM::DIRECTORY: + rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + if((rv >= 0) && S_ISLNK(st_->mode)) + ::_set_stat_if_leads_to_dir(fullpath,st_); + break; + case FollowSymlinks::ENUM::REGULAR: + rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + if((rv >= 0) && S_ISLNK(st_->mode)) + ::_set_stat_if_leads_to_reg(fullpath,st_); + break; + case FollowSymlinks::ENUM::ALL: + rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_FOLLOW,mask_,st_); + if(rv < 0) + rv = fs::statx(AT_FDCWD,fullpath,flags_|AT_SYMLINK_NOFOLLOW,mask_,st_); + break; + } + + if(rv < 0) + return rv; + + if(symlinkify_ && symlinkify::can_be_symlink(*st_,symlinkify_timeout_)) + symlinkify::convert(fullpath,st_); + + fs::inode::calc(basepaths[0],fusepath_,st_); + + return 0; +} + +static +int +_statx(const char *fusepath_, + const uint32_t flags_, + const uint32_t mask_, + struct fuse_statx *st_, + fuse_timeouts_t *timeout_) +{ + int rv; + Config::Read cfg; + const fuse_context *fc = fuse_get_context(); + const ugid::Set ugid(fc->uid,fc->gid); + + rv = ::_statx(cfg->func.getattr.policy, + cfg->branches, + fusepath_, + flags_, + mask_, + st_, + cfg->symlinkify, + cfg->symlinkify_timeout, + cfg->follow_symlinks); + + timeout_->entry = ((rv >= 0) ? + cfg->cache_entry : + cfg->cache_negative_entry); + timeout_->attr = cfg->cache_attr; + + return rv; +} + +int +FUSE::statx(const char *fusepath_, + const uint32_t flags_, + const uint32_t mask_, + struct fuse_statx *st_, + fuse_timeouts_t *timeout_) +{ + if(fusepath_ == CONTROLFILE) + return ::_statx_controlfile(st_); + + return ::_statx(fusepath_,flags_|AT_STATX_DONT_SYNC,mask_,st_,timeout_); +} + +int +FUSE::statx_fh(const uint64_t fh_, + const uint32_t flags_, + const uint32_t mask_, + struct fuse_statx *st_, + fuse_timeouts_t *timeout_) +{ + FileInfo *fi = reinterpret_cast(fh_); + + return ::_statx(fi->fusepath.c_str(),flags_,mask_,st_,timeout_); +} diff --git a/src/fuse_statx_unsupported.icpp b/src/fuse_statx_unsupported.icpp new file mode 100644 index 00000000..254f6e48 --- /dev/null +++ b/src/fuse_statx_unsupported.icpp @@ -0,0 +1,21 @@ +#include "fuse_statx.hpp" + +int +FUSE::statx(const char *fusepath_, + const uint32_t flags_, + const uint32_t mask_, + struct fuse_statx *st_, + fuse_timeouts_t *timeout_) +{ + return -ENOSYS; +} + +int +FUSE::statx_fh(const uint64_t fh_, + const uint32_t flags_, + const uint32_t mask_, + struct fuse_statx *st_, + fuse_timeouts_t *timeout_) +{ + return -ENOSYS; +} diff --git a/src/mergerfs.cpp b/src/mergerfs.cpp index bf2c9bc6..8f73bad3 100644 --- a/src/mergerfs.cpp +++ b/src/mergerfs.cpp @@ -269,8 +269,8 @@ namespace l if(uid == 0) return; - char const *s = "mergerfs is not running as root and may not work correctly\n"; - fprintf(stderr,"warning: %s",s); + const char s[] = "mergerfs is not running as root and may not work correctly\n"; + fmt::print(stderr,"warning: {}",s); SysLog::warning(s); } @@ -287,8 +287,6 @@ namespace l SysLog::open(); SysLog::info("mergerfs v{} started",MERGERFS_VERSION); - l::warn_if_not_root(); - memset(&ops,0,sizeof(fuse_operations)); args.argc = argc_; @@ -311,6 +309,8 @@ namespace l return 1; } + l::warn_if_not_root(); + l::setup_resources(cfg->scheduling_priority); l::setup_signal_handlers(); l::get_fuse_operations(ops,cfg->nullrw); diff --git a/src/num.cpp b/src/num.cpp index 7bfaf89b..28c690fb 100644 --- a/src/num.cpp +++ b/src/num.cpp @@ -16,6 +16,8 @@ #include "ef.hpp" +#include "fmt/core.h" + #include #include #include @@ -114,21 +116,17 @@ namespace num std::string humanize(const uint64_t bytes_) { - char buf[64]; - if(bytes_ < KB) - sprintf(buf,"%" PRIu64 "",bytes_); + return fmt::format("{}",bytes_); ef(((bytes_ / TB) * TB) == bytes_) - sprintf(buf,"%" PRIu64 "T",bytes_ / TB); + return fmt::format("{}T",bytes_ / TB); ef(((bytes_ / GB) * GB) == bytes_) - sprintf(buf,"%" PRIu64 "G",bytes_ / GB); + return fmt::format("{}G",bytes_ / GB); ef(((bytes_ / MB) * MB) == bytes_) - sprintf(buf,"%" PRIu64 "M",bytes_ / MB); + return fmt::format("{}M",bytes_ / MB); ef(((bytes_ / KB) * KB) == bytes_) - sprintf(buf,"%" PRIu64 "K",bytes_ / KB); - else - sprintf(buf,"%" PRIu64 "",bytes_); + return fmt::format("{}K",bytes_ / KB); - return std::string(buf); + return fmt::format("{}",bytes_); } } diff --git a/src/policy_eprand.cpp b/src/policy_eprand.cpp index 248249c8..00d1e556 100644 --- a/src/policy_eprand.cpp +++ b/src/policy_eprand.cpp @@ -18,8 +18,8 @@ #include "policies.hpp" #include "policy.hpp" #include "policy_eprand.hpp" +#include "rnd.hpp" -#include int Policy::EPRand::Action::operator()(const Branches::CPtr &branches_, @@ -30,10 +30,7 @@ Policy::EPRand::Action::operator()(const Branches::CPtr &branches_, rv = Policies::Action::epall(branches_,fusepath_,paths_); if(rv == 0) - { - std::random_shuffle(paths_->begin(),paths_->end()); - paths_->resize(1); - } + RND::shrink_to_rand_elem(*paths_); return rv; } @@ -47,10 +44,7 @@ Policy::EPRand::Create::operator()(const Branches::CPtr &branches_, rv = Policies::Create::epall(branches_,fusepath_,paths_); if(rv == 0) - { - std::random_shuffle(paths_->begin(),paths_->end()); - paths_->resize(1); - } + RND::shrink_to_rand_elem(*paths_); return rv; } @@ -64,10 +58,7 @@ Policy::EPRand::Search::operator()(const Branches::CPtr &branches_, rv = Policies::Search::epall(branches_,fusepath_,paths_); if(rv == 0) - { - std::random_shuffle(paths_->begin(),paths_->end()); - paths_->resize(1); - } + RND::shrink_to_rand_elem(*paths_); return rv; } diff --git a/src/policy_rand.cpp b/src/policy_rand.cpp index 9a74f017..f1aa12b4 100644 --- a/src/policy_rand.cpp +++ b/src/policy_rand.cpp @@ -18,8 +18,7 @@ #include "policy.hpp" #include "policy_rand.hpp" #include "policies.hpp" - -#include +#include "rnd.hpp" int Policy::Rand::Action::operator()(const Branches::CPtr &branches_, @@ -30,10 +29,7 @@ Policy::Rand::Action::operator()(const Branches::CPtr &branches_, rv = Policies::Action::all(branches_,fusepath_,paths_); if(rv == 0) - { - std::random_shuffle(paths_->begin(),paths_->end()); - paths_->resize(1); - } + RND::shrink_to_rand_elem(*paths_); return rv; } @@ -47,10 +43,7 @@ Policy::Rand::Create::operator()(const Branches::CPtr &branches_, rv = Policies::Create::all(branches_,fusepath_,paths_); if(rv == 0) - { - std::random_shuffle(paths_->begin(),paths_->end()); - paths_->resize(1); - } + RND::shrink_to_rand_elem(*paths_); return rv; } @@ -64,10 +57,7 @@ Policy::Rand::Search::operator()(const Branches::CPtr &branches_, rv = Policies::Search::all(branches_,fusepath_,paths_); if(rv == 0) - { - std::random_shuffle(paths_->begin(),paths_->end()); - paths_->resize(1); - } + RND::shrink_to_rand_elem(*paths_); return rv; } diff --git a/src/procfs_get_name.cpp b/src/procfs_get_name.cpp index c2f42225..bea4ca7b 100644 --- a/src/procfs_get_name.cpp +++ b/src/procfs_get_name.cpp @@ -10,10 +10,14 @@ #include "fs_openat.hpp" #include "fs_read.hpp" +#include "fmt/core.h" + +#include + #include static int g_PROCFS_DIR_FD = -1; -const char PROCFS_PATH[] = "/proc"; +constexpr const char PROCFS_PATH[] = "/proc"; int procfs::init() @@ -33,15 +37,17 @@ procfs::get_name(const int tid_) { int fd; int rv; - char commpath[256]; + std::array commpath; + fmt::format_to_n_result frv; - snprintf(commpath,sizeof(commpath),"%d/comm",tid_); + frv = fmt::format_to_n(commpath.data(),commpath.size()-1,"{}/comm",tid_); + frv.out[0] = '\0'; - fd = fs::openat(g_PROCFS_DIR_FD,commpath,O_RDONLY); + fd = fs::openat(g_PROCFS_DIR_FD,commpath.data(),O_RDONLY); if(fd < 0) return {}; - rv = fs::read(fd,commpath,sizeof(commpath)); + rv = fs::read(fd,commpath.data(),commpath.size()); if(rv == -1) return {}; @@ -50,5 +56,5 @@ procfs::get_name(const int tid_) fs::close(fd); - return commpath; + return commpath.data(); } diff --git a/src/rnd.hpp b/src/rnd.hpp index e9b300d0..0cc38e48 100644 --- a/src/rnd.hpp +++ b/src/rnd.hpp @@ -19,7 +19,7 @@ #pragma once #include - +#include class RND { @@ -31,4 +31,21 @@ public: static uint64_t rand64(const uint64_t max_); static uint64_t rand64(const uint64_t min_, const uint64_t max_); + + template + static + void + shrink_to_rand_elem(std::vector &v_) + { + static std::mt19937 gen(std::random_device{}()); + + if(v_.size() <= 1) + return; + + std::uniform_int_distribution<> dist(0,(v_.size() - 1)); + + std::swap(v_[0],v_[dist(gen)]); + + v_.resize(1); + } }; diff --git a/src/to_string.cpp b/src/to_string.cpp index c35be48e..c16cd093 100644 --- a/src/to_string.cpp +++ b/src/to_string.cpp @@ -18,6 +18,8 @@ #include "to_string.hpp" +#include "fmt/core.h" + #include #include @@ -35,21 +37,13 @@ namespace str std::string to(const int int_) { - char buf[24]; - - sprintf(buf,"%d",int_); - - return buf; + return fmt::format("{}",int_); } std::string to(const uint64_t uint64_) { - char buf[64]; - - sprintf(buf,"%llu",(unsigned long long)uint64_); - - return buf; + return fmt::format("{}",uint64_); } std::string diff --git a/src/tofrom_wrapper.hpp b/src/tofrom_wrapper.hpp index 9b04111c..99525146 100644 --- a/src/tofrom_wrapper.hpp +++ b/src/tofrom_wrapper.hpp @@ -41,11 +41,11 @@ public: } public: - ToFromWrapper() + ToFromWrapper() { } - ToFromWrapper(const T data_) + ToFromWrapper(const T data_) : _data(data_) { } @@ -112,11 +112,11 @@ public: } public: - ROToFromWrapper() + ROToFromWrapper() { } - ROToFromWrapper(const T data_) + ROToFromWrapper(const T data_) : _data(data_) { } diff --git a/src/ugid.hpp b/src/ugid.hpp index 620e8b45..8adf47bb 100644 --- a/src/ugid.hpp +++ b/src/ugid.hpp @@ -28,9 +28,9 @@ namespace ugid } #if defined __linux__ and UGID_USE_RWLOCK == 0 -#warning "using ugid_linux.hpp" +#pragma message "using ugid_linux.hpp" #include "ugid_linux.hpp" #else -#warning "using ugid_rwlock.hpp" +#pragma message "using ugid_rwlock.hpp" #include "ugid_rwlock.hpp" #endif