mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-19 16:16:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e87bcbff2 | ||
|
|
fcd7527c9d | ||
|
|
83d1630f55 | ||
|
|
cfb925e5ae | ||
|
|
515b0549ef | ||
|
|
8ea47df13e | ||
|
|
42ab831d97 | ||
|
|
89b4046d11 | ||
|
|
7ec6f8e2f2 | ||
|
|
5f7fa17ad8 | ||
|
|
30d59d4458 | ||
|
|
02dc36080c | ||
|
|
8734847968 | ||
|
|
608cd00764 | ||
|
|
b13727abde | ||
|
|
231aac15cd | ||
|
|
8b17d5f35e | ||
|
|
8608b2be46 | ||
|
|
1e0bd54989 | ||
|
|
56285fc6e5 | ||
|
|
dfe053a0ae | ||
|
|
12ac368e8b | ||
|
|
c8f32f9f25 | ||
|
|
ce3737ff33 | ||
|
|
84e4cdeb7e | ||
|
|
6d6d73c7b3 | ||
|
|
bea31543a0 | ||
|
|
99835a0178 | ||
|
|
342a1efcfb | ||
|
|
c587a127fb | ||
|
|
f1034a3ad6 | ||
|
|
8117998205 | ||
|
|
cc780bc521 | ||
|
|
14b7c8e607 | ||
|
|
3ff9794fff | ||
|
|
f71e27644e | ||
|
|
943f91ff98 | ||
|
|
ef1e01b5a5 | ||
|
|
50c0627b2b | ||
|
|
660f556ac6 | ||
|
|
997b036064 | ||
|
|
9308a3729c | ||
|
|
7616d8b1ac | ||
|
|
a86092b3c2 | ||
|
|
e613d9415f | ||
|
|
1a8eb326af | ||
|
|
9fb1642e93 | ||
|
|
8be86ee611 | ||
|
|
da68cf3a85 | ||
|
|
ec40866091 | ||
|
|
bd0714272e | ||
|
|
27acd9fcc7 | ||
|
|
4516685ca7 | ||
|
|
0bdbaf1d6c | ||
|
|
e67b7d0825 | ||
|
|
981d5473ee | ||
|
|
364f8c4407 | ||
|
|
ef9c7ad5df | ||
|
|
4288a60c5d | ||
|
|
cf38a37176 | ||
|
|
f5d9a60b9d | ||
|
|
0c17492c78 | ||
|
|
98648ae44b | ||
|
|
0dcca0f1a3 | ||
|
|
103cd358f2 | ||
|
|
aa46001923 | ||
|
|
ff48ff6047 | ||
|
|
d5a2187713 | ||
|
|
351adc2b20 | ||
|
|
a1e681f36b | ||
|
|
e3a467de80 | ||
|
|
cdadda5eb9 | ||
|
|
4c9a9d7e15 | ||
|
|
425e30b50f | ||
|
|
3905355522 | ||
|
|
9e2e1afafa | ||
|
|
6d218fa87f | ||
|
|
3b10406f95 | ||
|
|
f2c8e5d9eb | ||
|
|
b581c5d5e7 | ||
|
|
1ebc8cfa5f | ||
|
|
acfa797503 | ||
|
|
7fc20457ca | ||
|
|
ecee84b323 | ||
|
|
b8776d205d | ||
|
|
06433a0548 | ||
|
|
f4b602340b | ||
|
|
a78aef36e0 | ||
|
|
0d05523dfd | ||
|
|
0cfab60f37 | ||
|
|
21966ade97 | ||
|
|
96eb51eecb | ||
|
|
881147daa2 | ||
|
|
0f1a8e9b69 | ||
|
|
08f9cc6932 | ||
|
|
3099b15db1 | ||
|
|
07b1e6bdba | ||
|
|
7763c7384a | ||
|
|
61365d226d | ||
|
|
f33e78bc24 | ||
|
|
7f52623536 | ||
|
|
a30a4df9c0 | ||
|
|
1e0d5aa91d | ||
|
|
d09fadbc28 | ||
|
|
21ce889449 | ||
|
|
9d1a4696e9 | ||
|
|
ed0cbcf064 | ||
|
|
076e359891 | ||
|
|
bf98d3f3a6 | ||
|
|
c45c5ce920 | ||
|
|
e0a892f0d0 | ||
|
|
164b7f73c9 | ||
|
|
3a45be4cd8 | ||
|
|
107a3acf95 | ||
|
|
a5eafb8972 | ||
|
|
8199cc8a8b | ||
|
|
e5d897f3dc | ||
|
|
c0af09f7d8 | ||
|
|
1b1e14ca29 | ||
|
|
82b89aa099 | ||
|
|
aa91e1b2ba | ||
|
|
82e81afcdc | ||
|
|
1a5c39f813 | ||
|
|
fce1803d42 | ||
|
|
5a9a17afcb | ||
|
|
8648de6bb6 | ||
|
|
f227a63e15 | ||
|
|
aa47b74dea | ||
|
|
206ad1dd53 | ||
|
|
80aead7085 | ||
|
|
1220befacf | ||
|
|
01aa888c7a | ||
|
|
6a6aa054cd | ||
|
|
04939bbe72 | ||
|
|
2ea6290e4b | ||
|
|
c66c68b137 | ||
|
|
075a8a6548 | ||
|
|
3c6d1b47f6 | ||
|
|
6f281b9f7f | ||
|
|
511bf47af9 | ||
|
|
53c5a2cf1e | ||
|
|
eb8074ed9a | ||
|
|
38c19df95a | ||
|
|
b1228f11bd | ||
|
|
cfafafd7ff | ||
|
|
c8ce82ce54 | ||
|
|
05c3d97368 | ||
|
|
2c87e68635 | ||
|
|
8f1ef59770 | ||
|
|
a460c52861 | ||
|
|
a4aade6666 | ||
|
|
1d1bcd3b87 | ||
|
|
099ce2d91d | ||
|
|
56cd831ec8 | ||
|
|
e43be3c221 | ||
|
|
2754536727 | ||
|
|
a0cff637e1 | ||
|
|
a5a6c892b9 | ||
|
|
3d50d4a52f | ||
|
|
f69c31fd77 | ||
|
|
a2860c3f1f | ||
|
|
3f70e66ee3 | ||
|
|
160d3c832d | ||
|
|
15bcc47d6a | ||
|
|
e55bb93890 | ||
|
|
5a2a9e4530 | ||
|
|
b4239495a0 | ||
|
|
3222de6c9c | ||
|
|
be4d388813 | ||
|
|
a47842e2f8 | ||
|
|
f769d7474b | ||
|
|
2ae57642f4 | ||
|
|
7f1b7ac343 | ||
|
|
1290ab9708 | ||
|
|
965e26989c | ||
|
|
0caae4fbf0 | ||
|
|
4d1760cb69 | ||
|
|
22df87c86a | ||
|
|
72fb6e4aba | ||
|
|
9b046d9a34 | ||
|
|
d8918458bf | ||
|
|
d3a3f65e7b | ||
|
|
411d181473 | ||
|
|
caf7915003 | ||
|
|
4c5473d569 | ||
|
|
104f378c2c | ||
|
|
a364405cf0 | ||
|
|
960f5fdbba | ||
|
|
6813a51fce | ||
|
|
f7e86d90ce | ||
|
|
66d120c721 | ||
|
|
01973214bf | ||
|
|
af1edadf72 | ||
|
|
3bacaa6f7f | ||
|
|
b6e1a08aef | ||
|
|
47bd4259b8 | ||
|
|
dc486e79ce | ||
|
|
78551049d5 | ||
|
|
4aab008233 | ||
|
|
3a7a4a13e7 | ||
|
|
41b65ae9e8 | ||
|
|
2412ae7316 | ||
|
|
ec16d07fba | ||
|
|
37f10888a9 | ||
|
|
0db8a1ed6a | ||
|
|
cfaad60275 | ||
|
|
077484323c | ||
|
|
700fcfccf5 | ||
|
|
4b4801add6 | ||
|
|
741128cb23 | ||
|
|
3bee0f0b57 | ||
|
|
727c522fb8 | ||
|
|
00e5467e99 | ||
|
|
0371f03def | ||
|
|
d18c273293 | ||
|
|
0f24bb29ac | ||
|
|
3cb35a3a65 | ||
|
|
25b0bc72df | ||
|
|
e6fcebe4f3 | ||
|
|
46f85c0f6d | ||
|
|
7a9c3b7eb2 | ||
|
|
569a151da2 | ||
|
|
2f3e807e9d | ||
|
|
95ab37fdd2 | ||
|
|
1707dc27e3 | ||
|
|
c4126a0796 | ||
|
|
6acef3a638 | ||
|
|
b3656a0243 | ||
|
|
41b40b94fa | ||
|
|
ca59ae53a5 | ||
|
|
8c649d33ee | ||
|
|
f64ff9ca1e | ||
|
|
99c28ad3bc | ||
|
|
5becb334cd |
-150
@@ -1,150 +0,0 @@
|
||||
linux_task:
|
||||
name: "alpine:latest"
|
||||
container:
|
||||
image: alpine:latest
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- make STATIC=1 LTO=1
|
||||
|
||||
linux_task:
|
||||
name: "alpine-arm:latest"
|
||||
arm_container:
|
||||
image: alpine:latest
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- make STATIC=1 LTO=1
|
||||
|
||||
linux_task:
|
||||
name: "fedora:41"
|
||||
container:
|
||||
image: fedora:41
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- make
|
||||
- make rpm
|
||||
|
||||
linux_task:
|
||||
name: "fedora-arm:41"
|
||||
arm_container:
|
||||
image: fedora:41
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- make
|
||||
- make rpm
|
||||
|
||||
linux_task:
|
||||
name: "ubuntu:22.04"
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
|
||||
linux_task:
|
||||
name: "ubuntu:24.04"
|
||||
container:
|
||||
image: ubuntu:24.04
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
|
||||
linux_task:
|
||||
name: "ubuntu:latest"
|
||||
container:
|
||||
image: ubuntu:latest
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
|
||||
linux_task:
|
||||
name: "ubuntu-arm:latest"
|
||||
arm_container:
|
||||
image: ubuntu:latest
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
|
||||
linux_task:
|
||||
name: "debian:12"
|
||||
container:
|
||||
image: debian:12
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
|
||||
linux_task:
|
||||
name: "debian:latest"
|
||||
container:
|
||||
image: debian:latest
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
|
||||
linux_task:
|
||||
name: "debian-arm:latest"
|
||||
arm_container:
|
||||
image: debian:latest
|
||||
cpu: 4
|
||||
memory: 4G
|
||||
timeout_in: 15m
|
||||
script:
|
||||
- buildtools/install-build-pkgs
|
||||
- git fetch
|
||||
- make deb
|
||||
- apt-get -y install fuse
|
||||
- dpkg -i ../*.deb
|
||||
- mergerfs -v || true
|
||||
@@ -7,22 +7,31 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
See also https://trapexit.github.io/mergerfs/latest/support
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
A clear and concise description of the unexpected behavior.
|
||||
|
||||
**Please be sure to use latest release of mergerfs to ensure the issue still exists. Not your distro's latest but the latest official release.**
|
||||
**Please be sure to use latest release of mergerfs to ensure the issue
|
||||
still exists. Not your distro's latest but the latest official
|
||||
release. Support will NOT be provided for old versions.**
|
||||
|
||||
The master branch is **not** to be considered production ready. Feel free to file bug reports but do so indicating clearly that you are testing unreleased code.
|
||||
The `master` branch is **not** to be considered production ready. Feel
|
||||
free to file bug reports but do so indicating clearly that you are
|
||||
testing unreleased code.
|
||||
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
Steps to reproduce the behavior. List **all** steps to reproduce. **All** settings.
|
||||
Provide steps to reproduce the behavior. List **all** steps to
|
||||
reproduce. **All** settings. The version of all software involved.
|
||||
|
||||
Please simplify the reproduction as much as possible.
|
||||
- Unless it is dependenat on multiple branches, use a single branch
|
||||
- Reproduce with standard tooling if possible (touch,truncate,rm,rmdir,ln,etc.) Having to install 3rd party software will make debugging more difficult.
|
||||
* Unless it is dependenat on multiple branches, use a single branch
|
||||
* Reproduce with standard tooling if possible
|
||||
(touch,truncate,rm,rmdir,ln,etc.) Having to install 3rd party
|
||||
software will make debugging more difficult.
|
||||
|
||||
|
||||
**Expected behavior**
|
||||
@@ -30,19 +39,38 @@ Please simplify the reproduction as much as possible.
|
||||
A clear and concise description of the expected behavior.
|
||||
|
||||
|
||||
**Workarounds already tested**
|
||||
|
||||
Please list all workarounds attempted and why they were ruled out as
|
||||
options.
|
||||
|
||||
|
||||
**System information:**
|
||||
|
||||
- OS, kernel version: `uname -a`
|
||||
- mergerfs version: `mergerfs -V`
|
||||
- mergerfs settings
|
||||
- List of drives, filesystems, & sizes:
|
||||
- `df -h`
|
||||
- `lsblk -f`
|
||||
- A strace of the application having a problem:
|
||||
- `strace -fvTtt -s 256 -o /tmp/app.strace.txt <cmd>`
|
||||
- `strace -fvTtt -s 256 -o /tmp/app.strace.txt -p <appPID>`
|
||||
- strace of mergerfs while app tried to do it's thing:
|
||||
- `strace -fvTtt -s 256 -p <mergerfsPID> -o /tmp/mergerfs.strace.txt`
|
||||
* Attach output from
|
||||
[mergerfs.collect-info](https://trapexit.github.io/mergerfs/latest/tooling/#mergerfscollect-info)
|
||||
tool.
|
||||
* Alternatively:
|
||||
* OS: `lsb_release -a`
|
||||
* kernel version: `uname -a`
|
||||
* mergerfs version: `mergerfs --version`
|
||||
* mergerfs settings / arguments: from fstab, systemd unit, command
|
||||
line, OMV plugin, etc.
|
||||
* List of drives, filesystems, & sizes:
|
||||
* `df -h`
|
||||
* `lsblk -o NAME,FSTYPE,FSSIZE,SIZE,MOUNTPOINTS,RM,RO,ROTA`
|
||||
* All information about the relevant paths and files: permissions,
|
||||
ownership, etc.
|
||||
* All information about the application making the requests:
|
||||
version, uid/gid
|
||||
* Is mergerfs running within a container or used with container
|
||||
technologies?
|
||||
* Are the client apps using mergerfs running in a container?
|
||||
* A strace of the application having a problem:
|
||||
* `strace -fvTtt -s 256 -o /tmp/app.strace.txt <cmd>`
|
||||
* `strace -fvTtt -s 256 -o /tmp/app.strace.txt -p <appPID>`
|
||||
* strace of mergerfs while app tried to do it's thing:
|
||||
* `strace -fvTtt -s 256 -p <mergerfsPID> -o /tmp/mergerfs.strace.txt`
|
||||
|
||||
**Additional context**
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: "7 3 * * 6"
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ python, cpp ]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
if: ${{ matrix.language == 'python' || matrix.language == 'cpp' }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
@@ -1,49 +0,0 @@
|
||||
name: "mkdocs"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- name: Configure Git Credentials
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./mkdocs
|
||||
run: |
|
||||
pip install --no-cache-dir mkdocs mkdocs-material pymdown-extensions mike
|
||||
|
||||
- name: Deploy docs
|
||||
working-directory: ./mkdocs
|
||||
env:
|
||||
REF_TYPE: ${{ github.ref_type }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ "$REF_TYPE" = "tag" ]; then
|
||||
mike deploy "$REF_NAME" "latest" --allow-empty --ignore-remote-status --update-aliases
|
||||
mike set-default "latest"
|
||||
#git checkout gh-pages
|
||||
#git push -f origin gh-pages
|
||||
elif [ "$REF_TYPE" = "branch" ] && [ "$REF_NAME" = "master" ]; then
|
||||
mike deploy "master" --ignore-remote-status --allow-empty
|
||||
#git checkout gh-pages
|
||||
#git push -f origin gh-pages
|
||||
fi
|
||||
@@ -9,6 +9,7 @@
|
||||
* scope_guard: https://github.com/Neargye/scope_guard
|
||||
* subprocess: https://github.com/arun11299/cpp-subprocess
|
||||
* CLI11: https://github.com/CLIUtils/CLI11
|
||||
* string-lite: https://github.com/martinmoene/string-lite
|
||||
* boost
|
||||
* download boost
|
||||
* ./bootstrap.sh
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2025, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
Copyright (c) 2026, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
$(info mergerfs MAKEFLAGS: $(MAKEFLAGS))
|
||||
|
||||
ifeq ($(shell id -u),0)
|
||||
FAKEROOT ?=
|
||||
endif
|
||||
@@ -27,6 +29,8 @@ MKDIR ?= mkdir
|
||||
MKTEMP ?= mktemp
|
||||
MV ?= mv
|
||||
RM ?= rm
|
||||
RPMBUILD ?= rpmbuild
|
||||
RPMBUILD_FLAGS ?=
|
||||
SED ?= sed
|
||||
STRIP ?= strip
|
||||
TAR ?= tar
|
||||
@@ -35,6 +39,8 @@ TOUCH ?= touch
|
||||
BUILDDIR := build
|
||||
GITHUB_REPO ?= "https://github.com/trapexit/mergerfs"
|
||||
|
||||
DEFAULT_TARGET := debian:13.amd64
|
||||
|
||||
ifndef GIT_REPO
|
||||
ifneq ($(shell $(GIT) --version 2> /dev/null),)
|
||||
ifeq ($(shell test -e .git; echo $$?),0)
|
||||
@@ -44,12 +50,30 @@ endif
|
||||
endif
|
||||
|
||||
USE_XATTR ?= 1
|
||||
UGID_USE_RWLOCK ?= 0
|
||||
|
||||
ifdef NDEBUG
|
||||
OPT_FLAGS := -O2 -DNDEBUG
|
||||
ifdef SANITIZE
|
||||
ifeq ($(SANITIZE),1)
|
||||
override SANITIZE := -fsanitize=address,undefined,leak
|
||||
else ifeq ($(filter -fsanitize=%,$(SANITIZE)),)
|
||||
override SANITIZE := -fsanitize=$(SANITIZE)
|
||||
endif
|
||||
endif
|
||||
|
||||
COMMON_OPT_FLAGS += \
|
||||
-fstack-protector-strong \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter
|
||||
|
||||
DEBUG_OPT_FLAGS += \
|
||||
-O0 \
|
||||
-g3 \
|
||||
-fno-omit-frame-pointer \
|
||||
$(SANITIZE)
|
||||
|
||||
ifdef RELEASE
|
||||
OPT_FLAGS := -O3 $(COMMON_OPT_FLAGS)
|
||||
else
|
||||
OPT_FLAGS := -O0 -g -fno-omit-frame-pointer -DDEBUG
|
||||
OPT_FLAGS ?= $(DEBUG_OPT_FLAGS) $(COMMON_OPT_FLAGS)
|
||||
endif
|
||||
|
||||
ifdef STATIC
|
||||
@@ -69,7 +93,7 @@ OBJS := $(SRC:src/%.cpp=build/.objs/%.cpp.o)
|
||||
DEPS := $(SRC:src/%.cpp=build/.objs/%.cpp.d)
|
||||
|
||||
TESTS := $(wildcard tests/*.cpp)
|
||||
TESTS_OBJS := $(filter-out build/.objs/mergerfs.o,$(OBJS))
|
||||
TESTS_OBJS := $(filter-out build/.objs/mergerfs.cpp.o,$(OBJS))
|
||||
TESTS_OBJS += $(TESTS:tests/%.cpp=build/.test_objs/%.cpp.o)
|
||||
TESTS_DEPS := $(TESTS:tests/%.cpp=build/.test_objs/%.cpp.d)
|
||||
TESTS_DEPS += $(DEPS)
|
||||
@@ -77,7 +101,8 @@ TESTS_DEPS += $(DEPS)
|
||||
MANPAGE := mergerfs.1
|
||||
CPPFLAGS ?=
|
||||
override CPPFLAGS += \
|
||||
-D_FILE_OFFSET_BITS=64
|
||||
-D_FILE_OFFSET_BITS=64 \
|
||||
-DBOOST_UNORDERED_DISABLE_PARALLEL_ALGORITHMS
|
||||
CFLAGS ?= \
|
||||
$(OPT_FLAGS) \
|
||||
-Wall \
|
||||
@@ -98,28 +123,34 @@ CXXFLAGS ?= \
|
||||
-Wno-unused-result \
|
||||
-pipe
|
||||
override CXXFLAGS += \
|
||||
-std=c++17 \
|
||||
-std=c++20 \
|
||||
-MMD \
|
||||
-MP
|
||||
override INC_FLAGS := \
|
||||
-Isrc \
|
||||
-Ilibfuse/include
|
||||
-Ivendored \
|
||||
-Ivendored/libfuse/include
|
||||
override MFS_FLAGS := \
|
||||
-DUSE_XATTR=$(USE_XATTR) \
|
||||
-DUGID_USE_RWLOCK=$(UGID_USE_RWLOCK)
|
||||
-DUSE_XATTR=$(USE_XATTR)
|
||||
override TESTS_FLAGS := \
|
||||
-Isrc \
|
||||
-Ivendored \
|
||||
-Ivendored/acutest \
|
||||
-DTESTS
|
||||
|
||||
LIBFUSE := libfuse/$(BUILDDIR)/libfuse.a
|
||||
LIBFUSE := vendored/libfuse/$(BUILDDIR)/libfuse.a
|
||||
LDFLAGS ?=
|
||||
LDLIBS := \
|
||||
-lrt \
|
||||
-latomic \
|
||||
-pthread \
|
||||
-lstdc++fs
|
||||
-pthread
|
||||
override LDLIBS += \
|
||||
$(LIBFUSE)
|
||||
ifeq ($(CXX),g++)
|
||||
GCC_VERSION := $(shell $(CXX) -dumpversion | cut -f1 -d.)
|
||||
ifeq ($(shell test $(GCC_VERSION) -lt 9; echo $$?),0)
|
||||
override LDLIBS += -lstdc++fs
|
||||
endif
|
||||
endif
|
||||
|
||||
# https://www.gnu.org/prep/standards/html_node/Directory-Variables.html
|
||||
DESTDIR ?=
|
||||
@@ -148,6 +179,8 @@ help:
|
||||
@echo "USE_XATTR=0 - build program without xattrs functionality"
|
||||
@echo "STATIC=1 - build static binary"
|
||||
@echo "LTO=1 - build with link time optimization"
|
||||
@echo "SANITIZE=1 - build with sanitizers (address,undefined,leak)"
|
||||
@echo "SANITIZE=list - build with custom sanitizers (comma separated)"
|
||||
@echo "CLEANUP=1 - cleanup images between release build process"
|
||||
@echo "PKGDIR=/dir/ - location for release build pkgs"
|
||||
@echo "GITREF=gitref - gitref to use for release builds"
|
||||
@@ -156,19 +189,21 @@ help:
|
||||
$(BUILDDIR)/mergerfs: $(LIBFUSE) src/version.hpp $(OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
$(BUILDDIR)/fsck.mergerfs:
|
||||
$(BUILDDIR)/fsck.mergerfs: $(BUILDDIR)/mergerfs
|
||||
$(LN) -sf "mergerfs" $@
|
||||
|
||||
$(BUILDDIR)/mergerfs.collect-info:
|
||||
$(BUILDDIR)/mergerfs.collect-info: $(BUILDDIR)/mergerfs
|
||||
$(LN) -sf "mergerfs" $@
|
||||
|
||||
$(BUILDDIR)/tests: $(BUILDDIR)/mergerfs $(TESTS_OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(TESTS_OBJS) -o $@ $(LDFLAGS) $(LDLIBS)
|
||||
|
||||
.PHONY: libfuse
|
||||
libfuse: $(LIBFUSE)
|
||||
$(MAKE) -C vendored/libfuse
|
||||
|
||||
$(LIBFUSE):
|
||||
libfuse:
|
||||
$(MAKE) NDEBUG=$(NDEBUG) -C libfuse
|
||||
$(MAKE) -C vendored/libfuse $(BUILDDIR)/libfuse.a RELEASE=$(RELEASE)
|
||||
|
||||
tests: $(BUILDDIR)/tests
|
||||
|
||||
@@ -194,7 +229,7 @@ $(BUILDDIR)/stamp:
|
||||
$(BUILDDIR)/.objs/%.cpp.o: src/%.cpp $(BUILDDIR)/stamp
|
||||
$(CXX) $(CXXFLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/.test_objs/%.cpp.o: tests/%.cpp
|
||||
$(BUILDDIR)/.test_objs/%.cpp.o: tests/%.cpp | $(BUILDDIR)/stamp
|
||||
$(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(INC_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/preload.so: $(BUILDDIR)/stamp tools/preload.c
|
||||
@@ -206,7 +241,7 @@ preload: $(BUILDDIR)/preload.so
|
||||
clean: rpm-clean
|
||||
$(RM) -rf $(BUILDDIR)
|
||||
$(FIND) . -name "*~" -delete
|
||||
$(MAKE) -C libfuse clean
|
||||
$(MAKE) -C vendored/libfuse clean
|
||||
|
||||
.PHONY: distclean
|
||||
distclean: clean
|
||||
@@ -227,7 +262,7 @@ install-base: all
|
||||
.PHONY: install-mount-tools
|
||||
install-mount-tools: install-base
|
||||
$(MKDIR) -p "$(INSTALLBINDIR)"
|
||||
$(MAKE) -C libfuse install
|
||||
$(MAKE) -C vendored/libfuse install
|
||||
|
||||
.PHONY: install-man
|
||||
install-man: man/$(MANPAGE)
|
||||
@@ -244,10 +279,12 @@ install-strip: install-base
|
||||
$(STRIP) "$(INSTALLBINDIR)/mergerfs"
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall: uninstall-base uninstall-mount.mergerfs uninstall-man
|
||||
uninstall: uninstall-base uninstall-mount.mergerfs uninstall-preload uninstall-man
|
||||
|
||||
uninstall-base:
|
||||
$(RM) -f "$(INSTALLBINDIR)/mergerfs"
|
||||
$(RM) -f "$(INSTALLBINDIR)/fsck.mergerfs"
|
||||
$(RM) -f "$(INSTALLBINDIR)/mergerfs.collect-info"
|
||||
|
||||
uninstall-mount.mergerfs:
|
||||
$(RM) -f "$(INSTALLBINDIR)/mount.mergerfs"
|
||||
@@ -255,6 +292,9 @@ uninstall-mount.mergerfs:
|
||||
uninstall-man:
|
||||
$(RM) -f "$(INSTALLMAN1DIR)/$(MANPAGE)"
|
||||
|
||||
uninstall-preload:
|
||||
$(RM) -f "$(INSTALLLIBDIR)/preload.so"
|
||||
|
||||
.PHONY: tarball
|
||||
tarball: changelog version
|
||||
$(eval VERSION := $(shell cat VERSION))
|
||||
@@ -272,6 +312,10 @@ ifdef GIT_REPO
|
||||
$(GIT2DEBCL) --name mergerfs > debian/changelog
|
||||
else
|
||||
$(CP) -v ChangeLog debian/changelog
|
||||
$(eval DISTRO := $(shell lsb_release -i -s | tr '[:upper:]' '[:lower:]'))
|
||||
$(eval CODENAME := $(shell lsb_release -c -s | tr '[:upper:]' '[:lower:]'))
|
||||
sed -i 's/~unknown-unknown/~$(DISTRO)-$(CODENAME)/g' debian/changelog
|
||||
sed -i 's/ unknown; / $(CODENAME); /g' debian/changelog
|
||||
endif
|
||||
|
||||
.PHONY: signed-deb
|
||||
@@ -301,7 +345,7 @@ rpm: tarball
|
||||
$(SED) 's/__VERSION__/$(VERSION)/g' mergerfs.spec > \
|
||||
rpmbuild/SOURCES/mergerfs.spec
|
||||
cp -ar mergerfs-$(VERSION).tar.gz rpmbuild/SOURCES
|
||||
rpmbuild -ba rpmbuild/SOURCES/mergerfs.spec \
|
||||
$(RPMBUILD) $(RPMBUILD_FLAGS) -ba rpmbuild/SOURCES/mergerfs.spec \
|
||||
--define "_topdir $(CURDIR)/rpmbuild"
|
||||
|
||||
.PHONY: install-build-pkgs
|
||||
@@ -310,10 +354,11 @@ install-build-pkgs:
|
||||
|
||||
.PHONY: install-build-tools
|
||||
install-build-tools:
|
||||
./bulidtools/install-build-tools
|
||||
./buildtools/install-build-tools
|
||||
|
||||
define build_release
|
||||
$(eval GITREF ?= $(shell git describe --exact-match --tags HEAD 2>/dev/null || git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD))
|
||||
@echo "GITREF=$(GITREF)"
|
||||
./buildtools/build-release \
|
||||
--target=$(1) \
|
||||
$(if $(CLEANUP),--cleanup) \
|
||||
@@ -322,13 +367,16 @@ define build_release
|
||||
--branch=$(GITREF)
|
||||
endef
|
||||
|
||||
.PHONY: release
|
||||
.PHONY: release release-all
|
||||
.PHONY: release-amd64 release-arm64 release-armhf release-riscv64
|
||||
.PHONY: release-sample release-static release-tarball
|
||||
.PHONY: release-sample release-static release-tarball release-installer
|
||||
release:
|
||||
$(MAKE) RELEASE=1 CPPFLAGS="$(CPPFLAGS) -DNDEBUG" all
|
||||
release-all:
|
||||
$(call build_release,"all")
|
||||
release-sample:
|
||||
$(call build_release,"debian:13.amd64")
|
||||
$(if $(TARGET),,$(eval TARGET := $(shell ./buildtools/detect-local-target 2>/dev/null)))
|
||||
$(call build_release,$(if $(TARGET),$(TARGET),$(DEFAULT_TARGET)))
|
||||
release-amd64:
|
||||
$(call build_release,"amd64")
|
||||
release-arm64:
|
||||
@@ -336,11 +384,18 @@ release-arm64:
|
||||
release-armhf:
|
||||
$(call build_release,"armhf")
|
||||
release-riscv64:
|
||||
$(call build_release,"risc64")
|
||||
$(call build_release,"riscv64")
|
||||
release-static:
|
||||
$(call build_release,"static")
|
||||
release-tarball:
|
||||
$(call build_release,"tarball")
|
||||
release-installer:
|
||||
$(call build_release,"installer")
|
||||
|
||||
container:
|
||||
$(eval GITREF ?= $(shell git describe --exact-match --tags HEAD 2>/dev/null || git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD))
|
||||
@echo "GITREF=$(GITREF)"
|
||||
./buildtools/build-containerimage "$(GITREF)"
|
||||
|
||||
.PHONY: tags
|
||||
tags:
|
||||
|
||||
@@ -25,6 +25,7 @@ etc.](https://trapexit.github.io/mergerfs/latest/project_comparisons/)
|
||||
* Works with filesystems of [almost any
|
||||
type](https://trapexit.github.io/mergerfs/latest/faq/compatibility_and_integration/#what-filesystems-can-be-used-as-branches)
|
||||
* Ignore read-only filesystems when creating files
|
||||
* [Hard links](https://trapexit.github.io/mergerfs/latest/faq/technical_behavior_and_limitations/#do-hard-links-work)
|
||||
* Hard link [copy-on-write /
|
||||
CoW](https://trapexit.github.io/mergerfs/latest/config/link_cow/)
|
||||
* [Runtime configurable](https://trapexit.github.io/mergerfs/latest/runtime_interface/)
|
||||
@@ -59,8 +60,10 @@ which branch will be selected for the creation.
|
||||
For functions which change attributes or remove the file the behavior
|
||||
may be applied to all instances found.
|
||||
|
||||
Read more about [policies
|
||||
here](https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies/).
|
||||
The way in which mergerfs behaves is controlled by the
|
||||
[config/options/settings](https://trapexit.github.io/mergerfs/latest/config/options). More
|
||||
specifically by
|
||||
[policies](https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies/).
|
||||
|
||||
|
||||
### Visualization
|
||||
@@ -77,7 +80,7 @@ A + B = C
|
||||
| | |
|
||||
+-- /dir2 | +-- /dir2
|
||||
| | | | |
|
||||
| *-- file4 | | +-- file4
|
||||
| +-- file4 | | +-- file4
|
||||
| | |
|
||||
| +-- /dir3 +-- /dir3
|
||||
| | | | |
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
GIT_BRANCH="${1:-master}"
|
||||
|
||||
set -x
|
||||
podman \
|
||||
build \
|
||||
--squash \
|
||||
--no-cache \
|
||||
--force-rm \
|
||||
--build-arg="GIT_REPO=file:///mnt" \
|
||||
--build-arg="BRANCH=${GIT_BRANCH}" \
|
||||
-v "${PWD}":/mnt:ro \
|
||||
-f "buildtools/containerimage/Containerfile" \
|
||||
--tag "ghcr.io/trapexit/mergerfs:${GIT_BRANCH}" \
|
||||
buildtools/
|
||||
+37
-24
@@ -1,6 +1,19 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ -e /tmp/build-env ]; then
|
||||
. /tmp/build-env
|
||||
fi
|
||||
|
||||
rpmbuild_flags()
|
||||
{
|
||||
case "$(command -v "${CXX:-g++}")" in
|
||||
/opt/rh/*)
|
||||
printf '%s' '--undefine _annotated_build'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "usage: $0 <branch> <git_repo>"
|
||||
echo "argv: $@"
|
||||
@@ -27,37 +40,37 @@ git log HEAD^1..
|
||||
mkdir "/build"
|
||||
if [ -e /usr/bin/apt-get ]; then
|
||||
make deb
|
||||
find /tmp/ \
|
||||
-type f \
|
||||
-name "*.deb" \
|
||||
-not -name "*dbgsym*" \
|
||||
-exec cp -v {} /build/ \;
|
||||
exit 0
|
||||
elif [ -e /usr/bin/dnf ]; then
|
||||
make rpm
|
||||
find /tmp/ \
|
||||
-type f \
|
||||
-name "*.rpm" \
|
||||
-not -name "*sym*" \
|
||||
-not -name "*src.rpm" \
|
||||
-exec cp -v {} /build/ \;
|
||||
exit 0
|
||||
make RPMBUILD_FLAGS="$(rpmbuild_flags)" rpm
|
||||
elif [ -e /usr/bin/yum ]; then
|
||||
. /opt/rh/devtoolset-9/enable
|
||||
make rpm
|
||||
find /tmp/ \
|
||||
-type f \
|
||||
-name "*.rpm" \
|
||||
-not -name "*sym*" \
|
||||
-not -name "*src.rpm" \
|
||||
-exec cp -v {} /build/ \;
|
||||
exit 0
|
||||
make RPMBUILD_FLAGS="$(rpmbuild_flags)" rpm
|
||||
elif [ -e /sbin/apk ]; then
|
||||
echo "NOT YET SUPPORTED"
|
||||
exit 1
|
||||
elif [ -e /usr/sbin/pkg ]; then
|
||||
echo "NOT YET SUPPORTED"
|
||||
exit 1
|
||||
elif [ -e /usr/bin/pacman ]; then
|
||||
make tarball
|
||||
else
|
||||
echo "NOT YET SUPPORTED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 1
|
||||
echo "Copy packages to host..."
|
||||
if [ -e /usr/bin/pacman ]; then
|
||||
find "${BUILDDIR}" \
|
||||
-type f \
|
||||
-name "*.tar.gz" \
|
||||
-exec cp -v {} /build/ \;
|
||||
else
|
||||
find "${BUILDDIR}" \
|
||||
-type f \
|
||||
\( -name "*.deb" -or -name "*.rpm" \) \
|
||||
-not -name "*dbgsym*" \
|
||||
-not -name "*sym*" \
|
||||
-not -name "*src.rpm" \
|
||||
-exec cp -v {} /build/ \;
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
+50
-11
@@ -18,10 +18,12 @@ def build(git_repo,
|
||||
'--force-rm']
|
||||
if os.path.exists(git_repo):
|
||||
git_repo = os.path.realpath(git_repo)
|
||||
args += ['-v',f'{git_repo}:/tmp/mergerfs.git:ro']
|
||||
args += ['--build-arg=GIT_REPO=/tmp/mergerfs.git']
|
||||
args += ['-v',f'{git_repo}:/tmp/mergerfs.git:ro:z']
|
||||
args += ['--build-arg=GIT_REPO=file:///tmp/mergerfs.git']
|
||||
else:
|
||||
args += [f'--build-arg=GIT_REPO={git_repo}']
|
||||
os.makedirs(pkgdirpath,exist_ok=True)
|
||||
args += ['-v',f'{os.path.realpath(pkgdirpath)}:/pkgs:ro']
|
||||
args += ['-o',pkgdirpath,
|
||||
'-f',containerfile,
|
||||
f'--build-arg=BRANCH={branch}',
|
||||
@@ -30,8 +32,6 @@ def build(git_repo,
|
||||
# TODO: Capture output and write to log
|
||||
print(args)
|
||||
rv = subprocess.run(args)
|
||||
|
||||
os.makedirs(pkgdirpath,exist_ok=True)
|
||||
report_filepath = os.path.join(pkgdirpath,"build-report.txt")
|
||||
with open(report_filepath,"a+") as f:
|
||||
build = os.path.basename(containerfile)
|
||||
@@ -47,14 +47,49 @@ def build(git_repo,
|
||||
|
||||
|
||||
def setup():
|
||||
args = ['sudo',
|
||||
'apt-get',
|
||||
'install',
|
||||
'-fy',
|
||||
'qemu-user-static',
|
||||
'qemu-user-binfmt']
|
||||
# Detect distribution
|
||||
if os.path.exists('/usr/bin/apt-get'):
|
||||
args = ['sudo',
|
||||
'apt-get',
|
||||
'install',
|
||||
'-fy',
|
||||
'qemu-user-static',
|
||||
'qemu-user-binfmt']
|
||||
elif os.path.exists('/usr/bin/pacman'):
|
||||
args = ['sudo',
|
||||
'pacman',
|
||||
'-Sy',
|
||||
'--noconfirm',
|
||||
'qemu-user-static']
|
||||
else:
|
||||
print("Unsupported distribution for setup")
|
||||
sys.exit(1)
|
||||
print(args)
|
||||
subprocess.run(args)
|
||||
setup_binfmt()
|
||||
|
||||
|
||||
def setup_binfmt():
|
||||
"""Register qemu-user-static binfmt handlers (needed on Arch, etc.)."""
|
||||
binfmt_conf = """\
|
||||
:qemu-aarch64:M::\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\xb7\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-aarch64-static:F
|
||||
:qemu-arm:M::\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x28\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-arm-static:F
|
||||
:qemu-armeb:M::\\x7fELF\\x01\\x02\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x28:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff:/usr/bin/qemu-armeb-static:F
|
||||
:qemu-riscv64:M::\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\xf3\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-riscv64-static:F
|
||||
:qemu-i386:M::\\x7fELF\\x01\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x00\\x03\\x00:\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xfe\\xff\\xff\\xff:/usr/bin/qemu-i386-static:F
|
||||
"""
|
||||
conf_path = '/etc/binfmt.d/qemu-static.conf'
|
||||
print(f"Writing {conf_path}")
|
||||
try:
|
||||
with open(conf_path, 'w') as f:
|
||||
f.write(binfmt_conf)
|
||||
except PermissionError:
|
||||
print(f"Permission denied writing {conf_path}; trying with sudo...")
|
||||
subprocess.run(['sudo', 'tee', conf_path],
|
||||
input=binfmt_conf.encode(), check=True)
|
||||
|
||||
print("Restarting systemd-binfmt...")
|
||||
subprocess.run(['sudo', 'systemctl', 'restart', 'systemd-binfmt'])
|
||||
|
||||
|
||||
def podman_cleanup():
|
||||
@@ -81,7 +116,7 @@ def parse_args():
|
||||
required=False,
|
||||
action='store_true')
|
||||
p.add_argument('--git-repo',
|
||||
required=True,
|
||||
required=False,
|
||||
type=str)
|
||||
|
||||
return p.parse_args()
|
||||
@@ -101,6 +136,10 @@ def main():
|
||||
setup()
|
||||
sys.exit(0)
|
||||
|
||||
if not args.git_repo:
|
||||
print("error: the following arguments are required: --git-repo")
|
||||
sys.exit(1)
|
||||
|
||||
containerfiles = []
|
||||
basepath = 'buildtools/containerfiles'
|
||||
if os.path.exists(f'{basepath}/{args.target}'):
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=amd64 fedora:44 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=arm64 fedora:44 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -1,13 +1,15 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM --platform=linux/amd64 alpine:latest as build
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BRANCH=master
|
||||
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
|
||||
WORKDIR /tmp/mergerfs
|
||||
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
|
||||
RUN mkdir /build
|
||||
RUN tar cvfz /build/mergerfs-static-linux_amd64.tar.gz --directory=/tmp usr sbin
|
||||
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
|
||||
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_amd64.tar.gz --directory=/tmp usr sbin
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM --platform=linux/arm64 alpine:latest as build
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BRANCH=master
|
||||
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
|
||||
WORKDIR /tmp/mergerfs
|
||||
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
|
||||
RUN mkdir /build
|
||||
RUN tar cvfz /build/mergerfs-static-linux_arm64.tar.gz --directory=/tmp usr sbin
|
||||
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
|
||||
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_arm64.tar.gz --directory=/tmp usr sbin
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM --platform=linux/armhf alpine:latest as build
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BRANCH=master
|
||||
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
|
||||
WORKDIR /tmp/mergerfs
|
||||
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
|
||||
RUN mkdir /build
|
||||
RUN tar cvfz /build/mergerfs-static-linux_armhf.tar.gz --directory=/tmp usr sbin
|
||||
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
|
||||
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_armhf.tar.gz --directory=/tmp usr sbin
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM --platform=linux/i386 alpine:latest as build
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BRANCH=master
|
||||
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
|
||||
WORKDIR /tmp/mergerfs
|
||||
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
|
||||
RUN mkdir /build
|
||||
RUN tar cvfz /build/mergerfs-static-linux_i386.tar.gz --directory=/tmp usr sbin
|
||||
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
|
||||
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_i386.tar.gz --directory=/tmp usr sbin
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM --platform=linux/amd64 ubuntu:latest as build
|
||||
RUN apt-get update && apt-get install -y makeself
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qy --no-install-suggests --no-install-recommends install \
|
||||
makeself
|
||||
RUN mkdir -p /tmp/mergerfs/
|
||||
RUN --mount=type=bind,src=pkgs,dst=/pkgs ls -lhd /pkgs
|
||||
RUN --mount=type=bind,src=pkgs,dst=/pkgs cp -v /pkg/*static*.gz /tmp/mergerfs/
|
||||
RUN ls -lhd /pkgs
|
||||
RUN cp -v /pkgs/*static*.gz /tmp/mergerfs/
|
||||
RUN mkdir -p /build
|
||||
RUN makeself /tmp/mergerfs/ /build/mergerfs-installer.run "mergerfs" echo "foo"
|
||||
|
||||
FROM scratch
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM --platform=linux/riscv64 alpine:latest as build
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BRANCH=master
|
||||
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN git clone --single-branch --branch="${BRANCH}" ${GIT_REPO} /tmp/mergerfs
|
||||
WORKDIR /tmp/mergerfs
|
||||
RUN make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp -j$(nproc) install
|
||||
RUN mkdir /build
|
||||
RUN tar cvfz /build/mergerfs-static-linux_riscv64.tar.gz --directory=/tmp usr sbin
|
||||
RUN /tmp/usr/local/bin/mergerfs --version | head -n1 | cut -c 11- | tee /tmp/version
|
||||
RUN tar cvfz /build/mergerfs-$(cat /tmp/version)-static-linux_riscv64.tar.gz --directory=/tmp usr sbin
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
FROM alpine:latest as tarball
|
||||
|
||||
COPY install-build-pkgs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG BRANCH=master
|
||||
RUN git clone --single-branch --depth=1 https://github.com/trapexit/mergerfs /tmp/mergerfs --branch="${BRANCH}"
|
||||
ARG GIT_REPO
|
||||
|
||||
RUN git clone --single-branch "${GIT_REPO}" /tmp/mergerfs --branch="${BRANCH}"
|
||||
RUN cd /tmp/mergerfs && make version tarball
|
||||
|
||||
RUN mkdir /build
|
||||
RUN cp -v /tmp/mergerfs/*.tar.gz /build/
|
||||
|
||||
|
||||
FROM scratch
|
||||
COPY --from=tarball /build/ /
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=amd64 ubuntu:25.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=arm64 ubuntu:25.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=armhf ubuntu:25.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=riscv64 ubuntu:25.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=amd64 ubuntu:25.10 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=arm64 ubuntu:25.10 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=armhf ubuntu:25.10 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=riscv64 ubuntu:25.10 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=amd64 ubuntu:26.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=arm64 ubuntu:26.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=armhf ubuntu:26.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM --platform=riscv64 ubuntu:26.04 as build
|
||||
COPY install-build-pkgs build-mergerfs /tmp/
|
||||
RUN /tmp/install-build-pkgs
|
||||
ARG BUILD_TIMESTAMP=0
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
RUN /tmp/build-mergerfs $GIT_REPO $BRANCH
|
||||
|
||||
FROM scratch
|
||||
COPY --from=build /build/ /
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM --platform=linux/amd64 alpine:latest as build
|
||||
|
||||
ARG GIT_REPO
|
||||
ARG BRANCH
|
||||
|
||||
RUN <<EOF
|
||||
apk add git gcc g++ make linux-headers
|
||||
git clone --single-branch --branch="${BRANCH}" "${GIT_REPO}" /tmp/mergerfs
|
||||
cd /tmp/mergerfs
|
||||
make NDEBUG=1 LTO=1 STATIC=1 DESTDIR=/tmp/install -j$(nproc) install
|
||||
tar cvfz /tmp/mergerfs.tgz --directory=/tmp/install usr sbin
|
||||
EOF
|
||||
|
||||
FROM --platform=linux/amd64 alpine:latest
|
||||
RUN --mount=from=build,source=/tmp/mergerfs.tgz,target=/tmp/mergerfs.tgz <<EOF
|
||||
tar xvf /tmp/mergerfs.tgz -C /
|
||||
EOF
|
||||
ENTRYPOINT ["/usr/local/bin/mergerfs"]
|
||||
CMD ["--help"]
|
||||
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
# Detects local OS and maps to containerfiles target
|
||||
# Returns target if found in buildtools/containerfiles/, empty string otherwise
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONTAINERFILES_DIR="buildtools/containerfiles"
|
||||
|
||||
# Detect system architecture and map to containerfiles format
|
||||
detect_arch() {
|
||||
local arch
|
||||
arch=$(uname -m)
|
||||
case "$arch" in
|
||||
x86_64) echo "amd64" ;;
|
||||
aarch64) echo "arm64" ;;
|
||||
armv7l) echo "armhf" ;;
|
||||
i686|i386) echo "i386" ;;
|
||||
riscv64) echo "riscv64" ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Detect distro and version from os-release
|
||||
detect_distro() {
|
||||
local os_release="${1:-}"
|
||||
if [[ ! -r "$os_release" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Source the file safely - ID and VERSION_ID are environment variables
|
||||
. "$os_release"
|
||||
|
||||
local distro="$ID"
|
||||
local version="$VERSION_ID"
|
||||
|
||||
# Map distro ID to containerfiles naming convention
|
||||
case "$distro" in
|
||||
ubuntu|debian|fedora) ;;
|
||||
rocky|rhel|centos|almalinux) distro="rockylinux" ;;
|
||||
*)
|
||||
# Fallback to ID_LIKE for derivatives
|
||||
if [[ -n "${ID_LIKE:-}" ]]; then
|
||||
case "$ID_LIKE" in
|
||||
*ubuntu*) distro="ubuntu" ;;
|
||||
*debian*) distro="debian" ;;
|
||||
*fedora*) distro="fedora" ;;
|
||||
esac
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Only return if we have valid distro and version
|
||||
if [[ -n "$distro" && -n "$version" ]]; then
|
||||
echo "$distro:$version"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Try to find a matching containerfile target
|
||||
find_containerfile() {
|
||||
local target="$1"
|
||||
local containerfile="${CONTAINERFILES_DIR}/${target}"
|
||||
|
||||
# Direct match
|
||||
if [[ -d "$containerfile" || -f "$containerfile" ]]; then
|
||||
echo "$target"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Main detection logic
|
||||
main() {
|
||||
local arch
|
||||
|
||||
# Detect architecture
|
||||
arch=$(detect_arch)
|
||||
if [[ -z "$arch" ]]; then
|
||||
echo "WARNING: Could not detect system architecture" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Try to detect distro from multiple locations
|
||||
local distro_ver
|
||||
distro_ver=$(detect_distro "/etc/os-release") || \
|
||||
distro_ver=$(detect_distro "/usr/lib/os-release") || {
|
||||
echo "WARNING: Could not detect distro from os-release" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build target string
|
||||
local target="${distro_ver}.${arch}"
|
||||
|
||||
# Try to find matching containerfile
|
||||
if find_containerfile "$target"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Try with partial version matching (e.g., 25.10 → 25)
|
||||
local distro="${distro_ver%%:*}"
|
||||
local version="${distro_ver##*:}"
|
||||
|
||||
# Strip last minor version number and try again
|
||||
local short_version="${version%.*}"
|
||||
if [[ -n "$short_version" ]]; then
|
||||
target="${distro}:${short_version}.${arch}"
|
||||
if find_containerfile "$target"; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# No match found
|
||||
echo "WARNING: No containerfile found for target: ${distro_ver}.${arch}" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,37 +1,162 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
BUILD_ENV=/tmp/build-env
|
||||
|
||||
write_gcc_env()
|
||||
{
|
||||
{
|
||||
printf 'export CC=%s\n' "$1"
|
||||
printf 'export CXX=%s\n' "$2"
|
||||
} > "$BUILD_ENV"
|
||||
}
|
||||
|
||||
write_toolset_env()
|
||||
{
|
||||
{
|
||||
printf '. %s\n' "$1"
|
||||
printf 'export CC=gcc\n'
|
||||
printf 'export CXX=g++\n'
|
||||
} > "$BUILD_ENV"
|
||||
}
|
||||
|
||||
latest_apt_gxx()
|
||||
{
|
||||
apt-cache --names-only search '^g[+][+]-[0-9]' \
|
||||
| sed -n 's/^\(g++-[0-9][0-9]*\) .*/\1/p' \
|
||||
| sort -t- -k2,2V \
|
||||
| tail -n 1
|
||||
}
|
||||
|
||||
latest_dnf_toolset()
|
||||
{
|
||||
dnf -q list --showduplicates 'gcc-toolset-*-gcc-c++' 2>/dev/null \
|
||||
| sed -n 's/^gcc-toolset-\([0-9][0-9]*\)-gcc-c++\..*/\1/p' \
|
||||
| sort -V \
|
||||
| tail -n 1
|
||||
}
|
||||
|
||||
latest_yum_toolset()
|
||||
{
|
||||
yum -q list available 'devtoolset-*-gcc-c++' 2>/dev/null \
|
||||
| sed -n 's/^devtoolset-\([0-9][0-9]*\)-gcc-c++\..*/\1/p' \
|
||||
| sort -V \
|
||||
| tail -n 1
|
||||
}
|
||||
|
||||
version_gt()
|
||||
{
|
||||
highest="$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1)"
|
||||
[ "$highest" = "$1" ] && [ "$1" != "$2" ]
|
||||
}
|
||||
|
||||
check_cxx20()
|
||||
{
|
||||
cxx="${CXX:-g++}"
|
||||
test_src=/tmp/mergerfs-cxx20-check.cpp
|
||||
test_bin=/tmp/mergerfs-cxx20-check
|
||||
|
||||
cat > "$test_src" <<'EOF'
|
||||
template <typename T>
|
||||
concept Incrementable = requires(T value) { value + 1; };
|
||||
|
||||
static_assert(Incrementable<int>);
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
if ! "$cxx" -std=c++20 "$test_src" -o "$test_bin" >/dev/null 2>&1; then
|
||||
echo "error: $cxx does not support C++20" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$test_src" "$test_bin"
|
||||
}
|
||||
|
||||
: > "$BUILD_ENV"
|
||||
|
||||
if [ -e /usr/bin/apt-get ]; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -qy update
|
||||
cxx_pkg="$(latest_apt_gxx)"
|
||||
if [ -n "$cxx_pkg" ]; then
|
||||
gcc_pkg="gcc-${cxx_pkg#g++-}"
|
||||
compiler_pkgs="$gcc_pkg $cxx_pkg"
|
||||
write_gcc_env "$gcc_pkg" "$cxx_pkg"
|
||||
else
|
||||
compiler_pkgs="g++"
|
||||
write_gcc_env gcc g++
|
||||
fi
|
||||
apt-get -qy --force-yes --no-install-suggests --no-install-recommends \
|
||||
install \
|
||||
ca-certificates \
|
||||
build-essential \
|
||||
dpkg-dev \
|
||||
git \
|
||||
g++ \
|
||||
$compiler_pkgs \
|
||||
debhelper \
|
||||
lsb-release \
|
||||
fakeroot
|
||||
. "$BUILD_ENV"
|
||||
check_cxx20
|
||||
elif [ -e /usr/bin/dnf ]; then
|
||||
dnf -y update
|
||||
toolset="$(latest_dnf_toolset)"
|
||||
dnf -y install \
|
||||
git rpm-build gcc-c++ make which libatomic
|
||||
default_gxx_major="$(g++ -dumpversion | cut -f1 -d.)"
|
||||
if [ -n "$toolset" ] && version_gt "$toolset" "$default_gxx_major"; then
|
||||
dnf -y install \
|
||||
"gcc-toolset-${toolset}-gcc" \
|
||||
"gcc-toolset-${toolset}-gcc-c++"
|
||||
write_toolset_env "/opt/rh/gcc-toolset-${toolset}/enable"
|
||||
else
|
||||
write_gcc_env gcc g++
|
||||
fi
|
||||
. "$BUILD_ENV"
|
||||
check_cxx20
|
||||
elif [ -e /usr/bin/yum ]; then
|
||||
yum -y update
|
||||
yum -y install \
|
||||
git rpm-build gcc-c++ make which
|
||||
yum -y install centos-release-scl
|
||||
yum -y install devtoolset-9-gcc\*
|
||||
yum -y install centos-release-scl || true
|
||||
yum -y update
|
||||
toolset="$(latest_yum_toolset)"
|
||||
default_gxx_major="$(g++ -dumpversion | cut -f1 -d.)"
|
||||
if [ -n "$toolset" ] && version_gt "$toolset" "$default_gxx_major"; then
|
||||
yum -y install \
|
||||
"devtoolset-${toolset}-gcc" \
|
||||
"devtoolset-${toolset}-gcc-c++"
|
||||
write_toolset_env "/opt/rh/devtoolset-${toolset}/enable"
|
||||
else
|
||||
write_gcc_env gcc g++
|
||||
fi
|
||||
. "$BUILD_ENV"
|
||||
check_cxx20
|
||||
elif [ -e /usr/bin/zypper ]; then
|
||||
zypper update -y
|
||||
zypper install -y \
|
||||
git rpm-build gcc-c++ make which
|
||||
write_gcc_env gcc g++
|
||||
. "$BUILD_ENV"
|
||||
check_cxx20
|
||||
elif [ -e /sbin/apk ]; then
|
||||
apk add \
|
||||
abuild git gcc g++ make \
|
||||
linux-headers
|
||||
write_gcc_env gcc g++
|
||||
. "$BUILD_ENV"
|
||||
check_cxx20
|
||||
elif [ -e /usr/sbin/pkg ]; then
|
||||
pkg install \
|
||||
git gmake gcc
|
||||
elif [ -e /usr/bin/pacman ]; then
|
||||
pacman -Sy --noconfirm base-devel git fakeroot
|
||||
write_gcc_env gcc g++
|
||||
. "$BUILD_ENV"
|
||||
check_cxx20
|
||||
fi
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
SUDO=
|
||||
elif command -v sudo >/dev/null 2>&1; then
|
||||
SUDO=sudo
|
||||
elif command -v doas >/dev/null 2>&1; then
|
||||
SUDO=doas
|
||||
else
|
||||
printf '%s\n' "error: need root privileges; run as root or install sudo/doas" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -e /usr/bin/apt-get ]; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -qy update
|
||||
apt-get -qy --force-yes --no-install-suggests --no-install-recommends install \
|
||||
${SUDO} apt-get -qy update
|
||||
${SUDO} apt-get -qy --no-install-suggests --no-install-recommends install \
|
||||
qemu-user-static \
|
||||
podman
|
||||
elif [ -e /usr/bin/dnf ]; then
|
||||
dnf -y update
|
||||
dnf -y install \
|
||||
${SUDO} dnf -y update
|
||||
${SUDO} dnf -y install \
|
||||
qemu-user-static \
|
||||
podman
|
||||
elif [ -e /sbin/apk ]; then
|
||||
apk add \
|
||||
${SUDO} apk add \
|
||||
python3 \
|
||||
qemu-user-static \
|
||||
podman
|
||||
elif [ -e /usr/bin/pacman ]; then
|
||||
${SUDO} pacman -Sy --noconfirm qemu-user-static podman
|
||||
fi
|
||||
|
||||
@@ -33,3 +33,7 @@ ubuntu:24.04 amd64
|
||||
ubuntu:24.04 arm64
|
||||
ubuntu:24.04 armhf
|
||||
ubuntu:24.04 riscv64
|
||||
ubuntu:25.04 amd64
|
||||
ubuntu:25.04 arm64
|
||||
ubuntu:25.04 armhf
|
||||
ubuntu:25.04 riscv64
|
||||
|
||||
Vendored
+4
-1
@@ -6,8 +6,11 @@ export DH_VERBOSE=1
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_build:
|
||||
$(MAKE) release
|
||||
|
||||
override_dh_auto_install:
|
||||
$(MAKE) \
|
||||
DESTDIR=$(CURDIR)/debian/mergerfs \
|
||||
PREFIX=/usr \
|
||||
install
|
||||
install
|
||||
@@ -1,786 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB.
|
||||
*/
|
||||
|
||||
#ifndef _FUSE_H_
|
||||
#define _FUSE_H_
|
||||
|
||||
#include "extern_c.h"
|
||||
#include "fuse_common.h"
|
||||
#include "fuse_kernel.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <utime.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/uio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
EXTERN_C_BEGIN
|
||||
|
||||
/* ----------------------------------------------------------- *
|
||||
* Basic FUSE API *
|
||||
* ----------------------------------------------------------- */
|
||||
|
||||
/** Handle for a FUSE filesystem */
|
||||
struct fuse;
|
||||
|
||||
/** Structure containing a raw command */
|
||||
struct fuse_cmd;
|
||||
|
||||
struct fuse_dirents_t;
|
||||
typedef struct fuse_dirents_t fuse_dirents_t;
|
||||
|
||||
/**
|
||||
* The file system operations:
|
||||
*
|
||||
* Most of these should work very similarly to the well known UNIX
|
||||
* file system operations. A major exception is that instead of
|
||||
* returning an error in 'errno', the operation should return the
|
||||
* negated error value (-errno) directly.
|
||||
*
|
||||
* All methods are optional, but some are essential for a useful
|
||||
* filesystem (e.g. getattr). Open, flush, release, fsync, opendir,
|
||||
* releasedir, fsyncdir, access, create, ftruncate, fgetattr, lock,
|
||||
* init and destroy are special purpose methods, without which a full
|
||||
* featured filesystem can still be implemented.
|
||||
*
|
||||
* Almost all operations take a path which can be of any length.
|
||||
*
|
||||
* Changed in fuse 2.8.0 (regardless of API version)
|
||||
* Previously, paths were limited to a length of PATH_MAX.
|
||||
*
|
||||
* See http://fuse.sourceforge.net/wiki/ for more information. There
|
||||
* is also a snapshot of the relevant wiki pages in the doc/ folder.
|
||||
*/
|
||||
struct fuse_operations
|
||||
{
|
||||
/** Get file attributes.
|
||||
*
|
||||
* Similar to stat(). The 'st_dev' and 'st_blksize' fields are
|
||||
* ignored. The 'st_ino' field is ignored except if the 'use_ino'
|
||||
* mount option is given.
|
||||
*/
|
||||
int (*getattr) (const char *, struct stat *, fuse_timeouts_t *);
|
||||
|
||||
/** Read the target of a symbolic link
|
||||
*
|
||||
* The buffer should be filled with a null terminated string. The
|
||||
* buffer size argument includes the space for the terminating
|
||||
* null character. If the linkname is too long to fit in the
|
||||
* buffer, it should be truncated. The return value should be 0
|
||||
* for success.
|
||||
*/
|
||||
int (*readlink) (const char *, char *, size_t);
|
||||
|
||||
/** Create a file node
|
||||
*
|
||||
* This is called for creation of all non-directory, non-symlink
|
||||
* nodes. If the filesystem defines a create() method, then for
|
||||
* regular files that will be called instead.
|
||||
*/
|
||||
int (*mknod) (const char *, mode_t, dev_t);
|
||||
|
||||
/** Create a directory
|
||||
*
|
||||
* Note that the mode argument may not have the type specification
|
||||
* bits set, i.e. S_ISDIR(mode) can be false. To obtain the
|
||||
* correct directory type bits use mode|S_IFDIR
|
||||
* */
|
||||
int (*mkdir) (const char *, mode_t);
|
||||
|
||||
/** Remove a file */
|
||||
int (*unlink) (const char *);
|
||||
|
||||
/** Remove a directory */
|
||||
int (*rmdir) (const char *);
|
||||
|
||||
/** Create a symbolic link */
|
||||
int (*symlink) (const char *, const char *, struct stat *, fuse_timeouts_t *);
|
||||
|
||||
/** Rename a file */
|
||||
int (*rename) (const char *, const char *);
|
||||
|
||||
/** Create a hard link to a file */
|
||||
int (*link) (const char *, const char *, struct stat *, fuse_timeouts_t *);
|
||||
|
||||
/** Change the permission bits of a file */
|
||||
int (*chmod) (const char *, mode_t);
|
||||
int (*fchmod)(const uint64_t, const mode_t);
|
||||
|
||||
/** Change the owner and group of a file */
|
||||
int (*chown) (const char *, uid_t, gid_t);
|
||||
int (*fchown)(const uint64_t, const uid_t, const gid_t);
|
||||
|
||||
/** Change the size of a file */
|
||||
int (*truncate) (const char *, off_t);
|
||||
|
||||
/** File open operation
|
||||
*
|
||||
* No creation (O_CREAT, O_EXCL) and by default also no
|
||||
* truncation (O_TRUNC) flags will be passed to open(). If an
|
||||
* application specifies O_TRUNC, fuse first calls truncate()
|
||||
* and then open(). Only if 'atomic_o_trunc' has been
|
||||
* specified and kernel version is 2.6.24 or later, O_TRUNC is
|
||||
* passed on to open.
|
||||
*
|
||||
* Unless the 'default_permissions' mount option is given,
|
||||
* open should check if the operation is permitted for the
|
||||
* given flags. Optionally open may also return an arbitrary
|
||||
* filehandle in the fuse_file_info structure, which will be
|
||||
* passed to all file operations.
|
||||
*
|
||||
* Changed in version 2.2
|
||||
*/
|
||||
int (*open) (const char *, fuse_file_info_t *);
|
||||
|
||||
/** Get file system statistics
|
||||
*
|
||||
* The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored
|
||||
*
|
||||
* Replaced 'struct statfs' parameter with 'struct statvfs' in
|
||||
* version 2.5
|
||||
*/
|
||||
int (*statfs) (const char *, struct statvfs *);
|
||||
|
||||
/** Possibly flush cached data
|
||||
*
|
||||
* BIG NOTE: This is not equivalent to fsync(). It's not a
|
||||
* request to sync dirty data.
|
||||
*
|
||||
* Flush is called on each close() of a file descriptor. So if a
|
||||
* filesystem wants to return write errors in close() and the file
|
||||
* has cached dirty data, this is a good place to write back data
|
||||
* and return any errors. Since many applications ignore close()
|
||||
* errors this is not always useful.
|
||||
*
|
||||
* NOTE: The flush() method may be called more than once for each
|
||||
* open(). This happens if more than one file descriptor refers
|
||||
* to an opened file due to dup(), dup2() or fork() calls. It is
|
||||
* not possible to determine if a flush is final, so each flush
|
||||
* should be treated equally. Multiple write-flush sequences are
|
||||
* relatively rare, so this shouldn't be a problem.
|
||||
*
|
||||
* Filesystems shouldn't assume that flush will always be called
|
||||
* after some writes, or that if will be called at all.
|
||||
*
|
||||
* Changed in version 2.2
|
||||
*/
|
||||
int (*flush) (const fuse_file_info_t *);
|
||||
|
||||
/** Release an open file
|
||||
*
|
||||
* Release is called when there are no more references to an open
|
||||
* file: all file descriptors are closed and all memory mappings
|
||||
* are unmapped.
|
||||
*
|
||||
* For every open() call there will be exactly one release() call
|
||||
* with the same flags and file descriptor. It is possible to
|
||||
* have a file opened more than once, in which case only the last
|
||||
* release will mean, that no more reads/writes will happen on the
|
||||
* file. The return value of release is ignored.
|
||||
*
|
||||
* Changed in version 2.2
|
||||
*/
|
||||
int (*release) (const fuse_file_info_t *);
|
||||
|
||||
/** Synchronize file contents
|
||||
*
|
||||
* If the datasync parameter is non-zero, then only the user data
|
||||
* should be flushed, not the meta data.
|
||||
*
|
||||
* Changed in version 2.2
|
||||
*/
|
||||
int (*fsync) (const uint64_t, int);
|
||||
|
||||
/** Set extended attributes */
|
||||
int (*setxattr) (const char *, const char *, const char *, size_t, int);
|
||||
|
||||
/** Get extended attributes */
|
||||
int (*getxattr) (const char *, const char *, char *, size_t);
|
||||
|
||||
/** List extended attributes */
|
||||
int (*listxattr) (const char *, char *, size_t);
|
||||
|
||||
/** Remove extended attributes */
|
||||
int (*removexattr) (const char *, const char *);
|
||||
|
||||
/** Open directory
|
||||
*
|
||||
* Unless the 'default_permissions' mount option is given,
|
||||
* this method should check if opendir is permitted for this
|
||||
* directory. Optionally opendir may also return an arbitrary
|
||||
* filehandle in the fuse_file_info structure, which will be
|
||||
* passed to readdir, closedir and fsyncdir.
|
||||
*
|
||||
* Introduced in version 2.3
|
||||
*/
|
||||
int (*opendir) (const char *,
|
||||
fuse_file_info_t *);
|
||||
|
||||
/** Read directory
|
||||
*
|
||||
* This supersedes the old getdir() interface. New applications
|
||||
* should use this.
|
||||
*
|
||||
* The filesystem may choose between two modes of operation:
|
||||
*
|
||||
* 1) The readdir implementation ignores the offset parameter, and
|
||||
* passes zero to the filler function's offset. The filler
|
||||
* function will not return '1' (unless an error happens), so the
|
||||
* whole directory is read in a single readdir operation. This
|
||||
* works just like the old getdir() method.
|
||||
*
|
||||
* 2) The readdir implementation keeps track of the offsets of the
|
||||
* directory entries. It uses the offset parameter and always
|
||||
* passes non-zero offset to the filler function. When the buffer
|
||||
* is full (or an error happens) the filler function will return
|
||||
* '1'.
|
||||
*
|
||||
* Introduced in version 2.3
|
||||
*/
|
||||
int (*readdir)(const fuse_file_info_t *,
|
||||
fuse_dirents_t *);
|
||||
|
||||
int (*readdir_plus)(const fuse_file_info_t *,
|
||||
fuse_dirents_t *);
|
||||
|
||||
|
||||
/** Release directory
|
||||
*
|
||||
* Introduced in version 2.3
|
||||
*/
|
||||
int (*releasedir) (const fuse_file_info_t *);
|
||||
|
||||
/** Synchronize directory contents
|
||||
*
|
||||
* If the datasync parameter is non-zero, then only the user data
|
||||
* should be flushed, not the meta data
|
||||
*
|
||||
* Introduced in version 2.3
|
||||
*/
|
||||
int (*fsyncdir) (const fuse_file_info_t *, int);
|
||||
|
||||
/**
|
||||
* Initialize filesystem
|
||||
*
|
||||
* The return value will passed in the private_data field of
|
||||
* fuse_context to all file operations and as a parameter to the
|
||||
* destroy() method.
|
||||
*
|
||||
* Introduced in version 2.3
|
||||
* Changed in version 2.6
|
||||
*/
|
||||
void *(*init) (struct fuse_conn_info *conn);
|
||||
|
||||
/**
|
||||
* Clean up filesystem
|
||||
*
|
||||
* Called on filesystem exit.
|
||||
*
|
||||
* Introduced in version 2.3
|
||||
*/
|
||||
void (*destroy) (void);
|
||||
|
||||
/**
|
||||
* Check file access permissions
|
||||
*
|
||||
* This will be called for the access() system call. If the
|
||||
* 'default_permissions' mount option is given, this method is not
|
||||
* called.
|
||||
*
|
||||
* This method is not called under Linux kernel versions 2.4.x
|
||||
*
|
||||
* Introduced in version 2.5
|
||||
*/
|
||||
int (*access) (const char *, int);
|
||||
|
||||
/**
|
||||
* Create and open a file
|
||||
*
|
||||
* If the file does not exist, first create it with the specified
|
||||
* mode, and then open it.
|
||||
*
|
||||
* If this method is not implemented or under Linux kernel
|
||||
* versions earlier than 2.6.15, the mknod() and open() methods
|
||||
* will be called instead.
|
||||
*
|
||||
* Introduced in version 2.5
|
||||
*/
|
||||
int (*create) (const char *, mode_t, fuse_file_info_t *);
|
||||
|
||||
/**
|
||||
* Change the size of an open file
|
||||
*
|
||||
* This method is called instead of the truncate() method if the
|
||||
* truncation was invoked from an ftruncate() system call.
|
||||
*
|
||||
* If this method is not implemented or under Linux kernel
|
||||
* versions earlier than 2.6.15, the truncate() method will be
|
||||
* called instead.
|
||||
*
|
||||
* Introduced in version 2.5
|
||||
*/
|
||||
int (*ftruncate) (const uint64_t, off_t);
|
||||
|
||||
/**
|
||||
* Get attributes from an open file
|
||||
*
|
||||
* This method is called instead of the getattr() method if the
|
||||
* file information is available.
|
||||
*
|
||||
* Currently this is only called after the create() method if that
|
||||
* is implemented (see above). Later it may be called for
|
||||
* invocations of fstat() too.
|
||||
*
|
||||
* Introduced in version 2.5
|
||||
*/
|
||||
int (*fgetattr) (const uint64_t, struct stat *, fuse_timeouts_t *);
|
||||
|
||||
/**
|
||||
* Perform POSIX file locking operation
|
||||
*
|
||||
* The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.
|
||||
*
|
||||
* For the meaning of fields in 'struct flock' see the man page
|
||||
* for fcntl(2). The l_whence field will always be set to
|
||||
* SEEK_SET.
|
||||
*
|
||||
* For checking lock ownership, the 'fuse_file_info->owner'
|
||||
* argument must be used.
|
||||
*
|
||||
* For F_GETLK operation, the library will first check currently
|
||||
* held locks, and if a conflicting lock is found it will return
|
||||
* information without calling this method. This ensures, that
|
||||
* for local locks the l_pid field is correctly filled in. The
|
||||
* results may not be accurate in case of race conditions and in
|
||||
* the presence of hard links, but it's unlikely that an
|
||||
* application would rely on accurate GETLK results in these
|
||||
* cases. If a conflicting lock is not found, this method will be
|
||||
* called, and the filesystem may fill out l_pid by a meaningful
|
||||
* value, or it may leave this field zero.
|
||||
*
|
||||
* For F_SETLK and F_SETLKW the l_pid field will be set to the pid
|
||||
* of the process performing the locking operation.
|
||||
*
|
||||
* Note: if this method is not implemented, the kernel will still
|
||||
* allow file locking to work locally. Hence it is only
|
||||
* interesting for network filesystems and similar.
|
||||
*
|
||||
* Introduced in version 2.6
|
||||
*/
|
||||
int (*lock) (const fuse_file_info_t *,
|
||||
int cmd,
|
||||
struct flock *);
|
||||
|
||||
/**
|
||||
* Change the access and modification times of a file with
|
||||
* nanosecond resolution
|
||||
*
|
||||
* This supersedes the old utime() interface. New applications
|
||||
* should use this.
|
||||
*
|
||||
* See the utimensat(2) man page for details.
|
||||
*
|
||||
* Introduced in version 2.6
|
||||
*/
|
||||
int (*utimens)(const char *, const struct timespec tv[2]);
|
||||
int (*futimens)(const uint64_t fh, const struct timespec tv[2]);
|
||||
|
||||
/**
|
||||
* Map block index within file to block index within device
|
||||
*
|
||||
* Note: This makes sense only for block device backed filesystems
|
||||
* mounted with the 'blkdev' option
|
||||
*
|
||||
* Introduced in version 2.6
|
||||
*/
|
||||
int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
|
||||
|
||||
/**
|
||||
* Ioctl
|
||||
*
|
||||
* flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in
|
||||
* 64bit environment. The size and direction of data is
|
||||
* determined by _IOC_*() decoding of cmd. For _IOC_NONE,
|
||||
* data will be NULL, for _IOC_WRITE data is out area, for
|
||||
* _IOC_READ in area and if both are set in/out area. In all
|
||||
* non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.
|
||||
*
|
||||
* If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a
|
||||
* directory file handle.
|
||||
*
|
||||
* Introduced in version 2.8
|
||||
*/
|
||||
int (*ioctl) (const fuse_file_info_t *ffi,
|
||||
unsigned long cmd,
|
||||
void *arg,
|
||||
unsigned int flags,
|
||||
void *data,
|
||||
uint32_t *out_bufsz);
|
||||
|
||||
/**
|
||||
* Poll for IO readiness events
|
||||
*
|
||||
* Note: If ph is non-NULL, the client should notify
|
||||
* when IO readiness events occur by calling
|
||||
* fuse_notify_poll() with the specified ph.
|
||||
*
|
||||
* Regardless of the number of times poll with a non-NULL ph
|
||||
* is received, single notification is enough to clear all.
|
||||
* Notifying more times incurs overhead but doesn't harm
|
||||
* correctness.
|
||||
*
|
||||
* The callee is responsible for destroying ph with
|
||||
* fuse_pollhandle_destroy() when no longer in use.
|
||||
*
|
||||
* Introduced in version 2.8
|
||||
*/
|
||||
int (*poll) (const fuse_file_info_t *ffi,
|
||||
fuse_pollhandle_t *ph,
|
||||
unsigned *reventsp);
|
||||
|
||||
/** Write contents of buffer to an open file
|
||||
*
|
||||
* Similar to the write() method, but data is supplied in a
|
||||
* generic buffer. Use fuse_buf_copy() to transfer data to
|
||||
* the destination.
|
||||
*
|
||||
* Introduced in version 2.9
|
||||
*/
|
||||
int (*write) (const fuse_file_info_t *ffi,
|
||||
const char *data,
|
||||
size_t size,
|
||||
off_t off);
|
||||
|
||||
/** Store data from an open file in a buffer
|
||||
*
|
||||
* Similar to the read() method, but data is stored and
|
||||
* returned in a generic buffer.
|
||||
*
|
||||
* No actual copying of data has to take place, the source
|
||||
* file descriptor may simply be stored in the buffer for
|
||||
* later data transfer.
|
||||
*
|
||||
* The buffer must be allocated dynamically and stored at the
|
||||
* location pointed to by bufp. If the buffer contains memory
|
||||
* regions, they too must be allocated using malloc(). The
|
||||
* allocated memory will be freed by the caller.
|
||||
*
|
||||
* Introduced in version 2.9
|
||||
*/
|
||||
int (*read)(const fuse_file_info_t *ffi,
|
||||
char *buf,
|
||||
size_t size,
|
||||
off_t off);
|
||||
/**
|
||||
* Perform BSD file locking operation
|
||||
*
|
||||
* The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN
|
||||
*
|
||||
* Nonblocking requests will be indicated by ORing LOCK_NB to
|
||||
* the above operations
|
||||
*
|
||||
* For more information see the flock(2) manual page.
|
||||
*
|
||||
* Additionally fi->owner will be set to a value unique to
|
||||
* this open file. This same value will be supplied to
|
||||
* ->release() when the file is released.
|
||||
*
|
||||
* Note: if this method is not implemented, the kernel will still
|
||||
* allow file locking to work locally. Hence it is only
|
||||
* interesting for network filesystems and similar.
|
||||
*
|
||||
* Introduced in version 2.9
|
||||
*/
|
||||
int (*flock) (const fuse_file_info_t *, int op);
|
||||
|
||||
/**
|
||||
* Allocates space for an open file
|
||||
*
|
||||
* This function ensures that required space is allocated for specified
|
||||
* file. If this function returns success then any subsequent write
|
||||
* request to specified range is guaranteed not to fail because of lack
|
||||
* of space on the file system media.
|
||||
*
|
||||
* Introduced in version 2.9.1
|
||||
*/
|
||||
int (*fallocate) (const uint64_t, int, off_t, off_t);
|
||||
|
||||
/**
|
||||
* Copy a range of data from one file to another
|
||||
*
|
||||
* Performs an optimized copy between two file descriptors without
|
||||
* the additional cost of transferring data through the FUSE kernel
|
||||
* module to user space (glibc) and then back into the FUSE filesystem
|
||||
* again.
|
||||
*
|
||||
* In case this method is not implemented, glibc falls back to
|
||||
* reading data from the source and writing to the
|
||||
* destination. Effectively doing an inefficient copy of the
|
||||
* data.
|
||||
*/
|
||||
ssize_t (*copy_file_range)(const fuse_file_info_t *fi_in,
|
||||
off_t offset_in,
|
||||
const fuse_file_info_t *fi_out,
|
||||
off_t offset_out,
|
||||
size_t size,
|
||||
int flags);
|
||||
|
||||
ssize_t (*setupmapping)(uint64_t *fh_,
|
||||
uint64_t foffset_,
|
||||
uint64_t len_,
|
||||
uint64_t flags_,
|
||||
uint64_t moffset_);
|
||||
|
||||
int (*removemapping)();
|
||||
int (*syncfs)();
|
||||
int (*tmpfile)(const char *, mode_t, fuse_file_info_t *);
|
||||
int (*statx)(const char *fusepath,
|
||||
const uint32_t flags,
|
||||
const uint32_t mask,
|
||||
struct fuse_statx *st,
|
||||
fuse_timeouts_t *timeout);
|
||||
int (*statx_fh)(const uint64_t fh,
|
||||
const uint32_t flags,
|
||||
const uint32_t mask,
|
||||
struct fuse_statx *st,
|
||||
fuse_timeouts_t *timeout);
|
||||
};
|
||||
|
||||
/** Extra context that may be needed by some filesystems
|
||||
*
|
||||
* The uid, gid and pid fields are not filled in case of a writepage
|
||||
* operation.
|
||||
*/
|
||||
struct fuse_context
|
||||
{
|
||||
uint64_t unique;
|
||||
uint64_t nodeid;
|
||||
uint32_t opcode;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
mode_t umask;
|
||||
struct fuse *fuse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Main function of FUSE.
|
||||
*
|
||||
* This is for the lazy. This is all that has to be called from the
|
||||
* main() function.
|
||||
*
|
||||
* This function does the following:
|
||||
* - parses command line options (-d -s and -h)
|
||||
* - passes relevant mount options to the fuse_mount()
|
||||
* - installs signal handlers for INT, HUP, TERM and PIPE
|
||||
* - registers an exit handler to unmount the filesystem on program exit
|
||||
* - creates a fuse handle
|
||||
* - registers the operations
|
||||
* - calls either the single-threaded or the multi-threaded event loop
|
||||
*
|
||||
* Note: this is currently implemented as a macro.
|
||||
*
|
||||
* @param argc the argument counter passed to the main() function
|
||||
* @param argv the argument vector passed to the main() function
|
||||
* @param op the file system operation
|
||||
* @return 0 on success, nonzero on failure
|
||||
*/
|
||||
/*
|
||||
int fuse_main(int argc, char *argv[], const struct fuse_operations *op);
|
||||
*/
|
||||
#define fuse_main(argc, argv, op) \
|
||||
fuse_main_real(argc, argv, op, sizeof(*(op)))
|
||||
|
||||
/* ----------------------------------------------------------- *
|
||||
* More detailed API *
|
||||
* ----------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create a new FUSE filesystem.
|
||||
*
|
||||
* @param ch the communication channel
|
||||
* @param args argument vector
|
||||
* @param op the filesystem operations
|
||||
* @param op_size the size of the fuse_operations structure
|
||||
* @return the created FUSE handle
|
||||
*/
|
||||
struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args,
|
||||
const struct fuse_operations *op, size_t op_size);
|
||||
|
||||
/**
|
||||
* Destroy the FUSE handle.
|
||||
*
|
||||
* The communication channel attached to the handle is also destroyed.
|
||||
*
|
||||
* NOTE: This function does not unmount the filesystem. If this is
|
||||
* needed, call fuse_unmount() before calling this function.
|
||||
*
|
||||
* @param f the FUSE handle
|
||||
*/
|
||||
void fuse_destroy(struct fuse *f);
|
||||
|
||||
/**
|
||||
* Exit from event loop
|
||||
*
|
||||
* @param f the FUSE handle
|
||||
*/
|
||||
void fuse_exit(struct fuse *f);
|
||||
|
||||
int fuse_config_read_thread_count(const struct fuse *f);
|
||||
int fuse_config_process_thread_count(const struct fuse *f);
|
||||
int fuse_config_process_thread_queue_depth(const struct fuse *f);
|
||||
const char* fuse_config_pin_threads(const struct fuse *f);
|
||||
|
||||
/**
|
||||
* FUSE event loop with multiple threads
|
||||
*
|
||||
* Requests from the kernel are processed, and the appropriate
|
||||
* operations are called. Request are processed in parallel by
|
||||
* distributing them between multiple threads.
|
||||
*
|
||||
* Calling this function requires the pthreads library to be linked to
|
||||
* the application.
|
||||
*
|
||||
* @param f the FUSE handle
|
||||
* @return 0 if no error occurred, -1 otherwise
|
||||
*/
|
||||
int fuse_loop_mt(struct fuse *f);
|
||||
|
||||
/**
|
||||
* Get the current context
|
||||
*
|
||||
* The context is only valid for the duration of a filesystem
|
||||
* operation, and thus must not be stored and used later.
|
||||
*
|
||||
* @return the context
|
||||
*/
|
||||
struct fuse_context *fuse_get_context(void);
|
||||
|
||||
/**
|
||||
* Check if the current request has already been interrupted
|
||||
*
|
||||
* @return 1 if the request has been interrupted, 0 otherwise
|
||||
*/
|
||||
int fuse_interrupted(void);
|
||||
|
||||
/**
|
||||
* Obsolete, doesn't do anything
|
||||
*
|
||||
* @return -EINVAL
|
||||
*/
|
||||
int fuse_invalidate(struct fuse *f, const char *path);
|
||||
|
||||
/* Deprecated, don't use */
|
||||
int fuse_is_lib_option(const char *opt);
|
||||
|
||||
/**
|
||||
* The real main function
|
||||
*
|
||||
* Do not call this directly, use fuse_main()
|
||||
*/
|
||||
int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size);
|
||||
|
||||
void fuse_populate_maintenance_thread(struct fuse *fuse);
|
||||
|
||||
int fuse_log_metrics_get(void);
|
||||
void fuse_log_metrics_set(int enabled);
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over cache removing stale entries
|
||||
* use in conjunction with "-oremember"
|
||||
*
|
||||
* NOTE: This is already done for the standard sessions
|
||||
*
|
||||
* @param fuse struct fuse pointer for fuse instance
|
||||
* @return the number of seconds until the next cleanup
|
||||
*/
|
||||
int fuse_clean_cache(struct fuse *fuse);
|
||||
|
||||
/*
|
||||
* Stacking API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fuse filesystem object
|
||||
*
|
||||
* This is opaque object represents a filesystem layer
|
||||
*/
|
||||
struct fuse_fs;
|
||||
|
||||
/*
|
||||
* These functions call the relevant filesystem operation, and return
|
||||
* the result.
|
||||
*
|
||||
* If the operation is not defined, they return -ENOSYS, with the
|
||||
* exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
|
||||
* fuse_fs_releasedir and fuse_fs_statfs, which return 0.
|
||||
*/
|
||||
|
||||
int fuse_notify_poll(fuse_pollhandle_t *ph);
|
||||
|
||||
/**
|
||||
* Create a new fuse filesystem object
|
||||
*
|
||||
* This is usually called from the factory of a fuse module to create
|
||||
* a new instance of a filesystem.
|
||||
*
|
||||
* @param op the filesystem operations
|
||||
* @param op_size the size of the fuse_operations structure
|
||||
* @return a new filesystem object
|
||||
*/
|
||||
struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size);
|
||||
|
||||
/* ----------------------------------------------------------- *
|
||||
* Advanced API for event handling, don't worry about this... *
|
||||
* ----------------------------------------------------------- */
|
||||
|
||||
/* NOTE: the following functions are deprecated, and will be removed
|
||||
from the 3.0 API. Use the lowlevel session functions instead */
|
||||
|
||||
/** Function type used to process commands */
|
||||
typedef void (*fuse_processor_t)(struct fuse *, struct fuse_cmd *, void *);
|
||||
|
||||
/** This is the part of fuse_main() before the event loop */
|
||||
struct fuse *fuse_setup(int argc, char *argv[],
|
||||
const struct fuse_operations *op, size_t op_size,
|
||||
char **mountpoint);
|
||||
|
||||
/** This is the part of fuse_main() after the event loop */
|
||||
void fuse_teardown(struct fuse *fuse, char *mountpoint);
|
||||
|
||||
/** Multi threaded event loop, which calls the custom command
|
||||
processor function */
|
||||
int fuse_loop_mt_proc(struct fuse *f, fuse_processor_t proc, void *data);
|
||||
|
||||
/** Return the exited flag, which indicates if fuse_exit() has been
|
||||
called */
|
||||
int fuse_exited(struct fuse *f);
|
||||
|
||||
/** This function is obsolete and implemented as a no-op */
|
||||
void fuse_set_getcontext_func(struct fuse_context *(*func)(void));
|
||||
|
||||
/** Get session from fuse object */
|
||||
struct fuse_session *fuse_get_session(struct fuse *f);
|
||||
|
||||
void fuse_gc1();
|
||||
void fuse_gc();
|
||||
void fuse_invalidate_all_nodes();
|
||||
|
||||
int fuse_get_dev_fuse_fd(const struct fuse_context *fc);
|
||||
int fuse_passthrough_open(const struct fuse_context *fc,
|
||||
const int fd);
|
||||
int fuse_passthrough_close(const struct fuse_context *fc,
|
||||
const int backing_id);
|
||||
|
||||
EXTERN_C_END
|
||||
|
||||
#endif /* _FUSE_H_ */
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
int fuse_config_get_read_thread_count();
|
||||
int fuse_config_get_process_thread_count();
|
||||
int fuse_config_get_process_thread_queue_depth();
|
||||
std::string fuse_config_get_pin_threads();
|
||||
|
||||
void fuse_config_set_read_thread_count(int const);
|
||||
void fuse_config_set_process_thread_count(int const);
|
||||
void fuse_config_set_process_thread_queue_depth(int const);
|
||||
void fuse_config_set_pin_threads(std::string const);
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2019, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "extern_c.h"
|
||||
|
||||
EXTERN_C_BEGIN
|
||||
|
||||
#include "kvec.h"
|
||||
#include "fuse_dirent.h"
|
||||
#include "fuse_direntplus.h"
|
||||
#include "fuse_entry.h"
|
||||
#include "linux_dirent64.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
enum fuse_dirents_type_e
|
||||
{
|
||||
UNSET = 0,
|
||||
NORMAL,
|
||||
PLUS
|
||||
};
|
||||
typedef enum fuse_dirents_type_e fuse_dirents_type_t;
|
||||
|
||||
typedef struct fuse_dirents_t fuse_dirents_t;
|
||||
struct fuse_dirents_t
|
||||
{
|
||||
kvec_t(char) data;
|
||||
kvec_t(uint32_t) offs;
|
||||
fuse_dirents_type_t type;
|
||||
};
|
||||
|
||||
int fuse_dirents_init(fuse_dirents_t *d);
|
||||
void fuse_dirents_free(fuse_dirents_t *d);
|
||||
void fuse_dirents_reset(fuse_dirents_t *d);
|
||||
|
||||
int fuse_dirents_add(fuse_dirents_t *d,
|
||||
const struct dirent *de,
|
||||
const uint64_t namelen);
|
||||
int fuse_dirents_add_plus(fuse_dirents_t *d,
|
||||
const struct dirent *de,
|
||||
const uint64_t namelen,
|
||||
const fuse_entry_t *entry,
|
||||
const struct stat *st);
|
||||
int fuse_dirents_add_linux(fuse_dirents_t *d,
|
||||
const linux_dirent64_t *de,
|
||||
const uint64_t namelen);
|
||||
int fuse_dirents_add_linux_plus(fuse_dirents_t *d,
|
||||
const linux_dirent64_t *de,
|
||||
const uint64_t namelen,
|
||||
const fuse_entry_t *entry,
|
||||
const struct stat *st);
|
||||
|
||||
void *fuse_dirents_find(fuse_dirents_t *d,
|
||||
const uint64_t ino);
|
||||
|
||||
int fuse_dirents_convert_plus2normal(fuse_dirents_t *d);
|
||||
|
||||
EXTERN_C_END
|
||||
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DIRENT_NAMELEN(X) (strlen((X)->name))
|
||||
|
||||
typedef struct linux_dirent64_t linux_dirent64_t;
|
||||
struct linux_dirent64_t
|
||||
{
|
||||
uint64_t ino;
|
||||
int64_t off;
|
||||
uint16_t reclen;
|
||||
uint8_t type;
|
||||
char name[];
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef DEBUG
|
||||
#include "mutex_debug.hpp"
|
||||
#else
|
||||
#include "mutex_ndebug.hpp"
|
||||
#endif
|
||||
@@ -1,389 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "moodycamel/blockingconcurrentqueue.h"
|
||||
#include "moodycamel/lightweightsemaphore.h"
|
||||
#include "invocable.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#define SEM
|
||||
//#undef SEM
|
||||
|
||||
#ifdef SEM
|
||||
#define SEMA_WAIT(S) (S.wait())
|
||||
#define SEMA_SIGNAL(S) (S.signal())
|
||||
#else
|
||||
#define SEMA_WAIT(S)
|
||||
#define SEMA_SIGNAL(S)
|
||||
#endif
|
||||
|
||||
|
||||
struct ThreadPoolTraits : public moodycamel::ConcurrentQueueDefaultTraits
|
||||
{
|
||||
static const int MAX_SEMA_SPINS = 1;
|
||||
};
|
||||
|
||||
|
||||
class ThreadPool
|
||||
{
|
||||
public:
|
||||
using PToken = moodycamel::ProducerToken;
|
||||
using CToken = moodycamel::ConsumerToken;
|
||||
|
||||
private:
|
||||
using Func = ofats::any_invocable<void()>;
|
||||
using Queue = moodycamel::BlockingConcurrentQueue<Func,ThreadPoolTraits>;
|
||||
|
||||
public:
|
||||
explicit
|
||||
ThreadPool(const unsigned thread_count_ = std::thread::hardware_concurrency(),
|
||||
const unsigned max_queue_depth_ = std::thread::hardware_concurrency(),
|
||||
std::string const name_ = {});
|
||||
~ThreadPool();
|
||||
|
||||
private:
|
||||
static void *start_routine(void *arg_);
|
||||
|
||||
public:
|
||||
int add_thread(std::string const name = {});
|
||||
int remove_thread(void);
|
||||
int set_threads(const std::size_t count);
|
||||
|
||||
void shutdown(void);
|
||||
|
||||
public:
|
||||
template<typename FuncType>
|
||||
void
|
||||
enqueue_work(ThreadPool::PToken &ptok_,
|
||||
FuncType &&func_);
|
||||
|
||||
template<typename FuncType>
|
||||
void
|
||||
enqueue_work(FuncType &&func_);
|
||||
|
||||
template<typename FuncType>
|
||||
[[nodiscard]]
|
||||
std::future<typename std::result_of<FuncType()>::type>
|
||||
enqueue_task(FuncType&& func_);
|
||||
|
||||
public:
|
||||
std::vector<pthread_t> threads() const;
|
||||
ThreadPool::PToken ptoken();
|
||||
|
||||
private:
|
||||
Queue _queue;
|
||||
moodycamel::LightweightSemaphore _sema;
|
||||
|
||||
private:
|
||||
std::string const _name;
|
||||
std::vector<pthread_t> _threads;
|
||||
mutable std::mutex _threads_mutex;
|
||||
};
|
||||
|
||||
|
||||
|
||||
inline
|
||||
ThreadPool::ThreadPool(const unsigned thread_count_,
|
||||
const unsigned max_queue_depth_,
|
||||
const std::string name_)
|
||||
: _queue(),
|
||||
_sema(max_queue_depth_),
|
||||
_name(name_)
|
||||
{
|
||||
sigset_t oldset;
|
||||
sigset_t newset;
|
||||
|
||||
sigfillset(&newset);
|
||||
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
|
||||
|
||||
_threads.reserve(thread_count_);
|
||||
for(std::size_t i = 0; i < thread_count_; ++i)
|
||||
{
|
||||
int rv;
|
||||
pthread_t t;
|
||||
|
||||
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
|
||||
if(rv != 0)
|
||||
{
|
||||
syslog(LOG_WARNING,
|
||||
"threadpool (%s): error spawning thread - %d (%s)",
|
||||
_name.c_str(),
|
||||
rv,
|
||||
strerror(rv));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!_name.empty())
|
||||
pthread_setname_np(t,_name.c_str());
|
||||
|
||||
_threads.push_back(t);
|
||||
}
|
||||
|
||||
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
|
||||
|
||||
if(_threads.empty())
|
||||
throw std::runtime_error("threadpool: failed to spawn any threads");
|
||||
|
||||
syslog(LOG_DEBUG,
|
||||
"threadpool (%s): spawned %zu threads w/ max queue depth %u",
|
||||
_name.c_str(),
|
||||
_threads.size(),
|
||||
max_queue_depth_);
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
ThreadPool::~ThreadPool()
|
||||
{
|
||||
syslog(LOG_DEBUG,
|
||||
"threadpool (%s): destroying %zu threads",
|
||||
_name.c_str(),
|
||||
_threads.size());
|
||||
|
||||
for(auto t : _threads)
|
||||
pthread_cancel(t);
|
||||
for(auto t : _threads)
|
||||
pthread_join(t,NULL);
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
void*
|
||||
ThreadPool::start_routine(void *arg_)
|
||||
{
|
||||
ThreadPool *btp = static_cast<ThreadPool*>(arg_);
|
||||
|
||||
bool done;
|
||||
ThreadPool::Func func;
|
||||
ThreadPool::Queue &q = btp->_queue;
|
||||
moodycamel::LightweightSemaphore &sema = btp->_sema;
|
||||
ThreadPool::CToken ctok(btp->_queue);
|
||||
|
||||
done = false;
|
||||
while(!done)
|
||||
{
|
||||
q.wait_dequeue(ctok,func);
|
||||
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
|
||||
|
||||
try
|
||||
{
|
||||
func();
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
SEMA_SIGNAL(sema);
|
||||
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline
|
||||
int
|
||||
ThreadPool::add_thread(const std::string name_)
|
||||
{
|
||||
int rv;
|
||||
pthread_t t;
|
||||
sigset_t oldset;
|
||||
sigset_t newset;
|
||||
std::string name;
|
||||
|
||||
name = (name_.empty() ? _name : name_);
|
||||
|
||||
sigfillset(&newset);
|
||||
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
|
||||
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
|
||||
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
|
||||
|
||||
if(rv != 0)
|
||||
{
|
||||
syslog(LOG_WARNING,
|
||||
"threadpool (%s): error spawning thread - %d (%s)",
|
||||
_name.c_str(),
|
||||
rv,
|
||||
strerror(rv));
|
||||
return -rv;
|
||||
}
|
||||
|
||||
if(!name.empty())
|
||||
pthread_setname_np(t,name.c_str());
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
_threads.push_back(t);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
int
|
||||
ThreadPool::remove_thread(void)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
if(_threads.size() <= 1)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
std::promise<pthread_t> promise;
|
||||
auto func =
|
||||
[this,&promise]()
|
||||
{
|
||||
pthread_t t;
|
||||
|
||||
t = pthread_self();
|
||||
promise.set_value(t);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
|
||||
for(auto i = _threads.begin(); i != _threads.end(); ++i)
|
||||
{
|
||||
if(*i != t)
|
||||
continue;
|
||||
|
||||
_threads.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_exit(NULL);
|
||||
};
|
||||
|
||||
enqueue_work(std::move(func));
|
||||
pthread_join(promise.get_future().get(),NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
int
|
||||
ThreadPool::set_threads(std::size_t const count_)
|
||||
{
|
||||
int diff;
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
|
||||
diff = ((int)count_ - (int)_threads.size());
|
||||
}
|
||||
|
||||
for(auto i = diff; i > 0; --i)
|
||||
add_thread();
|
||||
for(auto i = diff; i < 0; ++i)
|
||||
remove_thread();
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
void
|
||||
ThreadPool::shutdown(void)
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
|
||||
for(pthread_t tid : _threads)
|
||||
pthread_cancel(tid);
|
||||
for(pthread_t tid : _threads)
|
||||
pthread_join(tid,NULL);
|
||||
|
||||
_threads.clear();
|
||||
}
|
||||
|
||||
|
||||
template<typename FuncType>
|
||||
inline
|
||||
void
|
||||
ThreadPool::enqueue_work(ThreadPool::PToken &ptok_,
|
||||
FuncType &&func_)
|
||||
{
|
||||
SEMA_WAIT(_sema);
|
||||
_queue.enqueue(ptok_,
|
||||
std::forward<FuncType>(func_));
|
||||
}
|
||||
|
||||
|
||||
template<typename FuncType>
|
||||
inline
|
||||
void
|
||||
ThreadPool::enqueue_work(FuncType &&func_)
|
||||
{
|
||||
SEMA_WAIT(_sema);
|
||||
_queue.enqueue(std::forward<FuncType>(func_));
|
||||
}
|
||||
|
||||
|
||||
template<typename FuncType>
|
||||
[[nodiscard]]
|
||||
inline
|
||||
std::future<typename std::result_of<FuncType()>::type>
|
||||
ThreadPool::enqueue_task(FuncType&& func_)
|
||||
{
|
||||
using TaskReturnType = typename std::result_of<FuncType()>::type;
|
||||
using Promise = std::promise<TaskReturnType>;
|
||||
|
||||
Promise promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
auto work =
|
||||
[promise_ = std::move(promise),
|
||||
func_ = std::forward<FuncType>(func_)]() mutable
|
||||
{
|
||||
try
|
||||
{
|
||||
auto rv = func_();
|
||||
promise_.set_value(std::move(rv));
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
promise_.set_exception(std::current_exception());
|
||||
}
|
||||
};
|
||||
|
||||
SEMA_WAIT(_sema);
|
||||
_queue.enqueue(std::move(work));
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
std::vector<pthread_t>
|
||||
ThreadPool::threads() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
|
||||
return _threads;
|
||||
}
|
||||
|
||||
|
||||
inline
|
||||
ThreadPool::PToken
|
||||
ThreadPool::ptoken()
|
||||
{
|
||||
return ThreadPool::PToken(_queue);
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "fuse_i.h"
|
||||
#include "fuse_lowlevel.h"
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
|
||||
{
|
||||
size_t i;
|
||||
size_t size = 0;
|
||||
|
||||
for (i = 0; i < bufv->count; i++) {
|
||||
if (bufv->buf[i].size == SIZE_MAX)
|
||||
size = SIZE_MAX;
|
||||
else
|
||||
size += bufv->buf[i].size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static size_t min_size(size_t s1, size_t s2)
|
||||
{
|
||||
return s1 < s2 ? s1 : s2;
|
||||
}
|
||||
|
||||
static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
|
||||
const struct fuse_buf *src, size_t src_off,
|
||||
size_t len)
|
||||
{
|
||||
ssize_t res = 0;
|
||||
size_t copied = 0;
|
||||
|
||||
while (len) {
|
||||
if (dst->flags & FUSE_BUF_FD_SEEK) {
|
||||
res = pwrite(dst->fd, src->mem + src_off, len,
|
||||
dst->pos + dst_off);
|
||||
} else {
|
||||
res = write(dst->fd, src->mem + src_off, len);
|
||||
}
|
||||
if (res == -1) {
|
||||
if (!copied)
|
||||
return -errno;
|
||||
break;
|
||||
}
|
||||
if (res == 0)
|
||||
break;
|
||||
|
||||
copied += res;
|
||||
if (!(dst->flags & FUSE_BUF_FD_RETRY))
|
||||
break;
|
||||
|
||||
src_off += res;
|
||||
dst_off += res;
|
||||
len -= res;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
|
||||
const struct fuse_buf *src, size_t src_off,
|
||||
size_t len)
|
||||
{
|
||||
ssize_t res = 0;
|
||||
size_t copied = 0;
|
||||
|
||||
while (len) {
|
||||
if (src->flags & FUSE_BUF_FD_SEEK) {
|
||||
res = pread(src->fd, dst->mem + dst_off, len,
|
||||
src->pos + src_off);
|
||||
} else {
|
||||
res = read(src->fd, dst->mem + dst_off, len);
|
||||
}
|
||||
if (res == -1) {
|
||||
if (!copied)
|
||||
return -errno;
|
||||
break;
|
||||
}
|
||||
if (res == 0)
|
||||
break;
|
||||
|
||||
copied += res;
|
||||
if (!(src->flags & FUSE_BUF_FD_RETRY))
|
||||
break;
|
||||
|
||||
dst_off += res;
|
||||
src_off += res;
|
||||
len -= res;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
|
||||
const struct fuse_buf *src, size_t src_off,
|
||||
size_t len)
|
||||
{
|
||||
char buf[4096];
|
||||
struct fuse_buf tmp = {
|
||||
.size = sizeof(buf),
|
||||
.flags = 0,
|
||||
};
|
||||
ssize_t res;
|
||||
size_t copied = 0;
|
||||
|
||||
tmp.mem = buf;
|
||||
|
||||
while (len) {
|
||||
size_t this_len = min_size(tmp.size, len);
|
||||
size_t read_len;
|
||||
|
||||
res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
|
||||
if (res < 0) {
|
||||
if (!copied)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
if (res == 0)
|
||||
break;
|
||||
|
||||
read_len = res;
|
||||
res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
|
||||
if (res < 0) {
|
||||
if (!copied)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
if (res == 0)
|
||||
break;
|
||||
|
||||
copied += res;
|
||||
|
||||
if (res < this_len)
|
||||
break;
|
||||
|
||||
dst_off += res;
|
||||
src_off += res;
|
||||
len -= res;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SPLICE
|
||||
static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
|
||||
const struct fuse_buf *src, size_t src_off,
|
||||
size_t len, enum fuse_buf_copy_flags flags)
|
||||
{
|
||||
int splice_flags = 0;
|
||||
off_t *srcpos = NULL;
|
||||
off_t *dstpos = NULL;
|
||||
off_t srcpos_val;
|
||||
off_t dstpos_val;
|
||||
ssize_t res;
|
||||
size_t copied = 0;
|
||||
|
||||
if (flags & FUSE_BUF_SPLICE_MOVE)
|
||||
splice_flags |= SPLICE_F_MOVE;
|
||||
if (flags & FUSE_BUF_SPLICE_NONBLOCK)
|
||||
splice_flags |= SPLICE_F_NONBLOCK;
|
||||
|
||||
if (src->flags & FUSE_BUF_FD_SEEK) {
|
||||
srcpos_val = src->pos + src_off;
|
||||
srcpos = &srcpos_val;
|
||||
}
|
||||
if (dst->flags & FUSE_BUF_FD_SEEK) {
|
||||
dstpos_val = dst->pos + dst_off;
|
||||
dstpos = &dstpos_val;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
res = splice(src->fd, srcpos, dst->fd, dstpos, len, splice_flags);
|
||||
if (res == -1) {
|
||||
if (copied)
|
||||
break;
|
||||
|
||||
if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
|
||||
return -errno;
|
||||
|
||||
/* Maybe splice is not supported for this combination */
|
||||
return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
|
||||
len);
|
||||
}
|
||||
if (res == 0)
|
||||
break;
|
||||
|
||||
copied += res;
|
||||
if (!(src->flags & FUSE_BUF_FD_RETRY) &&
|
||||
!(dst->flags & FUSE_BUF_FD_RETRY)) {
|
||||
break;
|
||||
}
|
||||
|
||||
len -= res;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
#else
|
||||
static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
|
||||
const struct fuse_buf *src, size_t src_off,
|
||||
size_t len, enum fuse_buf_copy_flags flags)
|
||||
{
|
||||
(void) flags;
|
||||
|
||||
return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
|
||||
const struct fuse_buf *src, size_t src_off,
|
||||
size_t len, enum fuse_buf_copy_flags flags)
|
||||
{
|
||||
int src_is_fd = src->flags & FUSE_BUF_IS_FD;
|
||||
int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
|
||||
|
||||
if (!src_is_fd && !dst_is_fd) {
|
||||
char *dstmem = dst->mem + dst_off;
|
||||
char *srcmem = src->mem + src_off;
|
||||
|
||||
if (dstmem != srcmem) {
|
||||
if (dstmem + len <= srcmem || srcmem + len <= dstmem)
|
||||
memcpy(dstmem, srcmem, len);
|
||||
else
|
||||
memmove(dstmem, srcmem, len);
|
||||
}
|
||||
|
||||
return len;
|
||||
} else if (!src_is_fd) {
|
||||
return fuse_buf_write(dst, dst_off, src, src_off, len);
|
||||
} else if (!dst_is_fd) {
|
||||
return fuse_buf_read(dst, dst_off, src, src_off, len);
|
||||
} else if (flags & FUSE_BUF_NO_SPLICE) {
|
||||
return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
|
||||
} else {
|
||||
return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
|
||||
{
|
||||
if (bufv->idx < bufv->count)
|
||||
return &bufv->buf[bufv->idx];
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
|
||||
{
|
||||
const struct fuse_buf *buf = fuse_bufvec_current(bufv);
|
||||
|
||||
bufv->off += len;
|
||||
assert(bufv->off <= buf->size);
|
||||
if (bufv->off == buf->size) {
|
||||
assert(bufv->idx < bufv->count);
|
||||
bufv->idx++;
|
||||
if (bufv->idx == bufv->count)
|
||||
return 0;
|
||||
bufv->off = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
|
||||
enum fuse_buf_copy_flags flags)
|
||||
{
|
||||
size_t copied = 0;
|
||||
|
||||
if (dstv == srcv)
|
||||
return fuse_buf_size(dstv);
|
||||
|
||||
for (;;) {
|
||||
const struct fuse_buf *src = fuse_bufvec_current(srcv);
|
||||
const struct fuse_buf *dst = fuse_bufvec_current(dstv);
|
||||
size_t src_len;
|
||||
size_t dst_len;
|
||||
size_t len;
|
||||
ssize_t res;
|
||||
|
||||
if (src == NULL || dst == NULL)
|
||||
break;
|
||||
|
||||
src_len = src->size - srcv->off;
|
||||
dst_len = dst->size - dstv->off;
|
||||
len = min_size(src_len, dst_len);
|
||||
|
||||
res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
|
||||
if (res < 0) {
|
||||
if (!copied)
|
||||
return res;
|
||||
break;
|
||||
}
|
||||
copied += res;
|
||||
|
||||
if (!fuse_bufvec_advance(srcv, res) ||
|
||||
!fuse_bufvec_advance(dstv, res))
|
||||
break;
|
||||
|
||||
if (res < len)
|
||||
break;
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,380 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2021, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kvec.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ROUND_UP(N,S) ((((N) + (S) - 1) / (S)) * (S))
|
||||
|
||||
typedef kvec_t(void*) slab_kvec_t;
|
||||
|
||||
typedef struct mem_stack_t mem_stack_t;
|
||||
struct mem_stack_t
|
||||
{
|
||||
mem_stack_t *next;
|
||||
};
|
||||
|
||||
typedef struct fmp_t fmp_t;
|
||||
struct fmp_t
|
||||
{
|
||||
mem_stack_t *objs;
|
||||
slab_kvec_t slabs;
|
||||
uint64_t avail_objs;
|
||||
uint64_t obj_size;
|
||||
uint64_t page_size;
|
||||
uint64_t slab_size;
|
||||
};
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
fmp_page_size()
|
||||
{
|
||||
return sysconf(_SC_PAGESIZE);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_init(fmp_t *fmp_,
|
||||
const uint64_t obj_size_,
|
||||
const uint64_t page_multiple_)
|
||||
{
|
||||
kv_init(fmp_->slabs);
|
||||
fmp_->objs = NULL;
|
||||
fmp_->avail_objs = 0;
|
||||
fmp_->obj_size = ROUND_UP(obj_size_,sizeof(void*));
|
||||
fmp_->page_size = fmp_page_size();
|
||||
fmp_->slab_size = (fmp_->page_size * page_multiple_);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
fmp_slab_count(fmp_t *fmp_)
|
||||
{
|
||||
return kv_size(fmp_->slabs);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void*
|
||||
fmp_slab_alloc_posix_memalign(fmp_t *fmp_)
|
||||
{
|
||||
int rv;
|
||||
void *mem;
|
||||
const size_t alignment = fmp_->page_size;
|
||||
const size_t size = fmp_->slab_size;
|
||||
|
||||
rv = posix_memalign(&mem,alignment,size);
|
||||
if(rv != 0)
|
||||
return NULL;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void*
|
||||
fmp_slab_alloc_mmap(fmp_t *fmp_)
|
||||
{
|
||||
void *mem;
|
||||
void *address = NULL;
|
||||
const size_t length = fmp_->slab_size;
|
||||
const int protect = PROT_READ|PROT_WRITE;
|
||||
const int flags = MAP_PRIVATE|MAP_ANONYMOUS;
|
||||
const int filedes = -1;
|
||||
const off_t offset = 0;
|
||||
|
||||
mem = mmap(address,length,protect,flags,filedes,offset);
|
||||
if(mem == MAP_FAILED)
|
||||
return NULL;
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_slab_free_posix_memalign(fmp_t* fmp_,
|
||||
void *mem_)
|
||||
{
|
||||
(void)fmp_;
|
||||
free(mem_);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_slab_free_mmap(fmp_t* fmp_,
|
||||
void *mem_)
|
||||
{
|
||||
void *addr = mem_;
|
||||
size_t length = fmp_->slab_size;
|
||||
|
||||
(void)munmap(addr,length);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
fmp_slab_alloc(fmp_t *fmp_)
|
||||
{
|
||||
char *i;
|
||||
void *mem;
|
||||
|
||||
mem = fmp_slab_alloc_mmap(fmp_);
|
||||
if(mem == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
kv_push(void*,fmp_->slabs,mem);
|
||||
|
||||
i = ((char*)mem + fmp_->slab_size - fmp_->obj_size);
|
||||
while(i >= (char*)mem)
|
||||
{
|
||||
mem_stack_t *obj = (mem_stack_t*)i;
|
||||
|
||||
obj->next = fmp_->objs;
|
||||
fmp_->objs = obj;
|
||||
fmp_->avail_objs++;
|
||||
|
||||
i -= fmp_->obj_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void*
|
||||
fmp_alloc(fmp_t *fmp_)
|
||||
{
|
||||
void *rv;
|
||||
|
||||
if(fmp_->objs == NULL)
|
||||
fmp_slab_alloc(fmp_);
|
||||
if(fmp_->objs == NULL)
|
||||
return NULL;
|
||||
|
||||
rv = fmp_->objs;
|
||||
|
||||
fmp_->objs = fmp_->objs->next;
|
||||
fmp_->avail_objs--;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void*
|
||||
fmp_calloc(fmp_t *fmp_)
|
||||
{
|
||||
void *obj;
|
||||
|
||||
obj = fmp_alloc(fmp_);
|
||||
if(obj == NULL)
|
||||
return NULL;
|
||||
|
||||
memset(obj,0,fmp_->obj_size);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_free(fmp_t *fmp_,
|
||||
void *obj_)
|
||||
{
|
||||
mem_stack_t *obj = (mem_stack_t*)obj_;
|
||||
|
||||
obj->next = fmp_->objs;
|
||||
fmp_->objs = obj;
|
||||
fmp_->avail_objs++;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_clear(fmp_t *fmp_)
|
||||
{
|
||||
while(kv_size(fmp_->slabs))
|
||||
{
|
||||
void *slab = kv_pop(fmp_->slabs);
|
||||
|
||||
fmp_slab_free_mmap(fmp_,slab);
|
||||
}
|
||||
|
||||
fmp_->objs = NULL;
|
||||
fmp_->avail_objs = 0;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_destroy(fmp_t *fmp_)
|
||||
{
|
||||
fmp_clear(fmp_);
|
||||
kv_destroy(fmp_->slabs);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
fmp_avail_objs(fmp_t *fmp_)
|
||||
{
|
||||
return fmp_->avail_objs;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
fmp_objs_per_slab(fmp_t *fmp_)
|
||||
{
|
||||
return (fmp_->slab_size / fmp_->obj_size);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
fmp_objs_in_slab(fmp_t *fmp_,
|
||||
void *slab_)
|
||||
{
|
||||
char *slab;
|
||||
uint64_t objs_per_slab;
|
||||
uint64_t objs_in_slab;
|
||||
|
||||
slab = (char*)slab_;
|
||||
objs_in_slab = 0;
|
||||
objs_per_slab = fmp_objs_per_slab(fmp_);
|
||||
for(mem_stack_t *stack = fmp_->objs; stack != NULL; stack = stack->next)
|
||||
{
|
||||
char *obj = (char*)stack;
|
||||
if((obj >= slab) && (obj < (slab + fmp_->slab_size)))
|
||||
objs_in_slab++;
|
||||
if(objs_in_slab >= objs_per_slab)
|
||||
break;
|
||||
}
|
||||
|
||||
return objs_in_slab;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
fmp_remove_objs_in_slab(fmp_t *fmp_,
|
||||
void *slab_)
|
||||
{
|
||||
char *slab;
|
||||
uint64_t objs_per_slab;
|
||||
uint64_t objs_in_slab;
|
||||
mem_stack_t **p;
|
||||
|
||||
p = &fmp_->objs;
|
||||
slab = (char*)slab_;
|
||||
objs_in_slab = 0;
|
||||
objs_per_slab = fmp_objs_per_slab(fmp_);
|
||||
while((*p) != NULL)
|
||||
{
|
||||
char *obj = (char*)*p;
|
||||
|
||||
if((obj >= slab) && (obj < (slab + fmp_->slab_size)))
|
||||
{
|
||||
objs_in_slab++;
|
||||
*p = (*p)->next;
|
||||
fmp_->avail_objs--;
|
||||
|
||||
if(objs_in_slab >= objs_per_slab)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
p = &(*p)->next;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
fmp_gc_slab(fmp_t *fmp_,
|
||||
uint64_t slab_idx_)
|
||||
{
|
||||
char *slab;
|
||||
uint64_t objs_in_slab;
|
||||
uint64_t objs_per_slab;
|
||||
|
||||
slab_idx_ = (slab_idx_ % kv_size(fmp_->slabs));
|
||||
|
||||
slab = (char*)kv_A(fmp_->slabs,slab_idx_);
|
||||
|
||||
objs_per_slab = fmp_objs_per_slab(fmp_);
|
||||
objs_in_slab = fmp_objs_in_slab(fmp_,slab);
|
||||
if(objs_in_slab != objs_per_slab)
|
||||
return 0;
|
||||
|
||||
fmp_remove_objs_in_slab(fmp_,slab);
|
||||
kv_delete(fmp_->slabs,slab_idx_);
|
||||
fmp_slab_free_mmap(fmp_,slab);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
fmp_gc(fmp_t *fmp_)
|
||||
{
|
||||
uint64_t slab_idx;
|
||||
|
||||
slab_idx = rand();
|
||||
|
||||
return fmp_gc_slab(fmp_,slab_idx);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
double
|
||||
fmp_slab_usage_ratio(fmp_t *fmp_)
|
||||
{
|
||||
double avail_objs;
|
||||
double objs_per_slab;
|
||||
double nums_of_slabs;
|
||||
|
||||
avail_objs = fmp_->avail_objs;
|
||||
objs_per_slab = fmp_objs_per_slab(fmp_);
|
||||
nums_of_slabs = kv_size(fmp_->slabs);
|
||||
|
||||
return (avail_objs / (objs_per_slab * nums_of_slabs));
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
fmp_total_allocated_memory(fmp_t *fmp_)
|
||||
{
|
||||
return (fmp_->slab_size * kv_size(fmp_->slabs));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
|
||||
static int g_READ_THREAD_COUNT = -1;
|
||||
static int g_PROCESS_THREAD_COUNT = -1;
|
||||
static int g_PROCESS_THREAD_QUEUE_DEPTH = -1;
|
||||
static std::string g_PIN_THREADS = {};
|
||||
|
||||
|
||||
int
|
||||
fuse_config_get_read_thread_count()
|
||||
{
|
||||
return g_READ_THREAD_COUNT;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_config_set_read_thread_count(int const v_)
|
||||
{
|
||||
g_READ_THREAD_COUNT = v_;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_config_get_process_thread_count()
|
||||
{
|
||||
return g_PROCESS_THREAD_COUNT;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_config_set_process_thread_count(int const v_)
|
||||
{
|
||||
g_PROCESS_THREAD_COUNT = v_;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_config_get_process_thread_queue_depth()
|
||||
{
|
||||
return g_PROCESS_THREAD_QUEUE_DEPTH;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_config_set_process_thread_queue_depth(int const v_)
|
||||
{
|
||||
g_PROCESS_THREAD_QUEUE_DEPTH = v_;
|
||||
}
|
||||
|
||||
std::string
|
||||
fuse_config_get_pin_threads()
|
||||
{
|
||||
return g_PIN_THREADS;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_config_set_pin_threads(std::string const v_)
|
||||
{
|
||||
g_PIN_THREADS = v_;
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#include "fuse_attr.h"
|
||||
#include "fuse_dirent.h"
|
||||
#include "fuse_direntplus.h"
|
||||
#include "fuse_dirents.h"
|
||||
#include "fuse_entry.h"
|
||||
#include "linux_dirent64.h"
|
||||
#include "stat_utils.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 32KB - same as glibc getdents buffer size */
|
||||
#define DEFAULT_SIZE (1024 * 32)
|
||||
|
||||
static
|
||||
uint64_t
|
||||
round_up(const uint64_t number_,
|
||||
const uint64_t multiple_)
|
||||
{
|
||||
return (((number_ + multiple_ - 1) / multiple_) * multiple_);
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t
|
||||
align_uint64_t(uint64_t v_)
|
||||
{
|
||||
return ((v_ + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1));
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t
|
||||
fuse_dirent_size(const uint64_t namelen_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
rv = offsetof(fuse_dirent_t,name);
|
||||
rv += namelen_;
|
||||
rv = align_uint64_t(rv);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
uint64_t
|
||||
fuse_direntplus_size(const uint64_t namelen_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
rv = offsetof(fuse_direntplus_t,dirent.name);
|
||||
rv += namelen_;
|
||||
rv = align_uint64_t(rv);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
fuse_dirents_buf_resize(fuse_dirents_t *d_,
|
||||
uint64_t size_)
|
||||
{
|
||||
if((kv_size(d_->data) + size_) >= kv_max(d_->data))
|
||||
{
|
||||
uint64_t new_size;
|
||||
|
||||
new_size = round_up((kv_size(d_->data) + size_),DEFAULT_SIZE);
|
||||
|
||||
kv_resize(char,d_->data,new_size);
|
||||
if(d_->data.a == NULL)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
void*
|
||||
fuse_dirents_dirent_alloc(fuse_dirents_t *d_,
|
||||
uint64_t namelen_)
|
||||
{
|
||||
int rv;
|
||||
uint64_t size;
|
||||
fuse_dirent_t *d;
|
||||
|
||||
size = fuse_dirent_size(namelen_);
|
||||
|
||||
rv = fuse_dirents_buf_resize(d_,size);
|
||||
if(rv)
|
||||
return NULL;
|
||||
|
||||
d = (fuse_dirent_t*)&kv_end(d_->data);
|
||||
kv_size(d_->data) += size;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static
|
||||
void*
|
||||
fuse_dirents_direntplus_alloc(fuse_dirents_t *d_,
|
||||
uint64_t namelen_)
|
||||
{
|
||||
int rv;
|
||||
uint64_t size;
|
||||
fuse_dirent_t *d;
|
||||
|
||||
size = fuse_direntplus_size(namelen_);
|
||||
|
||||
rv = fuse_dirents_buf_resize(d_,size);
|
||||
if(rv)
|
||||
return NULL;
|
||||
|
||||
d = (fuse_dirent_t*)&kv_end(d_->data);
|
||||
kv_size(d_->data) += size;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
fuse_dirents_fill_attr(fuse_attr_t *attr_,
|
||||
const struct stat *st_)
|
||||
{
|
||||
attr_->ino = st_->st_ino;
|
||||
attr_->size = st_->st_size;
|
||||
attr_->blocks = st_->st_blocks;
|
||||
attr_->atime = st_->st_atime;
|
||||
attr_->mtime = st_->st_mtime;
|
||||
attr_->ctime = st_->st_ctime;
|
||||
attr_->atimensec = ST_ATIM_NSEC(st_);
|
||||
attr_->mtimensec = ST_MTIM_NSEC(st_);
|
||||
attr_->ctimensec = ST_CTIM_NSEC(st_);
|
||||
attr_->mode = st_->st_mode;
|
||||
attr_->nlink = st_->st_nlink;
|
||||
attr_->uid = st_->st_uid;
|
||||
attr_->gid = st_->st_gid;
|
||||
attr_->rdev = st_->st_rdev;
|
||||
attr_->blksize = st_->st_blksize;
|
||||
}
|
||||
|
||||
fuse_dirent_t*
|
||||
fuse_dirent_next(fuse_dirent_t *cur_)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
buf = (char*)cur_;
|
||||
buf += fuse_dirent_size(cur_->namelen);
|
||||
|
||||
return (fuse_dirent_t*)buf;
|
||||
}
|
||||
|
||||
fuse_direntplus_t*
|
||||
fuse_direntplus_next(fuse_direntplus_t *cur_)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
buf = (char*)cur_;
|
||||
buf += fuse_direntplus_size(cur_->dirent.namelen);
|
||||
|
||||
return (fuse_direntplus_t*)buf;
|
||||
}
|
||||
|
||||
fuse_dirent_t*
|
||||
fuse_dirent_find(fuse_dirents_t *d_,
|
||||
const uint64_t ino_)
|
||||
{
|
||||
fuse_dirent_t *cur;
|
||||
fuse_dirent_t *end;
|
||||
|
||||
if(d_->type != NORMAL)
|
||||
return NULL;
|
||||
|
||||
cur = (fuse_dirent_t*)&kv_first(d_->data);
|
||||
end = (fuse_dirent_t*)&kv_end(d_->data);
|
||||
while(cur < end)
|
||||
{
|
||||
if(cur->ino == ino_)
|
||||
return cur;
|
||||
|
||||
cur = fuse_dirent_next(cur);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fuse_direntplus_t*
|
||||
fuse_direntplus_find(fuse_dirents_t *d_,
|
||||
const uint64_t ino_)
|
||||
{
|
||||
fuse_direntplus_t *cur;
|
||||
fuse_direntplus_t *end;
|
||||
|
||||
if(d_->type != PLUS)
|
||||
return NULL;
|
||||
|
||||
cur = (fuse_direntplus_t*)&kv_first(d_->data);
|
||||
end = (fuse_direntplus_t*)&kv_end(d_->data);
|
||||
while(cur < end)
|
||||
{
|
||||
if(cur->dirent.ino == ino_)
|
||||
return cur;
|
||||
|
||||
cur = fuse_direntplus_next(cur);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void*
|
||||
fuse_dirents_find(fuse_dirents_t *d_,
|
||||
const uint64_t ino_)
|
||||
{
|
||||
switch(d_->type)
|
||||
{
|
||||
default:
|
||||
case UNSET:
|
||||
return NULL;
|
||||
case NORMAL:
|
||||
return fuse_dirent_find(d_,ino_);
|
||||
case PLUS:
|
||||
return fuse_direntplus_find(d_,ino_);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
fuse_dirents_convert_plus2normal(fuse_dirents_t *d_)
|
||||
{
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_dirents_add(fuse_dirents_t *d_,
|
||||
const struct dirent *dirent_,
|
||||
const uint64_t namelen_)
|
||||
{
|
||||
fuse_dirent_t *d;
|
||||
|
||||
switch(d_->type)
|
||||
{
|
||||
case UNSET:
|
||||
d_->type = NORMAL;
|
||||
break;
|
||||
case NORMAL:
|
||||
break;
|
||||
case PLUS:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
d = fuse_dirents_dirent_alloc(d_,namelen_);
|
||||
if(d == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
d->off = kv_size(d_->offs);
|
||||
kv_push(uint32_t,d_->offs,kv_size(d_->data));
|
||||
d->ino = dirent_->d_ino;
|
||||
d->namelen = namelen_;
|
||||
d->type = dirent_->d_type;
|
||||
memcpy(d->name,dirent_->d_name,namelen_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_dirents_add_plus(fuse_dirents_t *d_,
|
||||
const struct dirent *dirent_,
|
||||
const uint64_t namelen_,
|
||||
const fuse_entry_t *entry_,
|
||||
const struct stat *st_)
|
||||
{
|
||||
fuse_direntplus_t *d;
|
||||
|
||||
switch(d_->type)
|
||||
{
|
||||
case UNSET:
|
||||
d_->type = PLUS;
|
||||
break;
|
||||
case NORMAL:
|
||||
return -EINVAL;
|
||||
case PLUS:
|
||||
break;
|
||||
}
|
||||
|
||||
d = fuse_dirents_direntplus_alloc(d_,namelen_);
|
||||
if(d == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
d->dirent.off = kv_size(d_->offs);
|
||||
kv_push(uint32_t,d_->offs,kv_size(d_->data));
|
||||
d->dirent.ino = dirent_->d_ino;
|
||||
d->dirent.namelen = namelen_;
|
||||
d->dirent.type = dirent_->d_type;
|
||||
memcpy(d->dirent.name,dirent_->d_name,namelen_);
|
||||
|
||||
d->entry = *entry_;
|
||||
|
||||
fuse_dirents_fill_attr(&d->attr,st_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_dirents_add_linux(fuse_dirents_t *d_,
|
||||
const linux_dirent64_t *dirent_,
|
||||
const uint64_t namelen_)
|
||||
{
|
||||
fuse_dirent_t *d;
|
||||
|
||||
switch(d_->type)
|
||||
{
|
||||
case UNSET:
|
||||
d_->type = NORMAL;
|
||||
break;
|
||||
case NORMAL:
|
||||
break;
|
||||
case PLUS:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
d = fuse_dirents_dirent_alloc(d_,namelen_);
|
||||
if(d == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
d->off = kv_size(d_->offs);
|
||||
kv_push(uint32_t,d_->offs,kv_size(d_->data));
|
||||
d->ino = dirent_->ino;
|
||||
d->namelen = namelen_;
|
||||
d->type = dirent_->type;
|
||||
memcpy(d->name,dirent_->name,namelen_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_dirents_add_linux_plus(fuse_dirents_t *d_,
|
||||
const linux_dirent64_t *dirent_,
|
||||
const uint64_t namelen_,
|
||||
const fuse_entry_t *entry_,
|
||||
const struct stat *st_)
|
||||
{
|
||||
fuse_direntplus_t *d;
|
||||
|
||||
switch(d_->type)
|
||||
{
|
||||
case UNSET:
|
||||
d_->type = PLUS;
|
||||
break;
|
||||
case NORMAL:
|
||||
return -EINVAL;
|
||||
case PLUS:
|
||||
break;
|
||||
}
|
||||
|
||||
d = fuse_dirents_direntplus_alloc(d_,namelen_);
|
||||
if(d == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
d->dirent.off = kv_size(d_->offs);
|
||||
kv_push(uint32_t,d_->offs,kv_size(d_->data));
|
||||
d->dirent.ino = dirent_->ino;
|
||||
d->dirent.namelen = namelen_;
|
||||
d->dirent.type = dirent_->type;
|
||||
memcpy(d->dirent.name,dirent_->name,namelen_);
|
||||
|
||||
d->entry = *entry_;
|
||||
|
||||
fuse_dirents_fill_attr(&d->attr,st_);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_dirents_reset(fuse_dirents_t *d_)
|
||||
{
|
||||
d_->type = UNSET;
|
||||
kv_size(d_->data) = 0;
|
||||
kv_size(d_->offs) = 1;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_dirents_init(fuse_dirents_t *d_)
|
||||
{
|
||||
d_->type = UNSET;
|
||||
|
||||
kv_init(d_->data);
|
||||
kv_resize(char,d_->data,DEFAULT_SIZE);
|
||||
if(d_->data.a == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
kv_init(d_->offs);
|
||||
kv_resize(uint32_t,d_->offs,64);
|
||||
kv_push(uint32_t,d_->offs,0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_dirents_free(fuse_dirents_t *d_)
|
||||
{
|
||||
kv_destroy(d_->data);
|
||||
kv_destroy(d_->offs);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fuse.h"
|
||||
#include "fuse_lowlevel.h"
|
||||
#include "fuse_msgbuf.h"
|
||||
|
||||
#include "extern_c.h"
|
||||
|
||||
struct fuse_chan;
|
||||
struct fuse_ll;
|
||||
|
||||
struct fuse_session
|
||||
{
|
||||
int (*receive_buf)(struct fuse_session *se,
|
||||
fuse_msgbuf_t *msgbuf);
|
||||
|
||||
void (*process_buf)(struct fuse_session *se,
|
||||
const fuse_msgbuf_t *msgbuf);
|
||||
|
||||
void (*destroy)(void *data);
|
||||
|
||||
struct fuse_ll *f;
|
||||
volatile int exited;
|
||||
struct fuse_chan *ch;
|
||||
};
|
||||
|
||||
struct fuse_req
|
||||
{
|
||||
struct fuse_ll *f;
|
||||
uint64_t unique;
|
||||
struct fuse_ctx ctx;
|
||||
struct fuse_chan *ch;
|
||||
unsigned int ioctl_64bit : 1;
|
||||
};
|
||||
|
||||
struct fuse_notify_req
|
||||
{
|
||||
uint64_t unique;
|
||||
void (*reply)(struct fuse_notify_req *,
|
||||
fuse_req_t,
|
||||
uint64_t,
|
||||
const void *);
|
||||
struct fuse_notify_req *next;
|
||||
struct fuse_notify_req *prev;
|
||||
};
|
||||
|
||||
struct fuse_ll
|
||||
{
|
||||
int debug;
|
||||
int no_remote_posix_lock;
|
||||
int no_remote_flock;
|
||||
int big_writes;
|
||||
struct fuse_lowlevel_ops op;
|
||||
void *userdata;
|
||||
uid_t owner;
|
||||
struct fuse_conn_info conn;
|
||||
pthread_mutex_t lock;
|
||||
int got_init;
|
||||
int got_destroy;
|
||||
pthread_key_t pipe_key;
|
||||
int broken_splice_nonblock;
|
||||
uint64_t notify_ctr;
|
||||
struct fuse_notify_req notify_list;
|
||||
};
|
||||
|
||||
struct fuse_cmd
|
||||
{
|
||||
char *buf;
|
||||
size_t buflen;
|
||||
struct fuse_chan *ch;
|
||||
};
|
||||
|
||||
EXTERN_C_BEGIN
|
||||
|
||||
struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args,
|
||||
const struct fuse_operations *op,
|
||||
size_t op_size);
|
||||
|
||||
struct fuse_chan *fuse_kern_chan_new(int fd);
|
||||
|
||||
struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
|
||||
const struct fuse_lowlevel_ops *op,
|
||||
size_t op_size, void *userdata);
|
||||
|
||||
int fuse_chan_clearfd(struct fuse_chan *ch);
|
||||
|
||||
void fuse_kern_unmount(const char *mountpoint, int fd);
|
||||
int fuse_kern_mount(const char *mountpoint, struct fuse_args *args);
|
||||
|
||||
int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
|
||||
int count);
|
||||
void fuse_free_req(fuse_req_t req);
|
||||
|
||||
|
||||
struct fuse *fuse_setup_common(int argc, char *argv[],
|
||||
const struct fuse_operations *op,
|
||||
size_t op_size,
|
||||
char **mountpoint,
|
||||
int *fd);
|
||||
|
||||
int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
|
||||
|
||||
EXTERN_C_END
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "fuse_msgbuf.h"
|
||||
|
||||
int fuse_receive_buf(struct fuse_session *se,
|
||||
fuse_msgbuf_t *msgbuf);
|
||||
void fuse_process_buf(void *data,
|
||||
const fuse_msgbuf_t *msgbuf,
|
||||
struct fuse_chan *ch);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2022, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "fuse_msgbuf.hpp"
|
||||
#include "fuse.h"
|
||||
#include "fuse_kernel.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
|
||||
static std::uint32_t g_PAGESIZE = 0;
|
||||
static std::uint32_t g_BUFSIZE = 0;
|
||||
|
||||
static std::atomic<std::uint_fast64_t> g_MSGBUF_ALLOC_COUNT;
|
||||
|
||||
static std::mutex g_MUTEX;
|
||||
static std::vector<fuse_msgbuf_t*> g_MSGBUF_STACK;
|
||||
|
||||
uint64_t
|
||||
msgbuf_get_bufsize()
|
||||
{
|
||||
return g_BUFSIZE;
|
||||
}
|
||||
|
||||
void
|
||||
msgbuf_set_bufsize(const uint32_t size_in_pages_)
|
||||
{
|
||||
g_BUFSIZE = ((size_in_pages_ + 1) * g_PAGESIZE);
|
||||
}
|
||||
|
||||
void
|
||||
msgbuf_page_align(fuse_msgbuf_t *msgbuf_)
|
||||
{
|
||||
msgbuf_->mem = (char*)msgbuf_;
|
||||
msgbuf_->mem += g_PAGESIZE;
|
||||
msgbuf_->size = (g_BUFSIZE - g_PAGESIZE);
|
||||
}
|
||||
|
||||
void
|
||||
msgbuf_write_align(fuse_msgbuf_t *msgbuf_)
|
||||
{
|
||||
msgbuf_->mem = (char*)msgbuf_;
|
||||
msgbuf_->mem += g_PAGESIZE;
|
||||
msgbuf_->mem -= sizeof(struct fuse_in_header);
|
||||
msgbuf_->mem -= sizeof(struct fuse_write_in);
|
||||
msgbuf_->size = (g_BUFSIZE - g_PAGESIZE);
|
||||
}
|
||||
|
||||
static
|
||||
__attribute__((constructor))
|
||||
void
|
||||
msgbuf_constructor()
|
||||
{
|
||||
g_PAGESIZE = sysconf(_SC_PAGESIZE);
|
||||
// FUSE_MAX_MAX_PAGES for payload + 1 for message header
|
||||
msgbuf_set_bufsize(FUSE_DEFAULT_MAX_MAX_PAGES + 1);
|
||||
}
|
||||
|
||||
static
|
||||
__attribute__((destructor))
|
||||
void
|
||||
msgbuf_destroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static
|
||||
void*
|
||||
page_aligned_malloc(const uint64_t size_)
|
||||
{
|
||||
int rv;
|
||||
void *buf = NULL;
|
||||
|
||||
rv = posix_memalign(&buf,g_PAGESIZE,size_);
|
||||
if(rv != 0)
|
||||
return NULL;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
typedef void (*msgbuf_setup_func_t)(fuse_msgbuf_t*);
|
||||
|
||||
static
|
||||
fuse_msgbuf_t*
|
||||
_msgbuf_alloc(msgbuf_setup_func_t setup_func_)
|
||||
{
|
||||
fuse_msgbuf_t *msgbuf;
|
||||
|
||||
g_MUTEX.lock();
|
||||
if(g_MSGBUF_STACK.empty())
|
||||
{
|
||||
g_MUTEX.unlock();
|
||||
|
||||
msgbuf = (fuse_msgbuf_t*)page_aligned_malloc(g_BUFSIZE);
|
||||
if(msgbuf == NULL)
|
||||
return NULL;
|
||||
|
||||
g_MSGBUF_ALLOC_COUNT.fetch_add(1,std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
msgbuf = g_MSGBUF_STACK.back();
|
||||
g_MSGBUF_STACK.pop_back();
|
||||
g_MUTEX.unlock();
|
||||
}
|
||||
|
||||
setup_func_(msgbuf);
|
||||
|
||||
return msgbuf;
|
||||
}
|
||||
|
||||
// Offset the memory so write request payload will be placed on page
|
||||
// boundry so O_DIRECT can work. No impact on other message types
|
||||
// except for `read` which will require using `msgbuf_page_align`.
|
||||
fuse_msgbuf_t*
|
||||
msgbuf_alloc()
|
||||
{
|
||||
return _msgbuf_alloc(msgbuf_write_align);
|
||||
}
|
||||
|
||||
fuse_msgbuf_t*
|
||||
msgbuf_alloc_page_aligned()
|
||||
{
|
||||
return _msgbuf_alloc(msgbuf_page_align);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
msgbuf_destroy(fuse_msgbuf_t *msgbuf_)
|
||||
{
|
||||
// free(msgbuf_->mem);
|
||||
free(msgbuf_);
|
||||
}
|
||||
|
||||
void
|
||||
msgbuf_free(fuse_msgbuf_t *msgbuf_)
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(g_MUTEX);
|
||||
|
||||
if(msgbuf_->size != (g_BUFSIZE - g_PAGESIZE))
|
||||
{
|
||||
msgbuf_destroy(msgbuf_);
|
||||
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
g_MSGBUF_STACK.emplace_back(msgbuf_);
|
||||
|
||||
}
|
||||
|
||||
uint64_t
|
||||
msgbuf_alloc_count()
|
||||
{
|
||||
return g_MSGBUF_ALLOC_COUNT;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
msgbuf_avail_count()
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(g_MUTEX);
|
||||
|
||||
return g_MSGBUF_STACK.size();
|
||||
}
|
||||
|
||||
void
|
||||
msgbuf_gc_10percent()
|
||||
{
|
||||
std::vector<fuse_msgbuf_t*> togc;
|
||||
|
||||
{
|
||||
std::size_t size;
|
||||
std::size_t ten_percent;
|
||||
|
||||
std::lock_guard<std::mutex> lck(g_MUTEX);
|
||||
|
||||
size = g_MSGBUF_STACK.size();
|
||||
ten_percent = (size / 10);
|
||||
|
||||
for(std::size_t i = 0; i < ten_percent; i++)
|
||||
{
|
||||
togc.push_back(g_MSGBUF_STACK.back());
|
||||
g_MSGBUF_STACK.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
for(auto msgbuf : togc)
|
||||
{
|
||||
msgbuf_destroy(msgbuf);
|
||||
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
msgbuf_gc()
|
||||
{
|
||||
std::vector<fuse_msgbuf_t*> oldstack;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lck(g_MUTEX);
|
||||
oldstack.swap(g_MSGBUF_STACK);
|
||||
}
|
||||
|
||||
for(auto msgbuf: oldstack)
|
||||
{
|
||||
msgbuf_destroy(msgbuf);
|
||||
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
#include "fuse_kernel.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
|
||||
struct fuse_chan
|
||||
{
|
||||
struct fuse_session *se;
|
||||
int fd;
|
||||
size_t bufsize;
|
||||
};
|
||||
|
||||
struct fuse_session *
|
||||
fuse_session_new(void *data,
|
||||
void *receive_buf,
|
||||
void *process_buf,
|
||||
void *destroy)
|
||||
{
|
||||
struct fuse_session *se = (struct fuse_session *) malloc(sizeof(*se));
|
||||
if (se == NULL) {
|
||||
fprintf(stderr, "fuse: failed to allocate session\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(se, 0, sizeof(*se));
|
||||
se->f = data;
|
||||
se->receive_buf = receive_buf;
|
||||
se->process_buf = process_buf;
|
||||
se->destroy = destroy;
|
||||
|
||||
return se;
|
||||
}
|
||||
|
||||
void fuse_session_add_chan(struct fuse_session *se, struct fuse_chan *ch)
|
||||
{
|
||||
assert(se->ch == NULL);
|
||||
assert(ch->se == NULL);
|
||||
se->ch = ch;
|
||||
ch->se = se;
|
||||
}
|
||||
|
||||
void fuse_session_remove_chan(struct fuse_chan *ch)
|
||||
{
|
||||
struct fuse_session *se = ch->se;
|
||||
if (se) {
|
||||
assert(se->ch == ch);
|
||||
se->ch = NULL;
|
||||
ch->se = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fuse_session_destroy(struct fuse_session *se)
|
||||
{
|
||||
se->destroy(se->f);
|
||||
if(se->ch != NULL)
|
||||
fuse_chan_destroy(se->ch);
|
||||
free(se);
|
||||
}
|
||||
|
||||
void fuse_session_reset(struct fuse_session *se)
|
||||
{
|
||||
se->exited = 0;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_session_exited(struct fuse_session *se)
|
||||
{
|
||||
return se->exited;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_session_exit(struct fuse_session *se_)
|
||||
{
|
||||
se_->exited = 1;
|
||||
}
|
||||
|
||||
void*
|
||||
fuse_session_data(struct fuse_session *se)
|
||||
{
|
||||
return se->f;
|
||||
}
|
||||
|
||||
struct fuse_chan *
|
||||
fuse_chan_new(int fd,
|
||||
size_t bufsize)
|
||||
{
|
||||
struct fuse_chan *ch;
|
||||
|
||||
ch = (struct fuse_chan*)malloc(sizeof(*ch));
|
||||
if(ch == NULL)
|
||||
{
|
||||
fprintf(stderr, "fuse: failed to allocate channel\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(ch, 0, sizeof(*ch));
|
||||
|
||||
ch->fd = fd;
|
||||
ch->bufsize = bufsize;
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
int fuse_chan_fd(struct fuse_chan *ch)
|
||||
{
|
||||
return ch->fd;
|
||||
}
|
||||
|
||||
int fuse_chan_clearfd(struct fuse_chan *ch)
|
||||
{
|
||||
int fd = ch->fd;
|
||||
ch->fd = -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
size_t fuse_chan_bufsize(struct fuse_chan *ch)
|
||||
{
|
||||
return ch->bufsize;
|
||||
}
|
||||
|
||||
struct fuse_session *fuse_chan_session(struct fuse_chan *ch)
|
||||
{
|
||||
return ch->se;
|
||||
}
|
||||
|
||||
void
|
||||
fuse_chan_destroy(struct fuse_chan *ch)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fuse_session_remove_chan(ch);
|
||||
|
||||
fd = fuse_chan_fd(ch);
|
||||
if(fd != -1)
|
||||
close(fd);
|
||||
|
||||
free(ch);
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB.
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
#include "fuse_opt.h"
|
||||
#include "fuse_lowlevel.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <errno.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
enum {
|
||||
KEY_HELP,
|
||||
KEY_HELP_NOHEADER,
|
||||
KEY_VERSION,
|
||||
};
|
||||
|
||||
struct helper_opts
|
||||
{
|
||||
int foreground;
|
||||
int nodefault_subtype;
|
||||
char *mountpoint;
|
||||
};
|
||||
|
||||
#define FUSE_HELPER_OPT(t, p) { t, offsetof(struct helper_opts, p), 1 }
|
||||
|
||||
static
|
||||
const
|
||||
struct fuse_opt fuse_helper_opts[] =
|
||||
{
|
||||
FUSE_HELPER_OPT("-f", foreground),
|
||||
FUSE_HELPER_OPT("fsname=", nodefault_subtype),
|
||||
FUSE_HELPER_OPT("subtype=", nodefault_subtype),
|
||||
FUSE_OPT_KEY("-h", KEY_HELP),
|
||||
FUSE_OPT_KEY("--help", KEY_HELP),
|
||||
FUSE_OPT_KEY("-ho", KEY_HELP_NOHEADER),
|
||||
FUSE_OPT_KEY("-V", KEY_VERSION),
|
||||
FUSE_OPT_KEY("--version", KEY_VERSION),
|
||||
FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
|
||||
FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
|
||||
FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP),
|
||||
FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP),
|
||||
FUSE_OPT_END
|
||||
};
|
||||
|
||||
static void usage(const char *progname)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"usage: %s mountpoint [options]\n\n", progname);
|
||||
fprintf(stderr,
|
||||
"general options:\n"
|
||||
" -o opt,[opt...] mount options\n"
|
||||
" -h --help print help\n"
|
||||
" -V --version print version\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
static void helper_help(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"FUSE options:\n"
|
||||
" -d -o debug enable debug output (implies -f)\n"
|
||||
" -f foreground operation\n"
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
|
||||
static int fuse_helper_opt_proc(void *data, const char *arg, int key,
|
||||
struct fuse_args *outargs)
|
||||
{
|
||||
struct helper_opts *hopts = data;
|
||||
|
||||
switch (key) {
|
||||
case KEY_HELP:
|
||||
usage(outargs->argv[0]);
|
||||
/* fall through */
|
||||
|
||||
case KEY_HELP_NOHEADER:
|
||||
helper_help();
|
||||
return fuse_opt_add_arg(outargs, "-h");
|
||||
|
||||
case KEY_VERSION:
|
||||
return 1;
|
||||
|
||||
case FUSE_OPT_KEY_NONOPT:
|
||||
if (!hopts->mountpoint) {
|
||||
char mountpoint[PATH_MAX];
|
||||
if (realpath(arg, mountpoint) == NULL) {
|
||||
fprintf(stderr,
|
||||
"fuse: bad mount point `%s': %s\n",
|
||||
arg, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return fuse_opt_add_opt(&hopts->mountpoint, mountpoint);
|
||||
} else {
|
||||
fprintf(stderr, "fuse: invalid argument `%s'\n", arg);
|
||||
return -1;
|
||||
}
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int add_default_subtype(const char *progname, struct fuse_args *args)
|
||||
{
|
||||
int res;
|
||||
char *subtype_opt;
|
||||
const char *basename = strrchr(progname, '/');
|
||||
if (basename == NULL)
|
||||
basename = progname;
|
||||
else if (basename[1] != '\0')
|
||||
basename++;
|
||||
|
||||
subtype_opt = (char *) malloc(strlen(basename) + 64);
|
||||
if (subtype_opt == NULL) {
|
||||
fprintf(stderr, "fuse: memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
sprintf(subtype_opt, "-osubtype=%s", basename);
|
||||
res = fuse_opt_add_arg(args, subtype_opt);
|
||||
free(subtype_opt);
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
fuse_parse_cmdline(struct fuse_args *args_,
|
||||
char **mountpoint_,
|
||||
int *foreground_)
|
||||
{
|
||||
int res;
|
||||
struct helper_opts hopts;
|
||||
|
||||
memset(&hopts, 0, sizeof(hopts));
|
||||
|
||||
res = fuse_opt_parse(args_,
|
||||
&hopts,
|
||||
fuse_helper_opts,
|
||||
fuse_helper_opt_proc);
|
||||
if(res == -1)
|
||||
return -1;
|
||||
|
||||
if(!hopts.nodefault_subtype)
|
||||
{
|
||||
res = add_default_subtype(args_->argv[0], args_);
|
||||
if(res == -1)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(mountpoint_)
|
||||
*mountpoint_ = hopts.mountpoint;
|
||||
else
|
||||
free(hopts.mountpoint);
|
||||
|
||||
if(foreground_)
|
||||
*foreground_ = hopts.foreground;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
free(hopts.mountpoint);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fuse_daemonize(int foreground)
|
||||
{
|
||||
if (!foreground) {
|
||||
int nullfd;
|
||||
int waiter[2];
|
||||
char completed;
|
||||
|
||||
if (pipe(waiter)) {
|
||||
perror("fuse_daemonize: pipe");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* demonize current process by forking it and killing the
|
||||
* parent. This makes current process as a child of 'init'.
|
||||
*/
|
||||
switch(fork()) {
|
||||
case -1:
|
||||
perror("fuse_daemonize: fork");
|
||||
return -1;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
read(waiter[0], &completed, sizeof(completed));
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
if (setsid() == -1) {
|
||||
perror("fuse_daemonize: setsid");
|
||||
return -1;
|
||||
}
|
||||
|
||||
(void) chdir("/");
|
||||
|
||||
nullfd = open("/dev/null", O_RDWR, 0);
|
||||
if (nullfd != -1) {
|
||||
(void) dup2(nullfd, 0);
|
||||
(void) dup2(nullfd, 1);
|
||||
(void) dup2(nullfd, 2);
|
||||
if (nullfd > 2)
|
||||
close(nullfd);
|
||||
}
|
||||
|
||||
/* Propagate completion of daemon initializatation */
|
||||
completed = 1;
|
||||
write(waiter[1], &completed, sizeof(completed));
|
||||
close(waiter[0]);
|
||||
close(waiter[1]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
struct fuse_chan *
|
||||
fuse_mount_common(const char *mountpoint_,
|
||||
struct fuse_args *args_)
|
||||
{
|
||||
int fd;
|
||||
long bufsize;
|
||||
long pagesize;
|
||||
struct fuse_chan *ch;
|
||||
|
||||
/*
|
||||
* Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
|
||||
* would ensue.
|
||||
*/
|
||||
do
|
||||
{
|
||||
fd = open("/dev/null", O_RDWR);
|
||||
if(fd > 2)
|
||||
close(fd);
|
||||
} while(fd >= 0 && fd <= 2);
|
||||
|
||||
fd = fuse_kern_mount(mountpoint_,args_);
|
||||
if(fd == -1)
|
||||
return NULL;
|
||||
|
||||
pagesize = sysconf(_SC_PAGESIZE);
|
||||
bufsize = ((FUSE_DEFAULT_MAX_MAX_PAGES + 1) * pagesize);
|
||||
|
||||
ch = fuse_chan_new(fd,bufsize);
|
||||
if(!ch)
|
||||
fuse_kern_unmount(mountpoint_, fd);
|
||||
|
||||
return ch;
|
||||
}
|
||||
|
||||
struct fuse_chan *
|
||||
fuse_mount(const char *mountpoint_,
|
||||
struct fuse_args *args_)
|
||||
{
|
||||
return fuse_mount_common(mountpoint_,args_);
|
||||
}
|
||||
|
||||
static void fuse_unmount_common(const char *mountpoint, struct fuse_chan *ch)
|
||||
{
|
||||
if (mountpoint) {
|
||||
int fd = ch ? fuse_chan_clearfd(ch) : -1;
|
||||
fuse_kern_unmount(mountpoint, fd);
|
||||
if (ch)
|
||||
fuse_chan_destroy(ch);
|
||||
}
|
||||
}
|
||||
|
||||
void fuse_unmount(const char *mountpoint, struct fuse_chan *ch)
|
||||
{
|
||||
fuse_unmount_common(mountpoint, ch);
|
||||
}
|
||||
|
||||
struct fuse *
|
||||
fuse_setup_common(int argc,
|
||||
char *argv[],
|
||||
const struct fuse_operations *op,
|
||||
size_t op_size,
|
||||
char **mountpoint,
|
||||
int *fd)
|
||||
{
|
||||
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
|
||||
struct fuse_chan *ch;
|
||||
struct fuse *fuse;
|
||||
int foreground;
|
||||
int res;
|
||||
|
||||
res = fuse_parse_cmdline(&args, mountpoint, &foreground);
|
||||
if (res == -1)
|
||||
return NULL;
|
||||
|
||||
ch = fuse_mount_common(*mountpoint, &args);
|
||||
if (!ch) {
|
||||
fuse_opt_free_args(&args);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
fuse = fuse_new_common(ch, &args, op, op_size);
|
||||
fuse_opt_free_args(&args);
|
||||
if (fuse == NULL)
|
||||
goto err_unmount;
|
||||
|
||||
res = fuse_daemonize(foreground);
|
||||
if (res == -1)
|
||||
goto err_unmount;
|
||||
|
||||
res = fuse_set_signal_handlers(fuse_get_session(fuse));
|
||||
if (res == -1)
|
||||
goto err_unmount;
|
||||
|
||||
if (fd)
|
||||
*fd = fuse_chan_fd(ch);
|
||||
|
||||
return fuse;
|
||||
|
||||
err_unmount:
|
||||
fuse_unmount_common(*mountpoint, ch);
|
||||
if (fuse)
|
||||
fuse_destroy(fuse);
|
||||
err_free:
|
||||
free(*mountpoint);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct fuse *fuse_setup(int argc, char *argv[],
|
||||
const struct fuse_operations *op, size_t op_size,
|
||||
char **mountpoint)
|
||||
{
|
||||
return fuse_setup_common(argc, argv, op, op_size, mountpoint,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static void fuse_teardown_common(struct fuse *fuse, char *mountpoint)
|
||||
{
|
||||
struct fuse_session *se = fuse_get_session(fuse);
|
||||
struct fuse_chan *ch = se->ch;
|
||||
fuse_remove_signal_handlers(se);
|
||||
fuse_unmount_common(mountpoint, ch);
|
||||
fuse_destroy(fuse);
|
||||
free(mountpoint);
|
||||
}
|
||||
|
||||
void fuse_teardown(struct fuse *fuse, char *mountpoint)
|
||||
{
|
||||
fuse_teardown_common(fuse, mountpoint);
|
||||
}
|
||||
|
||||
static int fuse_main_common(int argc, char *argv[],
|
||||
const struct fuse_operations *op, size_t op_size)
|
||||
{
|
||||
struct fuse *fuse;
|
||||
char *mountpoint;
|
||||
int res;
|
||||
|
||||
fuse = fuse_setup_common(argc, argv, op, op_size,
|
||||
&mountpoint,
|
||||
NULL);
|
||||
if (fuse == NULL)
|
||||
return 1;
|
||||
|
||||
res = fuse_loop_mt(fuse);
|
||||
|
||||
fuse_teardown_common(fuse, mountpoint);
|
||||
if (res == -1)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_main_real(int argc,
|
||||
char *argv[],
|
||||
const struct fuse_operations *op,
|
||||
size_t op_size)
|
||||
{
|
||||
return fuse_main_common(argc, argv, op, op_size);
|
||||
}
|
||||
|
||||
#undef fuse_main
|
||||
int fuse_main(void);
|
||||
int fuse_main(void)
|
||||
{
|
||||
fprintf(stderr, "fuse_main(): This function does not exist\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fuse_version(void)
|
||||
{
|
||||
return FUSE_VERSION;
|
||||
}
|
||||
@@ -1,627 +0,0 @@
|
||||
/* The MIT License
|
||||
|
||||
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
An example:
|
||||
|
||||
#include "khash.h"
|
||||
KHASH_MAP_INIT_INT(32, char)
|
||||
int main() {
|
||||
int ret, is_missing;
|
||||
khiter_t k;
|
||||
khash_t(32) *h = kh_init(32);
|
||||
k = kh_put(32, h, 5, &ret);
|
||||
kh_value(h, k) = 10;
|
||||
k = kh_get(32, h, 10);
|
||||
is_missing = (k == kh_end(h));
|
||||
k = kh_get(32, h, 5);
|
||||
kh_del(32, h, k);
|
||||
for (k = kh_begin(h); k != kh_end(h); ++k)
|
||||
if (kh_exist(h, k)) kh_value(h, k) = 1;
|
||||
kh_destroy(32, h);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2013-05-02 (0.2.8):
|
||||
|
||||
* Use quadratic probing. When the capacity is power of 2, stepping function
|
||||
i*(i+1)/2 guarantees to traverse each bucket. It is better than double
|
||||
hashing on cache performance and is more robust than linear probing.
|
||||
|
||||
In theory, double hashing should be more robust than quadratic probing.
|
||||
However, my implementation is probably not for large hash tables, because
|
||||
the second hash function is closely tied to the first hash function,
|
||||
which reduce the effectiveness of double hashing.
|
||||
|
||||
Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
|
||||
|
||||
2011-12-29 (0.2.7):
|
||||
|
||||
* Minor code clean up; no actual effect.
|
||||
|
||||
2011-09-16 (0.2.6):
|
||||
|
||||
* The capacity is a power of 2. This seems to dramatically improve the
|
||||
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
|
||||
|
||||
- http://code.google.com/p/ulib/
|
||||
- http://nothings.org/computer/judy/
|
||||
|
||||
* Allow to optionally use linear probing which usually has better
|
||||
performance for random input. Double hashing is still the default as it
|
||||
is more robust to certain non-random input.
|
||||
|
||||
* Added Wang's integer hash function (not used by default). This hash
|
||||
function is more robust to certain non-random input.
|
||||
|
||||
2011-02-14 (0.2.5):
|
||||
|
||||
* Allow to declare global functions.
|
||||
|
||||
2009-09-26 (0.2.4):
|
||||
|
||||
* Improve portability
|
||||
|
||||
2008-09-19 (0.2.3):
|
||||
|
||||
* Corrected the example
|
||||
* Improved interfaces
|
||||
|
||||
2008-09-11 (0.2.2):
|
||||
|
||||
* Improved speed a little in kh_put()
|
||||
|
||||
2008-09-10 (0.2.1):
|
||||
|
||||
* Added kh_clear()
|
||||
* Fixed a compiling error
|
||||
|
||||
2008-09-02 (0.2.0):
|
||||
|
||||
* Changed to token concatenation which increases flexibility.
|
||||
|
||||
2008-08-31 (0.1.2):
|
||||
|
||||
* Fixed a bug in kh_get(), which has not been tested previously.
|
||||
|
||||
2008-08-31 (0.1.1):
|
||||
|
||||
* Added destructor
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __AC_KHASH_H
|
||||
#define __AC_KHASH_H
|
||||
|
||||
/*!
|
||||
@header
|
||||
|
||||
Generic hash table library.
|
||||
*/
|
||||
|
||||
#define AC_VERSION_KHASH_H "0.2.8"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* compiler specific configuration */
|
||||
|
||||
#if UINT_MAX == 0xffffffffu
|
||||
typedef unsigned int khint32_t;
|
||||
#elif ULONG_MAX == 0xffffffffu
|
||||
typedef unsigned long khint32_t;
|
||||
#endif
|
||||
|
||||
#if ULONG_MAX == ULLONG_MAX
|
||||
typedef unsigned long khint64_t;
|
||||
#else
|
||||
typedef unsigned long long khint64_t;
|
||||
#endif
|
||||
|
||||
#ifndef kh_inline
|
||||
#ifdef _MSC_VER
|
||||
#define kh_inline __inline
|
||||
#else
|
||||
#define kh_inline inline
|
||||
#endif
|
||||
#endif /* kh_inline */
|
||||
|
||||
#ifndef klib_unused
|
||||
#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
|
||||
#define klib_unused __attribute__ ((__unused__))
|
||||
#else
|
||||
#define klib_unused
|
||||
#endif
|
||||
#endif /* klib_unused */
|
||||
|
||||
typedef khint32_t khint_t;
|
||||
typedef khint_t khiter_t;
|
||||
|
||||
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
|
||||
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
|
||||
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
|
||||
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
|
||||
|
||||
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
|
||||
|
||||
#ifndef kroundup32
|
||||
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
|
||||
#endif
|
||||
|
||||
#ifndef kcalloc
|
||||
#define kcalloc(N,Z) calloc(N,Z)
|
||||
#endif
|
||||
#ifndef kmalloc
|
||||
#define kmalloc(Z) malloc(Z)
|
||||
#endif
|
||||
#ifndef krealloc
|
||||
#define krealloc(P,Z) realloc(P,Z)
|
||||
#endif
|
||||
#ifndef kfree
|
||||
#define kfree(P) free(P)
|
||||
#endif
|
||||
|
||||
static const double __ac_HASH_UPPER = 0.77;
|
||||
|
||||
#define __KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
typedef struct kh_##name##_s { \
|
||||
khint_t n_buckets, size, n_occupied, upper_bound; \
|
||||
khint32_t *flags; \
|
||||
khkey_t *keys; \
|
||||
khval_t *vals; \
|
||||
} kh_##name##_t;
|
||||
|
||||
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
|
||||
extern kh_##name##_t *kh_init_##name(void); \
|
||||
extern void kh_destroy_##name(kh_##name##_t *h); \
|
||||
extern void kh_clear_##name(kh_##name##_t *h); \
|
||||
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
|
||||
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
|
||||
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
|
||||
extern void kh_del_##name(kh_##name##_t *h, khint_t x);
|
||||
|
||||
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
SCOPE kh_##name##_t *kh_init_##name(void) { \
|
||||
return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
|
||||
} \
|
||||
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h) { \
|
||||
kfree((void *)h->keys); kfree(h->flags); \
|
||||
kfree((void *)h->vals); \
|
||||
kfree(h); \
|
||||
} \
|
||||
} \
|
||||
SCOPE void kh_clear_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h && h->flags) { \
|
||||
memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
|
||||
h->size = h->n_occupied = 0; \
|
||||
} \
|
||||
} \
|
||||
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
|
||||
{ \
|
||||
if (h->n_buckets) { \
|
||||
khint_t k, i, last, mask, step = 0; \
|
||||
mask = h->n_buckets - 1; \
|
||||
k = __hash_func(key); i = k & mask; \
|
||||
last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) return h->n_buckets; \
|
||||
} \
|
||||
return __ac_iseither(h->flags, i)? h->n_buckets : i; \
|
||||
} else return 0; \
|
||||
} \
|
||||
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
|
||||
{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
|
||||
khint32_t *new_flags = 0; \
|
||||
khint_t j = 1; \
|
||||
{ \
|
||||
kroundup32(new_n_buckets); \
|
||||
if (new_n_buckets < 4) new_n_buckets = 4; \
|
||||
if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
|
||||
else { /* hash table size to be changed (shrink or expand); rehash */ \
|
||||
new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (!new_flags) return -1; \
|
||||
memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (h->n_buckets < new_n_buckets) { /* expand */ \
|
||||
khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (!new_keys) { kfree(new_flags); return -1; } \
|
||||
h->keys = new_keys; \
|
||||
if (kh_is_map) { \
|
||||
khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
if (!new_vals) { kfree(new_flags); return -1; } \
|
||||
h->vals = new_vals; \
|
||||
} \
|
||||
} /* otherwise shrink */ \
|
||||
} \
|
||||
} \
|
||||
if (j) { /* rehashing is needed */ \
|
||||
for (j = 0; j != h->n_buckets; ++j) { \
|
||||
if (__ac_iseither(h->flags, j) == 0) { \
|
||||
khkey_t key = h->keys[j]; \
|
||||
khval_t val; \
|
||||
khint_t new_mask; \
|
||||
new_mask = new_n_buckets - 1; \
|
||||
if (kh_is_map) val = h->vals[j]; \
|
||||
__ac_set_isdel_true(h->flags, j); \
|
||||
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
|
||||
khint_t k, i, step = 0; \
|
||||
k = __hash_func(key); \
|
||||
i = k & new_mask; \
|
||||
while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
|
||||
__ac_set_isempty_false(new_flags, i); \
|
||||
if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
|
||||
{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
|
||||
if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
|
||||
__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
|
||||
} else { /* write the element and jump out of the loop */ \
|
||||
h->keys[i] = key; \
|
||||
if (kh_is_map) h->vals[i] = val; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
|
||||
h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
} \
|
||||
kfree(h->flags); /* free the working space */ \
|
||||
h->flags = new_flags; \
|
||||
h->n_buckets = new_n_buckets; \
|
||||
h->n_occupied = h->size; \
|
||||
h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
khint_t x; \
|
||||
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
|
||||
if (h->n_buckets > (h->size<<1)) { \
|
||||
if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
|
||||
{ \
|
||||
khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
|
||||
x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
|
||||
if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
|
||||
else { \
|
||||
last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
if (__ac_isdel(h->flags, i)) site = i; \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) { x = site; break; } \
|
||||
} \
|
||||
if (x == h->n_buckets) { \
|
||||
if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
|
||||
else x = i; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (__ac_isempty(h->flags, x)) { /* not present at all */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; ++h->n_occupied; \
|
||||
*ret = 1; \
|
||||
} else if (__ac_isdel(h->flags, x)) { /* deleted */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; \
|
||||
*ret = 2; \
|
||||
} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
|
||||
return x; \
|
||||
} \
|
||||
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
|
||||
{ \
|
||||
if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
|
||||
__ac_set_isdel_true(h->flags, x); \
|
||||
--h->size; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define KHASH_DECLARE(name, khkey_t, khval_t) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
|
||||
|
||||
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
/* --- BEGIN OF HASH FUNCTIONS --- */
|
||||
|
||||
/*! @function
|
||||
@abstract Integer hash function
|
||||
@param key The integer [khint32_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int_hash_func(key) (khint32_t)(key)
|
||||
/*! @function
|
||||
@abstract Integer comparison function
|
||||
*/
|
||||
#define kh_int_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract 64-bit integer hash function
|
||||
@param key The integer [khint64_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
|
||||
/*! @function
|
||||
@abstract 64-bit integer comparison function
|
||||
*/
|
||||
#define kh_int64_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract const char* hash function
|
||||
@param s Pointer to a null terminated string
|
||||
@return The hash value
|
||||
*/
|
||||
static kh_inline khint_t __ac_X31_hash_string(const char *s)
|
||||
{
|
||||
khint_t h = (khint_t)*s;
|
||||
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
|
||||
return h;
|
||||
}
|
||||
/*! @function
|
||||
@abstract Another interface to const char* hash function
|
||||
@param key Pointer to a null terminated string [const char*]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
|
||||
/*! @function
|
||||
@abstract Const char* comparison function
|
||||
*/
|
||||
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
|
||||
|
||||
static kh_inline khint_t __ac_Wang_hash(khint_t key)
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
|
||||
|
||||
/* --- END OF HASH FUNCTIONS --- */
|
||||
|
||||
/* Other convenient macros... */
|
||||
|
||||
/*!
|
||||
@abstract Type of the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define khash_t(name) kh_##name##_t
|
||||
|
||||
/*! @function
|
||||
@abstract Initiate a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@return Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_init(name) kh_init_##name()
|
||||
|
||||
/*! @function
|
||||
@abstract Destroy a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_destroy(name, h) kh_destroy_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Reset a hash table without deallocating memory.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_clear(name, h) kh_clear_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Resize a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param s New size [khint_t]
|
||||
*/
|
||||
#define kh_resize(name, h, s) kh_resize_##name(h, s)
|
||||
|
||||
/*! @function
|
||||
@abstract Insert a key to the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@param r Extra return code: -1 if the operation failed;
|
||||
0 if the key is present in the hash table;
|
||||
1 if the bucket is empty (never used); 2 if the element in
|
||||
the bucket has been deleted [int*]
|
||||
@return Iterator to the inserted element [khint_t]
|
||||
*/
|
||||
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
|
||||
|
||||
/*! @function
|
||||
@abstract Retrieve a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
|
||||
*/
|
||||
#define kh_get(name, h, k) kh_get_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Remove a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Iterator to the element to be deleted [khint_t]
|
||||
*/
|
||||
#define kh_del(name, h, k) kh_del_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Test whether a bucket contains data.
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return 1 if containing data; 0 otherwise [int]
|
||||
*/
|
||||
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
|
||||
|
||||
/*! @function
|
||||
@abstract Get key given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Key [type of keys]
|
||||
*/
|
||||
#define kh_key(h, x) ((h)->keys[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get value given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Value [type of values]
|
||||
@discussion For hash sets, calling this results in segfault.
|
||||
*/
|
||||
#define kh_val(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Alias of kh_val()
|
||||
*/
|
||||
#define kh_value(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get the start iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The start iterator [khint_t]
|
||||
*/
|
||||
#define kh_begin(h) (khint_t)(0)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the end iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The end iterator [khint_t]
|
||||
*/
|
||||
#define kh_end(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of elements in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of elements in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_size(h) ((h)->size)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of buckets in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of buckets in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_n_buckets(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the entries in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param kvar Variable to which key will be assigned
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(kvar) = kh_key(h,__i); \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the values in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/* More convenient interfaces */
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT(name) \
|
||||
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT(name, khval_t) \
|
||||
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT64(name) \
|
||||
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT64(name, khval_t) \
|
||||
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
typedef const char *kh_cstr_t;
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_STR(name) \
|
||||
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_STR(name, khval_t) \
|
||||
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
#endif /* __AC_KHASH_H */
|
||||
@@ -1,245 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fmp.h"
|
||||
|
||||
#include "mutex.hpp"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
|
||||
typedef struct lfmp_t lfmp_t;
|
||||
struct lfmp_t
|
||||
{
|
||||
fmp_t fmp;
|
||||
pthread_mutex_t lock;
|
||||
};
|
||||
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_init(lfmp_t *lfmp_,
|
||||
const uint64_t obj_size_,
|
||||
const uint64_t page_multiple_)
|
||||
{
|
||||
fmp_init(&lfmp_->fmp,obj_size_,page_multiple_);
|
||||
mutex_init(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_lock(lfmp_t *lfmp_)
|
||||
{
|
||||
mutex_lock(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_unlock(lfmp_t *lfmp_)
|
||||
{
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
lfmp_slab_count(lfmp_t *lfmp_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_slab_count(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
lfmp_slab_alloc(lfmp_t *lfmp_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_slab_alloc(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void*
|
||||
lfmp_alloc(lfmp_t *lfmp_)
|
||||
{
|
||||
void *rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_alloc(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void*
|
||||
lfmp_calloc(lfmp_t *lfmp_)
|
||||
{
|
||||
void *rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_calloc(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_free(lfmp_t *lfmp_,
|
||||
void *obj_)
|
||||
{
|
||||
mutex_lock(&lfmp_->lock);
|
||||
fmp_free(&lfmp_->fmp,obj_);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_clear(lfmp_t *lfmp_)
|
||||
{
|
||||
mutex_lock(&lfmp_->lock);
|
||||
fmp_clear(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_destroy(lfmp_t *lfmp_)
|
||||
{
|
||||
mutex_lock(&lfmp_->lock);
|
||||
fmp_destroy(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
mutex_destroy(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
lfmp_avail_objs(lfmp_t *lfmp_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_avail_objs(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
lfmp_objs_in_slab(lfmp_t *lfmp_,
|
||||
void *slab_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_objs_in_slab(&lfmp_->fmp,slab_);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
void
|
||||
lfmp_remove_objs_in_slab(lfmp_t *lfmp_,
|
||||
void *slab_)
|
||||
{
|
||||
mutex_lock(&lfmp_->lock);
|
||||
fmp_remove_objs_in_slab(&lfmp_->fmp,slab_);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
lfmp_gc(lfmp_t *lfmp_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_gc(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
lfmp_objs_per_slab(lfmp_t *lfmp_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_objs_per_slab(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
double
|
||||
lfmp_slab_usage_ratio(lfmp_t *lfmp_)
|
||||
{
|
||||
double rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_slab_usage_ratio(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
lfmp_total_allocated_memory(lfmp_t *lfmp_)
|
||||
{
|
||||
uint64_t rv;
|
||||
|
||||
mutex_lock(&lfmp_->lock);
|
||||
rv = fmp_total_allocated_memory(&lfmp_->fmp);
|
||||
mutex_unlock(&lfmp_->lock);
|
||||
|
||||
return rv;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct lock_s lock_t;
|
||||
struct lock_s
|
||||
{
|
||||
int type;
|
||||
off_t start;
|
||||
off_t end;
|
||||
pid_t pid;
|
||||
uint64_t owner;
|
||||
lock_t *next;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<class T> struct _Unique_if
|
||||
{
|
||||
typedef unique_ptr<T> _Single_object;
|
||||
};
|
||||
|
||||
template<class T> struct _Unique_if<T[]>
|
||||
{
|
||||
typedef unique_ptr<T[]> _Unknown_bound;
|
||||
};
|
||||
|
||||
template<class T, size_t N> struct _Unique_if<T[N]>
|
||||
{
|
||||
typedef void _Known_bound;
|
||||
};
|
||||
|
||||
template<class T, class... Args>
|
||||
typename _Unique_if<T>::_Single_object
|
||||
make_unique(Args&&... args)
|
||||
{
|
||||
return unique_ptr<T>(new T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
typename _Unique_if<T>::_Unknown_bound
|
||||
make_unique(size_t n)
|
||||
{
|
||||
typedef typename remove_extent<T>::type U;
|
||||
return unique_ptr<T>(new U[n]());
|
||||
}
|
||||
|
||||
template<class T, class... Args>
|
||||
typename _Unique_if<T>::_Known_bound
|
||||
make_unique(Args&&...) = delete;
|
||||
}
|
||||
@@ -1,595 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB.
|
||||
*/
|
||||
|
||||
#include "fuse_i.h"
|
||||
#include "fuse_opt.h"
|
||||
#include "mount_util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#include <perfuse.h>
|
||||
|
||||
#define MS_RDONLY MNT_RDONLY
|
||||
#define MS_NOSUID MNT_NOSUID
|
||||
#define MS_NODEV MNT_NODEV
|
||||
#define MS_NOEXEC MNT_NOEXEC
|
||||
#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
|
||||
#define MS_NOATIME MNT_NOATIME
|
||||
|
||||
#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
|
||||
#endif
|
||||
|
||||
#define FUSERMOUNT_PROG "fusermount"
|
||||
#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
|
||||
|
||||
#ifndef MS_DIRSYNC
|
||||
#define MS_DIRSYNC 128
|
||||
#endif
|
||||
|
||||
enum {
|
||||
KEY_KERN_FLAG,
|
||||
KEY_KERN_OPT,
|
||||
KEY_FUSERMOUNT_OPT,
|
||||
KEY_SUBTYPE_OPT,
|
||||
KEY_MTAB_OPT,
|
||||
KEY_RO,
|
||||
KEY_HELP,
|
||||
KEY_VERSION,
|
||||
};
|
||||
|
||||
struct mount_opts {
|
||||
int allow_other;
|
||||
int ishelp;
|
||||
int flags;
|
||||
int auto_unmount;
|
||||
int blkdev;
|
||||
char *fsname;
|
||||
char *subtype;
|
||||
char *subtype_opt;
|
||||
char *mtab_opts;
|
||||
char *fusermount_opts;
|
||||
char *kernel_opts;
|
||||
};
|
||||
|
||||
#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
|
||||
|
||||
static const struct fuse_opt fuse_mount_opts[] = {
|
||||
FUSE_MOUNT_OPT("allow_other", allow_other),
|
||||
FUSE_MOUNT_OPT("blkdev", blkdev),
|
||||
FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
|
||||
FUSE_MOUNT_OPT("fsname=%s", fsname),
|
||||
FUSE_MOUNT_OPT("subtype=%s", subtype),
|
||||
FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
|
||||
FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
|
||||
FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
|
||||
FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
|
||||
FUSE_OPT_KEY("large_read", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("context=", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
|
||||
FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_KEEP),
|
||||
FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
|
||||
FUSE_OPT_KEY("-r", KEY_RO),
|
||||
FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("async", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("atime", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
|
||||
FUSE_OPT_KEY("-h", KEY_HELP),
|
||||
FUSE_OPT_KEY("--help", KEY_HELP),
|
||||
FUSE_OPT_KEY("-V", KEY_VERSION),
|
||||
FUSE_OPT_KEY("--version", KEY_VERSION),
|
||||
FUSE_OPT_END
|
||||
};
|
||||
|
||||
static void mount_help(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
" -o auto_unmount auto unmount on process termination\n"
|
||||
" -o default_permissions enable permission checking by kernel\n"
|
||||
" -o fsname=NAME set filesystem name\n"
|
||||
" -o subtype=NAME set filesystem type\n"
|
||||
" -o large_read issue large read requests (2.4 only)\n"
|
||||
" -o max_read=N set maximum size of read requests\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
static void exec_fusermount(const char *argv[])
|
||||
{
|
||||
execv(FUSERMOUNT_DIR "/mergerfs-" FUSERMOUNT_PROG, (char **) argv);
|
||||
execvp("mergerfs-" FUSERMOUNT_PROG, (char **) argv);
|
||||
execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv);
|
||||
execvp(FUSERMOUNT_PROG, (char **) argv);
|
||||
}
|
||||
|
||||
static void mount_version(void)
|
||||
{
|
||||
int pid = fork();
|
||||
if (!pid) {
|
||||
const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL };
|
||||
exec_fusermount(argv);
|
||||
_exit(1);
|
||||
} else if (pid != -1)
|
||||
waitpid(pid, NULL, 0);
|
||||
}
|
||||
|
||||
struct mount_flags {
|
||||
const char *opt;
|
||||
unsigned long flag;
|
||||
int on;
|
||||
};
|
||||
|
||||
static const struct mount_flags mount_flags[] = {
|
||||
{"rw", MS_RDONLY, 0},
|
||||
{"ro", MS_RDONLY, 1},
|
||||
{"suid", MS_NOSUID, 0},
|
||||
{"nosuid", MS_NOSUID, 1},
|
||||
{"dev", MS_NODEV, 0},
|
||||
{"nodev", MS_NODEV, 1},
|
||||
{"exec", MS_NOEXEC, 0},
|
||||
{"noexec", MS_NOEXEC, 1},
|
||||
{"async", MS_SYNCHRONOUS, 0},
|
||||
{"sync", MS_SYNCHRONOUS, 1},
|
||||
{"atime", MS_NOATIME, 0},
|
||||
{"noatime", MS_NOATIME, 1},
|
||||
#ifndef __NetBSD__
|
||||
{"dirsync", MS_DIRSYNC, 1},
|
||||
#endif
|
||||
{NULL, 0, 0}
|
||||
};
|
||||
|
||||
static void set_mount_flag(const char *s, int *flags)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; mount_flags[i].opt != NULL; i++) {
|
||||
const char *opt = mount_flags[i].opt;
|
||||
if (strcmp(opt, s) == 0) {
|
||||
if (mount_flags[i].on)
|
||||
*flags |= mount_flags[i].flag;
|
||||
else
|
||||
*flags &= ~mount_flags[i].flag;
|
||||
return;
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "fuse: internal error, can't find mount flag\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
static int fuse_mount_opt_proc(void *data, const char *arg, int key,
|
||||
struct fuse_args *outargs)
|
||||
{
|
||||
struct mount_opts *mo = data;
|
||||
|
||||
switch (key) {
|
||||
case KEY_RO:
|
||||
arg = "ro";
|
||||
/* fall through */
|
||||
case KEY_KERN_FLAG:
|
||||
set_mount_flag(arg, &mo->flags);
|
||||
return 0;
|
||||
|
||||
case KEY_KERN_OPT:
|
||||
return fuse_opt_add_opt(&mo->kernel_opts, arg);
|
||||
|
||||
case KEY_FUSERMOUNT_OPT:
|
||||
return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
|
||||
|
||||
case KEY_SUBTYPE_OPT:
|
||||
return fuse_opt_add_opt(&mo->subtype_opt, arg);
|
||||
|
||||
case KEY_MTAB_OPT:
|
||||
return fuse_opt_add_opt(&mo->mtab_opts, arg);
|
||||
|
||||
case KEY_HELP:
|
||||
mount_help();
|
||||
mo->ishelp = 1;
|
||||
break;
|
||||
|
||||
case KEY_VERSION:
|
||||
mount_version();
|
||||
mo->ishelp = 1;
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* return value:
|
||||
* >= 0 => fd
|
||||
* -1 => error
|
||||
*/
|
||||
static int receive_fd(int fd)
|
||||
{
|
||||
struct msghdr msg;
|
||||
struct iovec iov;
|
||||
char buf[1];
|
||||
int rv;
|
||||
size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
iov.iov_base = buf;
|
||||
iov.iov_len = 1;
|
||||
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
msg.msg_name = 0;
|
||||
msg.msg_namelen = 0;
|
||||
msg.msg_iov = &iov;
|
||||
msg.msg_iovlen = 1;
|
||||
/* old BSD implementations should use msg_accrights instead of
|
||||
* msg_control; the interface is different. */
|
||||
msg.msg_control = ccmsg;
|
||||
msg.msg_controllen = sizeof(ccmsg);
|
||||
|
||||
while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
|
||||
if (rv == -1) {
|
||||
perror("recvmsg");
|
||||
return -1;
|
||||
}
|
||||
if(!rv) {
|
||||
/* EOF */
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
if (cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
fprintf(stderr, "got control message of unknown type %d\n",
|
||||
cmsg->cmsg_type);
|
||||
return -1;
|
||||
}
|
||||
return *(int*)CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
void fuse_kern_unmount(const char *mountpoint, int fd)
|
||||
{
|
||||
int res;
|
||||
int pid;
|
||||
|
||||
if (!mountpoint)
|
||||
return;
|
||||
|
||||
if (fd != -1) {
|
||||
struct pollfd pfd;
|
||||
|
||||
pfd.fd = fd;
|
||||
pfd.events = 0;
|
||||
res = poll(&pfd, 1, 0);
|
||||
|
||||
/* Need to close file descriptor, otherwise synchronous umount
|
||||
would recurse into filesystem, and deadlock.
|
||||
|
||||
Caller expects fuse_kern_unmount to close the fd, so close it
|
||||
anyway. */
|
||||
close(fd);
|
||||
|
||||
/* If file poll returns POLLERR on the device file descriptor,
|
||||
then the filesystem is already unmounted */
|
||||
if (res == 1 && (pfd.revents & POLLERR))
|
||||
return;
|
||||
}
|
||||
|
||||
if (geteuid() == 0) {
|
||||
fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
res = umount2(mountpoint, 2);
|
||||
if (res == 0)
|
||||
return;
|
||||
|
||||
pid = fork();
|
||||
if(pid == -1)
|
||||
return;
|
||||
|
||||
if(pid == 0) {
|
||||
const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z",
|
||||
"--", mountpoint, NULL };
|
||||
|
||||
exec_fusermount(argv);
|
||||
_exit(1);
|
||||
}
|
||||
waitpid(pid, NULL, 0);
|
||||
}
|
||||
|
||||
static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
|
||||
const char *opts, int quiet)
|
||||
{
|
||||
int fds[2], pid;
|
||||
int res;
|
||||
int rv;
|
||||
|
||||
if (!mountpoint) {
|
||||
fprintf(stderr, "fuse: missing mountpoint parameter\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
|
||||
if(res == -1) {
|
||||
perror("fuse: socketpair() failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if(pid == -1) {
|
||||
perror("fuse: fork() failed");
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(pid == 0) {
|
||||
char env[10];
|
||||
const char *argv[32];
|
||||
int a = 0;
|
||||
|
||||
if (quiet) {
|
||||
int fd = open("/dev/null", O_RDONLY);
|
||||
if (fd != -1) {
|
||||
dup2(fd, 1);
|
||||
dup2(fd, 2);
|
||||
}
|
||||
}
|
||||
|
||||
argv[a++] = FUSERMOUNT_PROG;
|
||||
if (opts) {
|
||||
argv[a++] = "-o";
|
||||
argv[a++] = opts;
|
||||
}
|
||||
argv[a++] = "--";
|
||||
argv[a++] = mountpoint;
|
||||
argv[a++] = NULL;
|
||||
|
||||
close(fds[1]);
|
||||
fcntl(fds[0], F_SETFD, 0);
|
||||
snprintf(env, sizeof(env), "%i", fds[0]);
|
||||
setenv(FUSE_COMMFD_ENV, env, 1);
|
||||
exec_fusermount(argv);
|
||||
perror("fuse: failed to exec fusermount");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
close(fds[0]);
|
||||
rv = receive_fd(fds[1]);
|
||||
|
||||
if (!mo->auto_unmount) {
|
||||
/* with auto_unmount option fusermount will not exit until
|
||||
this socket is closed */
|
||||
close(fds[1]);
|
||||
waitpid(pid, NULL, 0); /* bury zombie */
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
|
||||
const char *mnt_opts)
|
||||
{
|
||||
char tmp[128];
|
||||
const char *devname = "/dev/fuse";
|
||||
char *source = NULL;
|
||||
char *type = NULL;
|
||||
struct stat stbuf;
|
||||
int fd;
|
||||
int res;
|
||||
|
||||
if (!mnt) {
|
||||
fprintf(stderr, "fuse: missing mountpoint parameter\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = stat(mnt, &stbuf);
|
||||
if (res == -1) {
|
||||
fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n",
|
||||
mnt, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mo->auto_unmount) {
|
||||
/* Tell the caller to fallback to fusermount because
|
||||
auto-unmount does not work otherwise. */
|
||||
return -2;
|
||||
}
|
||||
|
||||
fd = open(devname, O_RDWR);
|
||||
if (fd == -1) {
|
||||
if (errno == ENODEV || errno == ENOENT)
|
||||
fprintf(stderr, "fuse: device not found, try 'modprobe fuse' first\n");
|
||||
else
|
||||
fprintf(stderr, "fuse: failed to open %s: %s\n",
|
||||
devname, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
|
||||
fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
|
||||
|
||||
res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
|
||||
if (res == -1)
|
||||
goto out_close;
|
||||
|
||||
source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
|
||||
(mo->subtype ? strlen(mo->subtype) : 0) +
|
||||
strlen(devname) + 32);
|
||||
|
||||
type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
|
||||
if (!type || !source) {
|
||||
fprintf(stderr, "fuse: failed to allocate memory\n");
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
|
||||
if (mo->subtype) {
|
||||
strcat(type, ".");
|
||||
strcat(type, mo->subtype);
|
||||
}
|
||||
strcpy(source,
|
||||
mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
|
||||
|
||||
res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
|
||||
if (res == -1 && errno == ENODEV && mo->subtype) {
|
||||
/* Probably missing subtype support */
|
||||
strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
|
||||
if (mo->fsname) {
|
||||
if (!mo->blkdev)
|
||||
sprintf(source, "%s#%s", mo->subtype,
|
||||
mo->fsname);
|
||||
} else {
|
||||
strcpy(source, type);
|
||||
}
|
||||
res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
|
||||
}
|
||||
if (res == -1) {
|
||||
/*
|
||||
* Maybe kernel doesn't support unprivileged mounts, in this
|
||||
* case try falling back to fusermount
|
||||
*/
|
||||
if (errno == EPERM) {
|
||||
res = -2;
|
||||
} else {
|
||||
int errno_save = errno;
|
||||
if (mo->blkdev && errno == ENODEV &&
|
||||
!fuse_mnt_check_fuseblk())
|
||||
fprintf(stderr,
|
||||
"fuse: 'fuseblk' support missing\n");
|
||||
else
|
||||
fprintf(stderr, "fuse: mount failed: %s\n",
|
||||
strerror(errno_save));
|
||||
}
|
||||
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
#ifndef __NetBSD__
|
||||
#ifndef IGNORE_MTAB
|
||||
if (geteuid() == 0) {
|
||||
char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
|
||||
res = -1;
|
||||
if (!newmnt)
|
||||
goto out_umount;
|
||||
|
||||
res = fuse_mnt_add_mount("fuse", source, newmnt, type,
|
||||
mnt_opts);
|
||||
free(newmnt);
|
||||
if (res == -1)
|
||||
goto out_umount;
|
||||
}
|
||||
#endif /* IGNORE_MTAB */
|
||||
#endif /* __NetBSD__ */
|
||||
free(type);
|
||||
free(source);
|
||||
|
||||
return fd;
|
||||
|
||||
out_umount:
|
||||
umount2(mnt, 2); /* lazy umount */
|
||||
out_close:
|
||||
free(type);
|
||||
free(source);
|
||||
close(fd);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int get_mnt_flag_opts(char **mnt_optsp, int flags)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
|
||||
return -1;
|
||||
|
||||
for (i = 0; mount_flags[i].opt != NULL; i++) {
|
||||
if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
|
||||
fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
|
||||
{
|
||||
struct mount_opts mo;
|
||||
int res = -1;
|
||||
char *mnt_opts = NULL;
|
||||
|
||||
memset(&mo, 0, sizeof(mo));
|
||||
mo.flags = MS_NOSUID | MS_NODEV;
|
||||
|
||||
if (args &&
|
||||
fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
|
||||
return -1;
|
||||
|
||||
res = 0;
|
||||
if (mo.ishelp)
|
||||
goto out;
|
||||
|
||||
res = -1;
|
||||
if (get_mnt_flag_opts(&mnt_opts, mo.flags) == -1)
|
||||
goto out;
|
||||
if (mo.kernel_opts && fuse_opt_add_opt(&mnt_opts, mo.kernel_opts) == -1)
|
||||
goto out;
|
||||
if (mo.mtab_opts && fuse_opt_add_opt(&mnt_opts, mo.mtab_opts) == -1)
|
||||
goto out;
|
||||
|
||||
res = fuse_mount_sys(mountpoint, &mo, mnt_opts);
|
||||
if (res == -2) {
|
||||
if (mo.fusermount_opts &&
|
||||
fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) == -1)
|
||||
goto out;
|
||||
|
||||
if (mo.subtype) {
|
||||
char *tmp_opts = NULL;
|
||||
|
||||
res = -1;
|
||||
if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
|
||||
fuse_opt_add_opt(&tmp_opts, mo.subtype_opt) == -1) {
|
||||
free(tmp_opts);
|
||||
goto out;
|
||||
}
|
||||
|
||||
res = fuse_mount_fusermount(mountpoint, &mo, tmp_opts, 1);
|
||||
free(tmp_opts);
|
||||
if (res == -1)
|
||||
res = fuse_mount_fusermount(mountpoint, &mo,
|
||||
mnt_opts, 0);
|
||||
} else {
|
||||
res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0);
|
||||
}
|
||||
}
|
||||
out:
|
||||
free(mnt_opts);
|
||||
free(mo.fsname);
|
||||
free(mo.subtype);
|
||||
free(mo.fusermount_opts);
|
||||
free(mo.subtype_opt);
|
||||
free(mo.kernel_opts);
|
||||
free(mo.mtab_opts);
|
||||
return res;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
int fuse_mnt_add_mount(const char *progname, const char *fsname,
|
||||
const char *mnt, const char *type, const char *opts);
|
||||
int fuse_mnt_remove_mount(const char *progname, const char *mnt);
|
||||
int fuse_mnt_umount(const char *progname, const char *abs_mnt,
|
||||
const char *rel_mnt, int lazy);
|
||||
char *fuse_mnt_resolve_path(const char *progname, const char *orig);
|
||||
int fuse_mnt_check_fuseblk(void);
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "node.hpp"
|
||||
|
||||
#include "lfmp.h"
|
||||
|
||||
static lfmp_t g_NODE_FMP;
|
||||
|
||||
static
|
||||
__attribute__((constructor))
|
||||
void
|
||||
node_constructor()
|
||||
{
|
||||
lfmp_init(&g_NODE_FMP,sizeof(node_t),256);
|
||||
}
|
||||
|
||||
static
|
||||
__attribute__((destructor))
|
||||
void
|
||||
node_destructor()
|
||||
{
|
||||
lfmp_destroy(&g_NODE_FMP);
|
||||
}
|
||||
|
||||
node_t*
|
||||
node_alloc()
|
||||
{
|
||||
return (node_t*)lfmp_calloc(&g_NODE_FMP);
|
||||
}
|
||||
|
||||
void
|
||||
node_free(node_t *node_)
|
||||
{
|
||||
lfmp_free(&g_NODE_FMP,node_);
|
||||
}
|
||||
|
||||
int
|
||||
node_gc1()
|
||||
{
|
||||
return lfmp_gc(&g_NODE_FMP);
|
||||
}
|
||||
|
||||
void
|
||||
node_gc()
|
||||
{
|
||||
int rv;
|
||||
int fails;
|
||||
|
||||
fails = 0;
|
||||
do
|
||||
{
|
||||
rv = node_gc1();
|
||||
if(rv == 0)
|
||||
fails++;
|
||||
} while(rv || (fails < 3));
|
||||
}
|
||||
|
||||
lfmp_t*
|
||||
node_lfmp()
|
||||
{
|
||||
return &g_NODE_FMP;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "lock.h"
|
||||
#include "lfmp.h"
|
||||
|
||||
typedef struct node_s node_t;
|
||||
struct node_s
|
||||
{
|
||||
node_t *name_next;
|
||||
node_t *id_next;
|
||||
|
||||
uint64_t nodeid;
|
||||
char *name;
|
||||
node_t *parent;
|
||||
|
||||
uint64_t nlookup;
|
||||
uint32_t refctr;
|
||||
uint32_t open_count;
|
||||
|
||||
int32_t treelock;
|
||||
lock_t *locks;
|
||||
|
||||
uint32_t stat_crc32b;
|
||||
uint8_t is_stat_cache_valid:1;
|
||||
};
|
||||
|
||||
node_t *node_alloc();
|
||||
void node_free(node_t*);
|
||||
int node_gc1();
|
||||
void node_gc();
|
||||
lfmp_t *node_lfmp();
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "cpu.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace PinThreads
|
||||
{
|
||||
void R1L(const CPU::ThreadIdVec threads);
|
||||
void R1P(const CPU::ThreadIdVec threads);
|
||||
void RP1L(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void RP1P(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void R1LP1L(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void R1PP1P(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void RPSL(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void RPSP(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void R1PPSP(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads);
|
||||
void pin(const CPU::ThreadIdVec read_threads,
|
||||
const CPU::ThreadIdVec process_threads,
|
||||
const std::string type);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU GPL.
|
||||
See the file COPYING.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
static char *progname;
|
||||
|
||||
static char *xstrdup(const char *s)
|
||||
{
|
||||
char *t = strdup(s);
|
||||
if (!t) {
|
||||
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
static void *xrealloc(void *oldptr, size_t size)
|
||||
{
|
||||
void *ptr = realloc(oldptr, size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void add_arg(char **cmdp, const char *opt)
|
||||
{
|
||||
size_t optlen = strlen(opt);
|
||||
size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
|
||||
char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
|
||||
char *s;
|
||||
s = cmd + cmdlen;
|
||||
if (*cmdp)
|
||||
*s++ = ' ';
|
||||
|
||||
*s++ = '\'';
|
||||
for (; *opt; opt++) {
|
||||
if (*opt == '\'') {
|
||||
*s++ = '\'';
|
||||
*s++ = '\\';
|
||||
*s++ = '\'';
|
||||
*s++ = '\'';
|
||||
} else
|
||||
*s++ = *opt;
|
||||
}
|
||||
*s++ = '\'';
|
||||
*s = '\0';
|
||||
*cmdp = cmd;
|
||||
}
|
||||
|
||||
static char *add_option(const char *opt, char *options)
|
||||
{
|
||||
int oldlen = options ? strlen(options) : 0;
|
||||
|
||||
options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
|
||||
if (!oldlen)
|
||||
strcpy(options, opt);
|
||||
else {
|
||||
strcat(options, ",");
|
||||
strcat(options, opt);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *type = NULL;
|
||||
char *source;
|
||||
const char *mountpoint;
|
||||
char *basename;
|
||||
char *options = NULL;
|
||||
char *command = NULL;
|
||||
char *setuid = NULL;
|
||||
int i;
|
||||
int dev = 1;
|
||||
int suid = 1;
|
||||
|
||||
progname = argv[0];
|
||||
basename = strrchr(argv[0], '/');
|
||||
if (basename)
|
||||
basename++;
|
||||
else
|
||||
basename = argv[0];
|
||||
|
||||
type = "mergerfs";
|
||||
if (strncmp(basename, "mount.fuse.", 11) == 0)
|
||||
type = basename + 11;
|
||||
if (strncmp(basename, "mount.fuseblk.", 14) == 0)
|
||||
type = basename + 14;
|
||||
|
||||
if (type && !type[0])
|
||||
type = NULL;
|
||||
|
||||
if (argc < 3) {
|
||||
fprintf(stderr,
|
||||
"usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
|
||||
progname, type ? "source" : "type#[source]");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
source = argv[1];
|
||||
if (!source[0])
|
||||
source = NULL;
|
||||
|
||||
mountpoint = argv[2];
|
||||
|
||||
for (i = 3; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-v") == 0) {
|
||||
continue;
|
||||
} else if (strcmp(argv[i], "-t") == 0) {
|
||||
i++;
|
||||
|
||||
if (i == argc) {
|
||||
fprintf(stderr,
|
||||
"%s: missing argument to option '-t'\n",
|
||||
progname);
|
||||
exit(1);
|
||||
}
|
||||
type = argv[i];
|
||||
if (strncmp(type, "fuse.", 5) == 0)
|
||||
type += 5;
|
||||
else if (strncmp(type, "fuseblk.", 8) == 0)
|
||||
type += 8;
|
||||
|
||||
if (!type[0]) {
|
||||
fprintf(stderr,
|
||||
"%s: empty type given as argument to option '-t'\n",
|
||||
progname);
|
||||
exit(1);
|
||||
}
|
||||
} else if (strcmp(argv[i], "-o") == 0) {
|
||||
char *opts;
|
||||
char *opt;
|
||||
i++;
|
||||
if (i == argc)
|
||||
break;
|
||||
|
||||
opts = xstrdup(argv[i]);
|
||||
opt = strtok(opts, ",");
|
||||
while (opt) {
|
||||
int j;
|
||||
int ignore = 0;
|
||||
const char *ignore_opts[] = { "",
|
||||
"user",
|
||||
"nouser",
|
||||
"users",
|
||||
"auto",
|
||||
"noauto",
|
||||
"_netdev",
|
||||
NULL};
|
||||
if (strncmp(opt, "setuid=", 7) == 0) {
|
||||
setuid = xstrdup(opt + 7);
|
||||
ignore = 1;
|
||||
}
|
||||
for (j = 0; ignore_opts[j]; j++)
|
||||
if (strcmp(opt, ignore_opts[j]) == 0)
|
||||
ignore = 1;
|
||||
|
||||
if (!ignore) {
|
||||
if (strcmp(opt, "nodev") == 0)
|
||||
dev = 0;
|
||||
else if (strcmp(opt, "nosuid") == 0)
|
||||
suid = 0;
|
||||
|
||||
options = add_option(opt, options);
|
||||
}
|
||||
opt = strtok(NULL, ",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dev)
|
||||
options = add_option("dev", options);
|
||||
if (suid)
|
||||
options = add_option("suid", options);
|
||||
|
||||
if (!type) {
|
||||
if (source) {
|
||||
type = xstrdup(source);
|
||||
source = strchr(type, '#');
|
||||
if (source)
|
||||
*source++ = '\0';
|
||||
if (!type[0]) {
|
||||
fprintf(stderr, "%s: empty filesystem type\n",
|
||||
progname);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "%s: empty source\n", progname);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
add_arg(&command, type);
|
||||
if (source)
|
||||
add_arg(&command, source);
|
||||
add_arg(&command, mountpoint);
|
||||
if (options) {
|
||||
add_arg(&command, "-o");
|
||||
add_arg(&command, options);
|
||||
}
|
||||
|
||||
if (setuid && setuid[0]) {
|
||||
char *sucommand = command;
|
||||
command = NULL;
|
||||
add_arg(&command, "su");
|
||||
add_arg(&command, "-");
|
||||
add_arg(&command, setuid);
|
||||
add_arg(&command, "-c");
|
||||
add_arg(&command, sucommand);
|
||||
} else if (!getenv("HOME")) {
|
||||
/* Hack to make filesystems work in the boot environment */
|
||||
setenv("HOME", "/root", 0);
|
||||
}
|
||||
|
||||
execl("/bin/sh", "/bin/sh", "-c", command, NULL);
|
||||
fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
/*
|
||||
FUSE: Filesystem in Userspace
|
||||
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
||||
|
||||
This program can be distributed under the terms of the GNU LGPLv2.
|
||||
See the file COPYING.LIB.
|
||||
*/
|
||||
|
||||
#include "mount_util.h"
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <paths.h>
|
||||
#ifndef __NetBSD__
|
||||
#include <mntent.h>
|
||||
#endif
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
|
||||
#define mtab_needs_update(mnt) 0
|
||||
#else
|
||||
static int mtab_needs_update(const char *mnt)
|
||||
{
|
||||
int res;
|
||||
struct stat stbuf;
|
||||
|
||||
/* If mtab is within new mount, don't touch it */
|
||||
if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
|
||||
_PATH_MOUNTED[strlen(mnt)] == '/')
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Skip mtab update if /etc/mtab:
|
||||
*
|
||||
* - doesn't exist,
|
||||
* - is a symlink,
|
||||
* - is on a read-only filesystem.
|
||||
*/
|
||||
res = lstat(_PATH_MOUNTED, &stbuf);
|
||||
if (res == -1) {
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
} else {
|
||||
uid_t ruid;
|
||||
int err;
|
||||
|
||||
if (S_ISLNK(stbuf.st_mode))
|
||||
return 0;
|
||||
|
||||
ruid = getuid();
|
||||
if (ruid != 0)
|
||||
setreuid(0, -1);
|
||||
|
||||
res = access(_PATH_MOUNTED, W_OK);
|
||||
err = (res == -1) ? errno : 0;
|
||||
if (ruid != 0)
|
||||
setreuid(ruid, -1);
|
||||
|
||||
if (err == EROFS)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif /* __NetBSD__ */
|
||||
|
||||
static int add_mount(const char *progname, const char *fsname,
|
||||
const char *mnt, const char *type, const char *opts)
|
||||
{
|
||||
int res;
|
||||
int status;
|
||||
sigset_t blockmask;
|
||||
sigset_t oldmask;
|
||||
|
||||
sigemptyset(&blockmask);
|
||||
sigaddset(&blockmask, SIGCHLD);
|
||||
res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = fork();
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
|
||||
goto out_restore;
|
||||
}
|
||||
if (res == 0) {
|
||||
char *env = NULL;
|
||||
|
||||
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
||||
setuid(geteuid());
|
||||
execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
|
||||
"-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
|
||||
fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
|
||||
progname, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
res = waitpid(res, &status, 0);
|
||||
if (res == -1)
|
||||
fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
|
||||
|
||||
if (status != 0)
|
||||
res = -1;
|
||||
|
||||
out_restore:
|
||||
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int fuse_mnt_add_mount(const char *progname, const char *fsname,
|
||||
const char *mnt, const char *type, const char *opts)
|
||||
{
|
||||
if (!mtab_needs_update(mnt))
|
||||
return 0;
|
||||
|
||||
return add_mount(progname, fsname, mnt, type, opts);
|
||||
}
|
||||
|
||||
static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
|
||||
{
|
||||
int res;
|
||||
int status;
|
||||
sigset_t blockmask;
|
||||
sigset_t oldmask;
|
||||
|
||||
sigemptyset(&blockmask);
|
||||
sigaddset(&blockmask, SIGCHLD);
|
||||
res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = fork();
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
|
||||
goto out_restore;
|
||||
}
|
||||
if (res == 0) {
|
||||
char *env = NULL;
|
||||
|
||||
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
||||
setuid(geteuid());
|
||||
if (lazy) {
|
||||
execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
|
||||
"-l", NULL, &env);
|
||||
} else {
|
||||
execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
|
||||
NULL, &env);
|
||||
}
|
||||
fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
|
||||
progname, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
res = waitpid(res, &status, 0);
|
||||
if (res == -1)
|
||||
fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
|
||||
|
||||
if (status != 0) {
|
||||
res = -1;
|
||||
}
|
||||
|
||||
out_restore:
|
||||
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
int fuse_mnt_umount(const char *progname, const char *abs_mnt,
|
||||
const char *rel_mnt, int lazy)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (!mtab_needs_update(abs_mnt)) {
|
||||
res = umount2(rel_mnt, lazy ? 2 : 0);
|
||||
if (res == -1)
|
||||
fprintf(stderr, "%s: failed to unmount %s: %s\n",
|
||||
progname, abs_mnt, strerror(errno));
|
||||
return res;
|
||||
}
|
||||
|
||||
return exec_umount(progname, rel_mnt, lazy);
|
||||
}
|
||||
|
||||
static int remove_mount(const char *progname, const char *mnt)
|
||||
{
|
||||
int res;
|
||||
int status;
|
||||
sigset_t blockmask;
|
||||
sigset_t oldmask;
|
||||
|
||||
sigemptyset(&blockmask);
|
||||
sigaddset(&blockmask, SIGCHLD);
|
||||
res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = fork();
|
||||
if (res == -1) {
|
||||
fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
|
||||
goto out_restore;
|
||||
}
|
||||
if (res == 0) {
|
||||
char *env = NULL;
|
||||
|
||||
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
||||
setuid(geteuid());
|
||||
execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
|
||||
"--fake", mnt, NULL, &env);
|
||||
fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
|
||||
progname, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
res = waitpid(res, &status, 0);
|
||||
if (res == -1)
|
||||
fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
|
||||
|
||||
if (status != 0)
|
||||
res = -1;
|
||||
|
||||
out_restore:
|
||||
sigprocmask(SIG_SETMASK, &oldmask, NULL);
|
||||
return res;
|
||||
}
|
||||
|
||||
int fuse_mnt_remove_mount(const char *progname, const char *mnt)
|
||||
{
|
||||
if (!mtab_needs_update(mnt))
|
||||
return 0;
|
||||
|
||||
return remove_mount(progname, mnt);
|
||||
}
|
||||
|
||||
char *fuse_mnt_resolve_path(const char *progname, const char *orig)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
char *copy;
|
||||
char *dst;
|
||||
char *end;
|
||||
char *lastcomp;
|
||||
const char *toresolv;
|
||||
|
||||
if (!orig[0]) {
|
||||
fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
|
||||
orig);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
copy = strdup(orig);
|
||||
if (copy == NULL) {
|
||||
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
toresolv = copy;
|
||||
lastcomp = NULL;
|
||||
for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
|
||||
if (end[0] != '/') {
|
||||
char *tmp;
|
||||
end[1] = '\0';
|
||||
tmp = strrchr(copy, '/');
|
||||
if (tmp == NULL) {
|
||||
lastcomp = copy;
|
||||
toresolv = ".";
|
||||
} else {
|
||||
lastcomp = tmp + 1;
|
||||
if (tmp == copy)
|
||||
toresolv = "/";
|
||||
}
|
||||
if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
|
||||
lastcomp = NULL;
|
||||
toresolv = copy;
|
||||
}
|
||||
else if (tmp)
|
||||
tmp[0] = '\0';
|
||||
}
|
||||
if (realpath(toresolv, buf) == NULL) {
|
||||
fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
|
||||
strerror(errno));
|
||||
free(copy);
|
||||
return NULL;
|
||||
}
|
||||
if (lastcomp == NULL)
|
||||
dst = strdup(buf);
|
||||
else {
|
||||
dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
|
||||
if (dst) {
|
||||
unsigned buflen = strlen(buf);
|
||||
if (buflen && buf[buflen-1] == '/')
|
||||
sprintf(dst, "%s%s", buf, lastcomp);
|
||||
else
|
||||
sprintf(dst, "%s/%s", buf, lastcomp);
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
if (dst == NULL)
|
||||
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
||||
return dst;
|
||||
}
|
||||
|
||||
int fuse_mnt_check_fuseblk(void)
|
||||
{
|
||||
char buf[256];
|
||||
FILE *f = fopen("/proc/filesystems", "r");
|
||||
if (!f)
|
||||
return 1;
|
||||
|
||||
while (fgets(buf, sizeof(buf), f))
|
||||
if (strstr(buf, "fuseblk\n")) {
|
||||
fclose(f);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
+6
-6
@@ -12,16 +12,16 @@ is a FUSE-based union filesystem geared towards simplifying storage and manageme
|
||||
and
|
||||
.BR aufs .
|
||||
.SH QUICKSTART
|
||||
.UR https://trapexit.github.io/mergerfs/quickstart
|
||||
.LI https://trapexit.github.io/mergerfs/quickstart
|
||||
.UR https://trapexit.github.io/mergerfs/latest/quickstart
|
||||
.LI https://trapexit.github.io/mergerfs/latest/quickstart
|
||||
.UE
|
||||
.SH DOCUMENTATION
|
||||
.UR https://trapexit.github.io/mergerfs
|
||||
.LI https://trapexit.github.io/mergerfs
|
||||
.UE
|
||||
.SH SUPPORT
|
||||
.UR https://trapexit.github.io/mergerfs/support
|
||||
.LI https://trapexit.github.io/mergerfs/support
|
||||
.UR https://trapexit.github.io/mergerfs/latest/support
|
||||
.LI https://trapexit.github.io/mergerfs/latest/support
|
||||
.UE
|
||||
.SH FEATURES
|
||||
.IP \[bu] 2
|
||||
@@ -74,8 +74,8 @@ When a file or directory is created, a policy is first run to determine which br
|
||||
For functions which change attributes or remove the file, the behavior may be applied to all instances found.
|
||||
|
||||
Read more about policies here:
|
||||
.UR https://trapexit.github.io/mergerfs/config/functions_categories_and_policies
|
||||
.LI https://trapexit.github.io/mergerfs/config/functions_categories_and_policies
|
||||
.UR https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies
|
||||
.LI https://trapexit.github.io/mergerfs/latest/config/functions_categories_policies
|
||||
.UE
|
||||
.SH VISUALIZATION
|
||||
.nf
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ set -e
|
||||
|
||||
python3 -m venv venv
|
||||
. ./venv/bin/activate
|
||||
pip3 install --no-cache-dir mkdocs mkdocs-material pymdown-extensions mike
|
||||
pip3 install --no-cache-dir 'mkdocs<2.0' mkdocs-material pymdown-extensions mike
|
||||
|
||||
@@ -58,13 +58,13 @@ read/writes. Contact the software author or worse case the mergerfs
|
||||
author so it may be investigated further.
|
||||
|
||||
|
||||
### write benchmark
|
||||
### Write Benchmark
|
||||
|
||||
```
|
||||
$ dd if=/dev/zero of=/mnt/mergerfs/16GB.file bs=1M count=16384 oflag=nocache conv=fdatasync status=progress
|
||||
```
|
||||
|
||||
### read benchmark
|
||||
### Read Benchmark
|
||||
|
||||
```
|
||||
$ dd if=/mnt/mergerfs/16GB.file of=/dev/null bs=1M iflag=nocache conv=fdatasync status=progress
|
||||
@@ -79,21 +79,21 @@ Below are some basic tests but also try setting `--direct=0`, changing
|
||||
the `ioengine`, or playing with other options.
|
||||
|
||||
|
||||
#### write
|
||||
#### Write
|
||||
|
||||
```
|
||||
$ fio --name=writetest --filename=/mnt/mergerfs/16GB.file --size=16Gb --rw=write --bs=1M --direct=1 --numjobs=1 --iodepth=8 --group_reporting --runtime=60 --startdelay=0 --ioengine=psync
|
||||
```
|
||||
|
||||
|
||||
#### read
|
||||
#### Read
|
||||
|
||||
```
|
||||
$ fio --name=readtest --filename=/mnt/mergerfs/16GB.file --size=16Gb --rw=read --bs=1M --direct=1 --numjobs=1 --iodepth=8 --group_reporting --runtime=60 --startdelay=0 --ioengine=psync
|
||||
```
|
||||
|
||||
|
||||
### other benchmarks
|
||||
### Other Benchmarks
|
||||
|
||||
If you are attempting to benchmark other behaviors you must ensure you
|
||||
clear kernel caches before runs. In fact it would be a good deal to
|
||||
|
||||
@@ -38,6 +38,32 @@ These files and xattrs are used to help `mergerfs` know whether or not
|
||||
a directory happens to be a branch or is the underlying directory
|
||||
mount point.
|
||||
|
||||
## Paths which appear after startup
|
||||
|
||||
The [`branches` resolution process](branches.md#resolution) runs before
|
||||
this option starts waiting. A missing literal path is retained as a
|
||||
branch, so a configuration such as `/mnt/hdd/disk0/foobar` can work
|
||||
when `/mnt/hdd/disk0` is mounted asynchronously and `foobar` only
|
||||
becomes visible afterward, provided the path then meets one of the
|
||||
"mounted" conditions above.
|
||||
|
||||
For each branch which is not ready, mergerfs first runs `mount` once
|
||||
with the full branch path as the target and then polls that same path
|
||||
until the timeout expires. It does not create the path, try to mount
|
||||
its parent, or rerun `mount`. In the example above, the initial
|
||||
`mount /mnt/hdd/disk0/foobar` may fail while the path is absent. If
|
||||
another service mounts `/mnt/hdd/disk0` and
|
||||
`/mnt/hdd/disk0/foobar` appears and meets one of the "mounted"
|
||||
conditions before the deadline, mergerfs detects it during polling
|
||||
and continues.
|
||||
|
||||
Globs are different. If `/mnt/hdd/disk0/*` has no matches during
|
||||
branch resolution, the literal path `/mnt/hdd/disk0/*` is retained.
|
||||
The mount wait does not expand it again after `/mnt/hdd/disk0`
|
||||
becomes available. Ensure the parent filesystem is mounted before
|
||||
mergerfs, configure a literal branch path, or update `branches`
|
||||
through the runtime API once the paths exist.
|
||||
|
||||
|
||||
**NOTE:** If on a `systemd` based system and using `fstab` it is a
|
||||
good idea to set the mount option
|
||||
|
||||
@@ -14,7 +14,7 @@ Branches currently have two options which can be set. A
|
||||
included in a policy calculation and a individual
|
||||
[minfreespace](#minfreespace) value. The values are set by
|
||||
prepending an `=` at the end of a branch designation and using commas
|
||||
as delimiters. Example: `/mnt/drive=RW,1234`
|
||||
as delimiters. Example: `/mnt/hdd/disk0=RW,1234`
|
||||
|
||||
|
||||
### branch mode
|
||||
@@ -34,6 +34,33 @@ Same purpose and syntax as the [global option](minfreespace.md) but
|
||||
specific to the branch. Defaults to the global value.
|
||||
|
||||
|
||||
### resolution
|
||||
|
||||
Each colon-delimited branch designation is resolved independently when
|
||||
the `branches` option is parsed, both at startup and when it is updated
|
||||
through the runtime API:
|
||||
|
||||
1. The path portion is expanded as a glob.
|
||||
2. If the glob matches one or more directories, each match becomes a
|
||||
branch.
|
||||
3. If the glob has no matches, mergerfs logs a notice and keeps the
|
||||
configured path unchanged as a single, literal branch.
|
||||
4. mergerfs resolves each resulting path with `realpath`. If that
|
||||
succeeds, the canonical path is stored. If it fails, typically
|
||||
because the path does not exist yet, the unchanged path is stored.
|
||||
5. A path which exists but is not a directory is logged and skipped. A
|
||||
path which does not exist is logged but retained.
|
||||
|
||||
Consequently, a literal branch such as `/mnt/hdd/disk0/foobar` may be
|
||||
configured before it exists and can become usable after
|
||||
`/mnt/hdd/disk0` is mounted. Branch resolution is not repeated in the
|
||||
background, however. If `/mnt/hdd/disk0/*` has no matches when it is
|
||||
resolved, mergerfs stores the literal path `/mnt/hdd/disk0/*`;
|
||||
directories which match it later are not discovered automatically.
|
||||
Update `branches` through the runtime API or restart mergerfs to
|
||||
resolve the glob again.
|
||||
|
||||
|
||||
### globbing
|
||||
|
||||
To make it easier to include multiple branches mergerfs supports
|
||||
@@ -42,18 +69,18 @@ MUST be escaped when using via the shell else the shell itself will
|
||||
apply the glob itself.**
|
||||
|
||||
```
|
||||
# mergerfs /mnt/hdd\*:/mnt/ssd /media
|
||||
# mergerfs /mnt/hdd/\*:/mnt/ssd/\* /media
|
||||
```
|
||||
|
||||
The above line will use all directories in /mnt prefixed with **hdd**
|
||||
as well as **ssd**.
|
||||
The above line will use all directories within `/mnt/hdd` and
|
||||
`/mnt/ssd`.
|
||||
|
||||
To have the pool mounted at boot or otherwise accessible from related
|
||||
tools use `/etc/fstab`.
|
||||
|
||||
```
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0
|
||||
/mnt/hdd/*:/mnt/ssd/* /media mergerfs minfreespace=16G 0 0
|
||||
```
|
||||
|
||||
**NOTE:** The globbing is done at mount or when updated using the
|
||||
|
||||
@@ -11,7 +11,8 @@ works for mergerfs itself. Not the underlying filesystems.
|
||||
* `cache.files=full`: Enables page caching. Files are cached across
|
||||
opens.
|
||||
* `cache.files=auto-full`: Enables page caching. Files are cached
|
||||
across opens if mtime and size are unchanged since previous open.
|
||||
across opens if mtime and size are unchanged since previous
|
||||
open. Cache is dropped if mtime or size change on open.
|
||||
* `cache.files=per-process`: Enable page caching (equivalent to
|
||||
`cache.files=partial`) only for processes whose 'comm' name matches
|
||||
one of the values defined in cache.files.process-names. If the name
|
||||
@@ -40,6 +41,11 @@ transparently enable page caching when mmap is requested. This means
|
||||
it should be safe to set `cache.files=off`. However, on Linux v6.5 and
|
||||
below you will need to configure `cache.files` as you need.
|
||||
|
||||
If [passthrough.io](passthrough.md) is enabled so must page
|
||||
caching. mergerfs will set `cache.files=auto-full` if `passthrough.io`
|
||||
is enabled. And when using `passthrough.io` the there is no double
|
||||
page caching since it is in fact passing through the IO.
|
||||
|
||||
|
||||
[^1]: This is not unique to mergerfs and affects all FUSE
|
||||
filesystems. It is something that the FUSE community hopes to
|
||||
@@ -67,9 +73,9 @@ little risk in setting the value much higher. Especially if there are
|
||||
no out-of-band changes.
|
||||
|
||||
|
||||
## cache.negative_entry
|
||||
## cache.negative-entry
|
||||
|
||||
* `cache.negative_entry=UINT`: Sets the number of seconds to cache
|
||||
* `cache.negative-entry=UINT`: Sets the number of seconds to cache
|
||||
negative entry queries. Defaults to `1`.
|
||||
|
||||
This is a cache for negative entry query responses. Such as when a
|
||||
@@ -166,8 +172,8 @@ the FUSE writeback cache small writes *may* be aggregated by the
|
||||
kernel and then sent to mergerfs as one larger request. This can
|
||||
greatly improve the throughput for apps which write to files
|
||||
inefficiently. The amount the kernel can aggregate is limited by the
|
||||
size of a FUSE message. Read the fuse_msg_size section for more
|
||||
details.
|
||||
size of a FUSE message. Read the [fuse-msg-size](fuse-msg-size.md)
|
||||
section for more details.
|
||||
|
||||
There is a side effect as a result of enabling writeback
|
||||
caching. Underlying files won't ever be opened with O_APPEND or
|
||||
|
||||
@@ -3,19 +3,24 @@
|
||||
These are old, deprecated options which may no longer have any
|
||||
function or have been replaced. **They should not be used.**
|
||||
|
||||
* **direct_io**: Bypass page cache. Use `cache.files=off`
|
||||
instead.
|
||||
* **kernel_cache**: Do not invalidate data cache on file open. Use
|
||||
`cache.files=full` instead.
|
||||
* **auto_cache**: Invalidate data cache if file mtime or
|
||||
size change. Use `cache.files=auto-full` instead. (default: false)
|
||||
* **async_read**: Perform reads asynchronously. Use
|
||||
`async_read=true` instead.
|
||||
* **sync_read**: Perform reads synchronously. Use
|
||||
`async_read=false` instead.
|
||||
* **splice_read**: Does nothing.
|
||||
* **splice_write**: Does nothing.
|
||||
* **splice_move**: Does nothing.
|
||||
* **allow_other**: mergerfs v2.35.0 and above sets this FUSE option
|
||||
automatically if running as root.
|
||||
* **use_ino**: Effectively replaced with `inodecalc`.
|
||||
* **async_read**: Use `async-read=true`.
|
||||
* **atomic_o_trunc**: Does nothing.
|
||||
* **attr_timeout**: Use [cache.attr](cache.md).
|
||||
* **auto_cache**: Use [cache.files=auto-full](cache.md).
|
||||
* **big_writes**: Does nothing.
|
||||
* **direct_io**: Use [cache.files=off](cache.md).
|
||||
* **entry_timeout**: Use [cache.entry](cache.md).
|
||||
* **hard_remove**: Does nothing.
|
||||
* **kernel_cache**: Use [cache.files=full](cache.md).
|
||||
* **negative_entry**: Use [cache.negative-entry](cache.md).
|
||||
* **remember**: Use `never-forget-nodes`.
|
||||
* **remember-nodes**: Use `never-forget-nodes`.
|
||||
* **noforget**: Use `never-forget-nodes`.
|
||||
* **nonempty**: Does nothing.
|
||||
* **splice_move**: Does nothing.
|
||||
* **splice_read**: Does nothing.
|
||||
* **splice_write**: Does nothing.
|
||||
* **sync_read**: Use `async-read=false`.
|
||||
* **use_ino**: Use [inodecalc](inodecalc.md).
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# func.readdir
|
||||
|
||||
Defaults to `seq`
|
||||
* type: `STR`
|
||||
* default: `seq`
|
||||
* examples: `func.readdir=seq`, `func.readdir=cor:4`
|
||||
|
||||
Examples: `func.readdir=seq`, `func.readdir=cor:4`
|
||||
|
||||
`readdir` has policies to control how it reads directory content.
|
||||
`readdir` has policies to control how to read directory content.
|
||||
|
||||
| Policy | Description |
|
||||
| ------ | ----------- |
|
||||
@@ -12,7 +12,7 @@ Examples: `func.readdir=seq`, `func.readdir=cor:4`
|
||||
| cosr:N:M | "concurrent open, sequential read" : Concurrently open branch directories using a thread pool and process them in the order defined in `branches`. This keeps memory and CPU usage low while also reducing the time spent waiting on branches to respond. `N` is the number of threads. If negative it will be the core count divided by `abs(N)`. `M` is the queue depth. If either value is `0` it will be decided based on system configuration. |
|
||||
| cosr:N | cosr:N:M with M = 0 |
|
||||
| cosr | cosr:N:M with N = 0 and M = 0 |
|
||||
| cor:N:M | "concurrent open and read" : Concurrently open branch directories and immediately start reading their contents using a thread pool. This will result in slightly higher memory and CPU usage but reduced latency. Particularly when using higher latency / slower speed network filesystem branches. Unlike `seq` and `cosr` the order of files could change due the async nature of the thread pool. This should not be a problem since the order of files listed in not guaranteed. `N` is the number of threads. If negative it will be the core count divided by `abs(N)`. `M` is the queue depth. If either value is `0` it will be decided based on system configuration. |
|
||||
| cor:N:M | "concurrent open and read" : Concurrently open branch directories and immediately start reading their contents using a thread pool. This will result in slightly higher memory and CPU usage but reduced latency. Particularly when using higher latency / slower speed network filesystem branches. Unlike `seq` and `cosr` the order of files could change due the async nature of the thread pool. This should not be a problem since the order of files returned in not guaranteed. `N` is the number of threads. If negative it will be the core count divided by `abs(N)`. `M` is the queue depth. If either value is `0` it will be decided based on system configuration. |
|
||||
| cor:N | cosr:N:M with M = 0 |
|
||||
| cor | cosr:N:M with N = 0 and M = 0 |
|
||||
|
||||
@@ -21,3 +21,23 @@ in a directory and possibly some basic metadata about said files. To
|
||||
know details about the files, as one would see from commands like
|
||||
`find` or `ls`, it is required to call `stat` on the file which is
|
||||
controlled by `fuse.getattr`.
|
||||
|
||||
Until Linux v6.16, despite being able to change the size of FUSE
|
||||
messages, `readdir` requests were limited to 1 page (4KiB) in size per
|
||||
message. This limitation limited throughput and therefore performance
|
||||
of the `readdir` call. In v6.16 that was changed to be able to grow to
|
||||
the [fuse-msg-size](fuse-msg-size.md) which allows a lot more data to
|
||||
be sent per request and therefore much better performance in certain
|
||||
situations.
|
||||
|
||||
|
||||
## Technical Details
|
||||
|
||||
* On Linux
|
||||
[getdents](https://man7.org/linux/man-pages/man2/getdents.2.html) is
|
||||
used instead of
|
||||
[readdir](https://man7.org/linux/man-pages/man2/readdir.2.html) in
|
||||
order to leverage larger buffers for better performance on larger
|
||||
directory reads as well as have more control in concurrent policies.
|
||||
* [readdir](https://man7.org/linux/man-pages/man2/readdir.2.html) is
|
||||
used on FreeBSD.
|
||||
|
||||
@@ -5,7 +5,9 @@ The POSIX filesystem API is made up of a number of functions. `creat`,
|
||||
the core functions are grouped into 3 categories: `action`, `create`,
|
||||
and `search`. These functions and categories can be assigned a policy
|
||||
which dictates which branch is chosen when performing that
|
||||
function.
|
||||
function. IE. When an application requests that a file or directory is
|
||||
created how should mergerfs decide which branch to choose to attempt
|
||||
the create? That is the policy.
|
||||
|
||||
Some functions, listed in the category `N/A` below, can not be
|
||||
assigned the normal policies because they are directly related to a
|
||||
@@ -44,9 +46,9 @@ branch base path provided is used. Not the full path to the file or
|
||||
directory in question. Meaning that mounts within the branch will not
|
||||
be considered in the space calculations.
|
||||
|
||||
NOTE: While any policy can be assigned to a function or category, some
|
||||
may not be very useful in practice. For instance: `rand` (random) may
|
||||
be useful for file creation but could lead to very odd behavior if
|
||||
**NOTE:** While any policy can be assigned to a function or category,
|
||||
some may not be very useful in practice. For instance: `rand` (random)
|
||||
may be useful for file creation but could lead to very odd behavior if
|
||||
used for `chmod` if there were more than one copy of the file. Unless
|
||||
users find this flexibility useful it will likely be removed in the
|
||||
future.
|
||||
@@ -69,7 +71,7 @@ whether or not a branch is mounted read-only, and the branch mode
|
||||
[minfreespace](minfreespace.md).
|
||||
|
||||
Policies may have their own additional filtering such as those that
|
||||
require existing paths to be present.
|
||||
require relative paths to be present.
|
||||
|
||||
If all branches are filtered an error will be returned. Typically
|
||||
`EROFS` (read-only filesystem) or `ENOSPC` (no space left on
|
||||
@@ -114,6 +116,7 @@ but it makes things a bit more uniform.
|
||||
| mfs (most free space) | Pick the branch with the most available free space. |
|
||||
| ff (first found) | Given the order of the branches, as defined at mount time or configured at runtime, act on the first one found. |
|
||||
| lfs (least free space) | Pick the branch with the least available free space. |
|
||||
| lup (least used percent) | Pick the branch with the least used percentage of space |
|
||||
| lus (least used space) | Pick the branch with the least used space. |
|
||||
| all | Search: For **mkdir**, **mknod**, and **symlink** it will apply to all branches. **create** works like **ff**. |
|
||||
| msppfrd (most shared path, percentage free random distribution) | Like **eppfrd** but if it fails to find a branch it will try again with the parent directory. Continues this pattern till finding one. |
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# fuse_msg_size
|
||||
# fuse-msg-size
|
||||
|
||||
* `fuse_msg_size=UINT|SIZE`
|
||||
* Defaults to `1M`
|
||||
* Performance improvements often peak at about `4M`
|
||||
* type: `UINT|SIZE`
|
||||
* default: `1M`
|
||||
* example: `fuse-msg-size=1M`
|
||||
* If the value is a `SIZE` in the form of `xB`, `xK`, `xM`, `xG`, or
|
||||
`xT` then it will choose the closest multiple of the system page
|
||||
size
|
||||
* If the value is just an integer without a size multipler suffix then
|
||||
it is treated as a page count
|
||||
`xT` then it will choose the closest multiple of the system page
|
||||
size. If the value is just an integer without a size multipler suffix
|
||||
then it is treated as a page count
|
||||
|
||||
FUSE applications communicate with the kernel over a special character
|
||||
device: `/dev/fuse`. A large portion of the overhead associated with
|
||||
@@ -29,10 +28,10 @@ v6.13](https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/co
|
||||
and above the max value supported by the kernel can range from 1
|
||||
(4KiB) to 65535 (~256MiB) (assuming a page size of 4KiB.) The default
|
||||
used by Linux >= 4.20, and hard coded value used before 4.20, is 32
|
||||
(128KiB). In mergerfs it is referred to as `fuse_msg_size` to make it
|
||||
(128KiB). In mergerfs it is referred to as `fuse-msg-size` to make it
|
||||
clear what it impacts and provide some abstraction.
|
||||
|
||||
If the `fuse_msg_size` value provided is more than the system wide
|
||||
If the `fuse-msg-size` value provided is more than the system wide
|
||||
maximum mergerfs will attempt to increase the system wide value to keep
|
||||
the user from needing to set the value using `sysctl`,
|
||||
`/etc/sysctl.conf`, or `/proc/sys/fs/fuse/max_pages_limit`.
|
||||
@@ -48,4 +47,4 @@ v4.20 and v6.13 the max value is 256. On kernels >= v6.13 the maximum
|
||||
value is 65535.
|
||||
|
||||
NOTE: If you intend to enable `cache.files` you should also set
|
||||
[readahead](readahead.md) to match `fuse_msg_size`.
|
||||
[readahead](readahead.md) to match `fuse-msg-size`.
|
||||
@@ -16,4 +16,4 @@ help. Like [export-support](export-support.md) this is mostly for
|
||||
debugging.
|
||||
|
||||
This option is a kernel mount option so unable to be changed at
|
||||
runtime.
|
||||
runtime. It controls the `default_permissions` FUSE kernel option.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# lazy-umount-mountpoint
|
||||
|
||||
* type: `BOOL`
|
||||
* default: `false`
|
||||
* example: `lazy-umount-mountpoint=true`
|
||||
|
||||
When enabled mergerfs will issue a [lazy
|
||||
umount](https://man7.org/linux/man-pages/man8/umount.8.html) request
|
||||
to the mount point as it starts.
|
||||
|
||||
```
|
||||
Lazy unmount. Detach the filesystem from the file hierarchy
|
||||
now, and clean up all references to this filesystem as soon as
|
||||
it is not busy anymore.
|
||||
```
|
||||
|
||||
The value of using this is in situations where you wish to change
|
||||
options which can only be set at initialization or upgrade mergerfs
|
||||
itself. It also helps in the case of using `mount -a` which leads to a
|
||||
new instance of mergerfs being mounted due to it being unable to
|
||||
recognize that it is already mounted.
|
||||
|
||||
In theory mergerfs could notice that an instance is already mounted at
|
||||
the mount point and attempt to use the runtime interface to modify the
|
||||
options and only mounting a new instance if the version has changed or
|
||||
initialization time only options are changed. However, given the
|
||||
relative rarity of this being an issue it isn't a priority.
|
||||
@@ -1,6 +1,6 @@
|
||||
# link_cow
|
||||
# link-cow
|
||||
|
||||
* `link_cow=true|false`
|
||||
* `link-cow=true|false`
|
||||
* Defaults to `false`
|
||||
|
||||
This feature offers similar functionality to what
|
||||
@@ -30,7 +30,7 @@ as appropriate and the original error returned.
|
||||
|
||||
If `moveonenospc` is disabled the underlying error will be returned.
|
||||
|
||||
NOTE: This feature has NO affect on policies. It ONLY applies to the
|
||||
NOTE: This feature has NO effect on policies. It ONLY applies to the
|
||||
literal write function. If the create function returns `ENOSPC` or the
|
||||
[policy returns `ENOSPC`](functions_categories_policies.md#filtering)
|
||||
that error will be returned back to the application making the
|
||||
|
||||
@@ -30,9 +30,11 @@ config file.
|
||||
## mount options
|
||||
|
||||
* **config**: Path to a config file. Same arguments as below in
|
||||
key=val / ini style format.
|
||||
`key` or `key=val` style format. `#` used for comments.
|
||||
* **[branches](branches.md)**: Colon delimited list of branches. Used
|
||||
primarily in config file.
|
||||
primarily in a config file.
|
||||
* **mountpoint=DIRPATH**: The path to the mountpoint. Used primarily
|
||||
in a config file.
|
||||
* **[branches-mount-timeout](branches-mount-timeout.md)=UINT**: Number
|
||||
of seconds to wait at startup for branches to be a mount other than
|
||||
the mountpoint's filesystem. (default: 0)
|
||||
@@ -68,7 +70,7 @@ config file.
|
||||
writable and its mtime or ctime is older than **symlinkify_timeout**
|
||||
files will be reported as symlinks to the original files. Please
|
||||
read more below before using. (default: false)
|
||||
* **[symlinkify_timeout](symlinkify.md)=UINT**: Time to wait, in
|
||||
* **[symlinkify-timeout](symlinkify.md)=UINT**: Time to wait, in
|
||||
seconds, to activate the **symlinkify** behavior. (default: 3600)
|
||||
* **nullrw=BOOL**: Turns reads and writes into no-ops. The request
|
||||
will succeed but do nothing. Useful for benchmarking
|
||||
@@ -86,13 +88,16 @@ config file.
|
||||
* **[export-support](export-support.md)=BOOL**: Sets a low-level FUSE
|
||||
feature intended to indicate the filesystem can support being
|
||||
exported via NFS. (default: true)
|
||||
* **security_capability=BOOL**: If false return ENOATTR when xattr
|
||||
* **[kernel-permissions-check](kernel-permissions-check.md)=BOOL**:
|
||||
Controls the FUSE `default_permissions` option. Primarily for
|
||||
debugging. (default: true)
|
||||
* **security-capability=BOOL**: If false return ENOATTR when xattr
|
||||
security.capability is queried. (default: true)
|
||||
* **[xattr](xattr.md)=passthrough|noattr|nosys**: Runtime control of
|
||||
xattrs. Default is to passthrough xattr requests. 'noattr' will
|
||||
short circuit as if nothing exists. 'nosys' will respond with ENOSYS
|
||||
as if xattrs are not supported or disabled. (default: passthrough)
|
||||
* **[link_cow](link_cow.md)=BOOL**: When enabled if a regular file is
|
||||
* **[link-cow](link-cow.md)=BOOL**: When enabled if a regular file is
|
||||
opened which has a link count > 1 it will copy the file to a
|
||||
temporary file and rename over the original. Breaking the link and
|
||||
providing a basic copy-on-write function similar to
|
||||
@@ -101,7 +106,7 @@ config file.
|
||||
means it will always use all branches in statfs calculations. 'full'
|
||||
is in effect path preserving and only includes branches where the
|
||||
path exists. (default: base)
|
||||
* **statfs_ignore=none|ro|nc**: 'ro' will cause statfs calculations to
|
||||
* **statfs-ignore=none|ro|nc**: 'ro' will cause statfs calculations to
|
||||
ignore available space for branches mounted or tagged as 'read-only'
|
||||
or 'no create'. 'nc' will ignore available space for branches tagged
|
||||
as 'no create'. (default: none)
|
||||
@@ -118,20 +123,20 @@ config file.
|
||||
directory and symlink to it.
|
||||
* **[readahead](readahead.md)=UINT**: Set readahead (in kilobytes) for
|
||||
mergerfs and branches if greater than 0. (default: 0)
|
||||
* **posix_acl=BOOL**: Enable POSIX ACL support (if supported by kernel
|
||||
* **posix-acl=BOOL**: Enable POSIX ACL support (if supported by kernel
|
||||
and underlying filesystem). (default: false)
|
||||
* **async_read=BOOL**: Perform reads asynchronously. If disabled or
|
||||
* **async-read=BOOL**: Perform reads asynchronously. If disabled or
|
||||
unavailable the kernel will ensure there is at most one pending read
|
||||
request per file handle and will attempt to order requests by
|
||||
offset. (default: true)
|
||||
* **[fuse_msg_size](fuse_msg_size.md)=PAGESIZE**: Set the max number of
|
||||
* **[fuse-msg-size](fuse-msg-size.md)=PAGESIZE**: Set the max number of
|
||||
pages per FUSE message. Only available on Linux >= 4.20 and ignored
|
||||
otherwise. (min: 1; max: 65535; default: "1M")
|
||||
* **[threads](threads.md)=INT**: Number of threads to use. When used
|
||||
alone (`process-thread-count=-1`) it sets the number of threads
|
||||
reading and processing FUSE messages. When used together it sets the
|
||||
number of threads reading from FUSE. When set to zero it will
|
||||
attempt to discover and use the number of logical cores. If the
|
||||
* **[read-thread-count](threads.md)=INT**: Number of threads to
|
||||
use. When used alone (`process-thread-count=-1`) it sets the number
|
||||
of threads reading and processing FUSE messages. When used together
|
||||
it sets the number of threads reading from FUSE. When set to zero it
|
||||
will attempt to discover and use the number of logical cores. If the
|
||||
thread count is set negative it will look up the number of cores
|
||||
then divide by the absolute value. ie. threads=-2 on an 8 core
|
||||
machine will result in 8 / 2 = 4 threads. There will always be at
|
||||
@@ -139,7 +144,6 @@ config file.
|
||||
`process-thread-count` then it will try to pick reasonable values
|
||||
based on CPU thread count. NOTE: higher number of threads increases
|
||||
parallelism but usually decreases throughput. (default: 0)
|
||||
* **[read-thread-count](threads.md)=INT**: Alias for `threads`.
|
||||
* **[process-thread-count](threads.md)=INT**: Enables separate thread
|
||||
pool to asynchronously process FUSE requests. In this mode
|
||||
`read-thread-count` refers to the number of threads reading FUSE
|
||||
@@ -150,7 +154,7 @@ config file.
|
||||
time. Meaning the total memory usage of the queues is queue depth
|
||||
multiplied by the number of process threads plus read thread
|
||||
count. 0 sets the depth to the same as the process thread
|
||||
count. (default: 0)
|
||||
count. (default: 2)
|
||||
* **[pin-threads](pin-threads.md)=STR**: Selects a strategy to pin
|
||||
threads to CPUs (default: unset)
|
||||
* **[flush-on-close](flush-on-close.md)=never|always|opened-for-write**:
|
||||
@@ -159,15 +163,17 @@ config file.
|
||||
* **scheduling-priority=INT**: Set mergerfs' scheduling
|
||||
priority. Valid values range from -20 to 19. See `setpriority` man
|
||||
page for more details. (default: -10)
|
||||
* **[proxy-ioprio](proxy-ioprio.md)=BOOL**: Use ioprio value from
|
||||
calling threads when reading and writing. (default: false)
|
||||
* **fsname=STR**: Sets the name of the filesystem as seen in
|
||||
**mount**, **df**, etc. Defaults to a list of the source paths
|
||||
concatenated together with the longest common prefix removed.
|
||||
* **[func.FUNC](functions_categories_policies.md)=POLICY**: Sets the
|
||||
specific FUSE function's policy. See below for the list of value
|
||||
types. Example: **func.getattr=newest**
|
||||
* **[func.readdir](func_readdir.md)=seq|cosr|cor|cosr:INT|cor:INT**: Sets `readdir`
|
||||
policy. INT value sets the number of threads to use for
|
||||
concurrency. (default: seq)
|
||||
* **[func.readdir](func_readdir.md)=seq|cosr|cor|cosr:INT|cor:INT**:
|
||||
Sets `readdir` policy. INT value sets the number of threads to use
|
||||
for concurrency. (default: seq)
|
||||
* **[category.action](functions_categories_policies.md)=POLICY**: Sets
|
||||
policy of all FUSE functions in the action category. (default:
|
||||
epall)
|
||||
@@ -182,7 +188,7 @@ config file.
|
||||
timeout in seconds. (default: 1)
|
||||
* **[cache.entry](cache.md#cacheentry)=UINT**: File name lookup cache
|
||||
timeout in seconds. (default: 1)
|
||||
* **[cache.negative_entry](cache.md#cachenegative_entry)=UINT**:
|
||||
* **[cache.negative-entry](cache.md#cachenegative-entry)=UINT**:
|
||||
Negative file name lookup cache timeout in seconds. (default: 0)
|
||||
* **[cache.files](cache.md#cachefiles)=off|partial|full|auto-full|per-process**:
|
||||
File page caching mode (default: off)
|
||||
@@ -201,15 +207,23 @@ config file.
|
||||
with `cache.files=per-process` (if the process is not in
|
||||
`process-names`) or `cache.files=off`. (Is a kernel feature added in
|
||||
v6.2) (default: true)
|
||||
* **[passthrough](passthrough.md)**: Enable [FUSE IO
|
||||
* **[passthrough.io](passthrough.md)=ENUM**: Enable [FUSE IO
|
||||
passthrough](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
|
||||
if available. (default: off)
|
||||
* **gid-cache-expire-timeout**: Number of seconds till supplemental
|
||||
group data is refreshed in the [GID
|
||||
cache](../known_issues_bugs.md#supplemental-user-groups). (default:
|
||||
3600)
|
||||
* **gid-cache-remove-timeout**: Number of seconds to wait till cached
|
||||
data is removed due to lack of usage. (default: 43200)
|
||||
* **passthrough.max-stack-depth=INT**: Set to `1` another filesystem
|
||||
can be stacked on mergerfs. Set to `2` to have mergerfs stacked over
|
||||
another filesystem. (default: 1)
|
||||
* **never-forget-nodes=BOOL**: Keep the internal representation of
|
||||
a file after the OS tells mergerfs it is no longer needed, until the
|
||||
node is unlinked, removed, or replaced. Really only needed for
|
||||
[exporting mergerfs via NFS](../remote_filesystems.md). Formerly
|
||||
`noforget`. (default: false)
|
||||
* **allow-idmap=BOOL**: Enables idmap (identity mapping). (default:
|
||||
true)
|
||||
* **debug**: Enable debugging information. At the moment limited to a
|
||||
FUSE message trace saved to `log.file`. (default: false)
|
||||
* **log.file**: The file path for the FUSE message trace. Empty string
|
||||
will set to stderr. (default: stderr)
|
||||
|
||||
**NOTE:** Options are evaluated in the order listed so if the options
|
||||
are **func.rmdir=rand,category.action=ff** the **action** category
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# passthrough
|
||||
# passthrough.io
|
||||
|
||||
* default: `off`
|
||||
* arguments:
|
||||
* `off`: Passthrough is never enabled.
|
||||
* `ro`: Only enable passthrough when file opened for reading only.
|
||||
* `wo`: Only enable passthrough when file opened for writing only.
|
||||
* `rw`: Enable passthrough when file opened for reading, writing,
|
||||
or both.
|
||||
* `ro`: Only enable IO passthrough when file opened for reading only.
|
||||
* `wo`: Only enable IO passthrough when file opened for writing only.
|
||||
* `rw`: Enable IO passthrough when file opened for reading, writing,
|
||||
or both.
|
||||
|
||||
In [Linux 6.9](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
|
||||
a IO passthrough feature was added to FUSE. Typically `mergerfs` has
|
||||
@@ -14,7 +14,7 @@ to act as an active proxy for all read and write requests. This
|
||||
results in, at times, significant overhead compared to direct
|
||||
interaction with the underlying filesystems. Not because `mergerfs` is
|
||||
doing anything particularly slow but because the additional
|
||||
communication and data transfers required. With the `passthrough`
|
||||
communication and data transfers required. With the `passthrough.io`
|
||||
feature `mergerfs` is able to instruct the kernel to perform reads and
|
||||
writes directly on the underlying file. Bypassing `mergerfs` entirely
|
||||
(specifically and only for reads and writes) and thereby providing
|
||||
@@ -31,46 +31,53 @@ features related to read/write are affected.
|
||||
* [readahead](readahead.md): Still affects the `readahead` values of
|
||||
underlying filesystems and `mergerfs` itself but no longer relevant
|
||||
to `mergerfs` in that it is not servicing IO requests.
|
||||
* [fuse_msg_size](fuse_msg_size.md): The primary reason to increase
|
||||
* [fuse-msg-size](fuse-msg-size.md): The primary reason to increase
|
||||
the FUSE message size is to allow transferring more data per request
|
||||
which helps improve read and write performance. Without read/write
|
||||
requests being sent to `mergerfs` there is little reason to have
|
||||
larger message sizes since the only other message larger than 1
|
||||
page, directory reading, currently has a small fixed buffer size.
|
||||
* parallel-direct-writes: Not only is `direct-io` and `passthrough`
|
||||
* parallel-direct-writes: Not only is `direct-io` and `passthrough.io`
|
||||
mutually exclusive but since `mergerfs` no longer receives write
|
||||
requests in passthrough mode then there is no parallel writes
|
||||
possible.
|
||||
* [cache.writeback](cache.md): FUSE's writeback caching is
|
||||
incompatible with `passthrough`. If `cache.writeback=true` when
|
||||
enabling `passthrough` it will be reset to `false`.
|
||||
* [cache.files](cache.md): Must be enabled for `passthrough` to
|
||||
incompatible with `passthrough.io`. If `cache.writeback=true` when
|
||||
enabling `passthrough.io` it will be reset to `false`.
|
||||
* [cache.files](cache.md): Must be enabled for `passthrough.io` to
|
||||
work. When `cache.files=off` FUSE's `direct-io` mode is enabled
|
||||
which overrides `passthrough.` Meaning `cache.files` should be set
|
||||
to `partial`, `full` or `auto-full` to use `passthrough`. If
|
||||
`cache.files` is set to `off` when using `passthrough` it will
|
||||
which overrides `passthrough.io`. Meaning `cache.files` should be
|
||||
set to `partial`, `full` or `auto-full` to use `passthrough.io`. If
|
||||
`cache.files` is set to `off` when using `passthrough.io` it will
|
||||
reset it to `auto-full`.
|
||||
|
||||
Technically `mergerfs` has the ability to choose options like
|
||||
`passthrough`, `direct-io`, and page caching independently for every
|
||||
file opened. However, at the moment there is no use case for picking
|
||||
and choosing which to enable outside `cache.files=per-process` (which
|
||||
is largely unnecessary on Linux v6.6 and above. See
|
||||
`passthrough.io`, `direct-io`, and page caching independently for
|
||||
every file opened. However, at the moment there is no use case for
|
||||
picking and choosing which to enable outside `cache.files=per-process`
|
||||
(which is largely unnecessary on Linux v6.6 and above. See
|
||||
[direct-io-allow-mmap](options.md)) If such a use case arises please
|
||||
reach out to the author to discuss.
|
||||
[reach out to the author](../support.md) to discuss.
|
||||
|
||||
Unlike [preload.so](../tooling.md#preloadso), `passthrough` will work for
|
||||
any software interacting with `mergerfs`. However, `passthrough`
|
||||
requires Linux v6.9 or above to work.
|
||||
Unlike [preload.so](../tooling.md#preloadso), `passthrough.io` will
|
||||
work for any software interacting with `mergerfs`. However,
|
||||
`passthrough.io` requires Linux v6.9 or above to work.
|
||||
|
||||
**NOTE:** This feature will **ONLY** work if `mergerfs` is running as
|
||||
`root` as currently only `root` is allowed to leverage the kernel
|
||||
feature.
|
||||
|
||||
**NOTE:** If a file has been opened and passthrough enabled, while that
|
||||
file is open, if another open request is made `mergerfs` must also
|
||||
enable `passthrough` for the second open request. This is a limitation
|
||||
of how the passthrough feature works.
|
||||
**NOTE:** If a file has been opened and `passthrough.io` enabled,
|
||||
while that file is open, if another open request is made `mergerfs`
|
||||
must also enable `passthrough.io` for the second open request. This is
|
||||
a limitation of how the passthrough feature works.
|
||||
|
||||
**NOTE:** In order to add `passthrough.io` feature to `mergerfs` it
|
||||
was necessary to remove the "feature" where mergerfs could open the
|
||||
same file on different branches. Such as using `func.open=rand` and
|
||||
having multiple files at the same relative path across different
|
||||
branches. This "feature" was very very rarely used and it was
|
||||
impossible to support `passthrough.io` without changing that behavior.
|
||||
|
||||
|
||||
## Alternatives
|
||||
@@ -96,7 +103,7 @@ Summary: passthrough is ~95% native speed. 200% faster than
|
||||
|
||||
* straight to tmpfs: 1.7 GB/s
|
||||
* nullrw=true: 2.1 GB/s
|
||||
* cache.files=off; passthrough=off: 800 MB/s
|
||||
* cache.files=auto-full; passthrough=rw: 1.6 GB/s
|
||||
* cache.files=off; passthrough.io=off: 800 MB/s
|
||||
* cache.files=auto-full; passthrough.io=rw: 1.6 GB/s
|
||||
* cache.files=auto-full; cache.writeback=false: 518 MB/s
|
||||
* cache.files=auto-full; cache.writeback=true: 518 MB/s
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# proxy-ioprio
|
||||
|
||||
* type: `BOOL`
|
||||
* default: `false`
|
||||
* example: `proxy-ioprio=true`
|
||||
|
||||
In Linux certain process schedulers have the ability to
|
||||
[prioritize](https://man7.org/linux/man-pages/man2/ioprio_set.2.html)
|
||||
based on a per thread [IO
|
||||
priority](https://www.kernel.org/doc/Documentation/block/ioprio.txt)
|
||||
assigned to the software by users using tools such as
|
||||
[ionice](https://man7.org/linux/man-pages/man1/ionice.1.html). Since
|
||||
such details are not provided by the FUSE protocol, users may not use
|
||||
schedulers which support IO priority, and the extra overhead to query
|
||||
the priority and set it within mergerfs it does not by default attempt
|
||||
to mirror/proxy the value. When enabled however, for all read and
|
||||
write requests, mergerfs will query the priority of the requesting
|
||||
process/thread and set the same value on the thread within mergerfs
|
||||
making the read/write.
|
||||
|
||||
This may be useful in situation where the system has high IO load or
|
||||
processes are known to have varying priorities. See the [man
|
||||
page](https://man7.org/linux/man-pages/man2/ioprio_set.2.html) for
|
||||
specific details.
|
||||
|
||||
Keep in mind that if no IO scheduler has been set for a thread then by
|
||||
default the IO priority will match the CPU nice value which for
|
||||
mergerfs is set by [scheduling-priority](options.md). [Nice
|
||||
value](https://man7.org/linux/man-pages/man2/getpriority.2.html)
|
||||
proxying may be added in a future release.
|
||||
|
||||
|
||||
## Conflicts With Other Options
|
||||
|
||||
If using [IO passthrough](passthrough.md) there is no reason to set
|
||||
this value to `true`. However, since `passthrough.io` leads to
|
||||
mergerfs IO calls being bypassed entirely will have no impact on
|
||||
performance or behavior regardless.
|
||||
|
||||
When using `passthrough.io` the IO priority of the client app would be
|
||||
used directly given the IO is passed through.
|
||||
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
Only Linux. This is not supported on FreeBSD.
|
||||
|
||||
|
||||
## Performance Impact
|
||||
|
||||
Despite the additional syscalls requires the impact appears
|
||||
undetectable with larger buffer sizes. At very small buffer sizes
|
||||
(such as 512 bytes) there is a noticable but small impact (~1.5%).
|
||||
@@ -6,7 +6,7 @@ value unit is in kibibytes.
|
||||
* `readahead=1024`
|
||||
|
||||
While the max size of messages sent between the kernel and mergerfs is
|
||||
configurable via the [fuse_msg_size](fuse_msg_size.md) option that
|
||||
configurable via the [fuse-msg-size](fuse-msg-size.md) option that
|
||||
doesn't mean that is the size used by the kernel for read and
|
||||
writes.
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
If using path preservation and a `rename` fails with `EXDEV`:
|
||||
|
||||
1. Move file from `/branch/a/b/c` to `/branch/.mergerfs_rename_exdev/a/b/c`.
|
||||
1. Move file from `/mnt/hdd/disk0/a/b/c` to
|
||||
`/mnt/hdd/disk0/.mergerfs_rename_exdev/a/b/c`.
|
||||
2. symlink the rename's `newpath` to the moved file.
|
||||
|
||||
The `target` value is determined by the value of `rename-exdev`.
|
||||
|
||||
@@ -55,6 +55,6 @@ returned but it will still be possible.
|
||||
|
||||
## Additional Reading
|
||||
|
||||
* [Do hardlinks work?](../faq/technical_behavior_and_limitations.md#do-hardlinks-work)
|
||||
* [Do hardlinks work?](../faq/technical_behavior_and_limitations.md#do-hard-links-work)
|
||||
* [How does mergerfs handle moving and copying of files?](../faq/technical_behavior_and_limitations.md#how-does-mergerfs-handle-moving-and-copying-of-files)
|
||||
* [Why are file moves and renames failing?](../faq/why_isnt_it_working.md#why-are-file-moves-renames-linkshardlinks-failing)
|
||||
* [Why are file moves and renames failing?](../faq/why_isnt_it_working.md#why-are-file-moves-renames-linkshard-links-failing)
|
||||
|
||||
@@ -88,11 +88,18 @@ filesystems. Mainly `ext4`.
|
||||
|
||||
## Logging
|
||||
|
||||
Filesystems, and therefore mergerfs, are doing lots of small actions
|
||||
at high speed. It simply isn't reasonable to log all the actions of
|
||||
the system. That said: certain details are logged at startup and when
|
||||
performing maintenance tasks. These are logged via `syslog` and on
|
||||
`systemd` based systems can be viewed by running
|
||||
Filesystems, and therefore mergerfs, are doing **lots** of small
|
||||
actions at high speed. It simply isn't reasonable to log all the
|
||||
actions of the system all the time. However, there is a debug mode
|
||||
which can be toggled on as needed, even at runtime, which will record
|
||||
some information to help with debugging. The main thing it will record
|
||||
is a trace of all FUSE messages to the location defined by the
|
||||
`log.file` option. See [config section](config/options.md) for more
|
||||
details.
|
||||
|
||||
That said even with debug mode disabled mergerfs will log certain
|
||||
details via `syslog` which on `systemd` based systems can be viewed by
|
||||
running:
|
||||
|
||||
```
|
||||
journalctl -t mergerfs
|
||||
|
||||
@@ -14,11 +14,11 @@ bottlenecked by their network, internet connection, or limited size of
|
||||
the cache. However, there are a few situations where a tiered cache
|
||||
setup could help.
|
||||
|
||||
1. Fast network, slow filesystems, many readers: You've a 10+Gbps
|
||||
1. Fast network, slow filesystems, many readers: You've a 10Gbps+
|
||||
network with many readers and your regular filesystems can't keep
|
||||
up.
|
||||
2. Fast network, slow filesystems, small'ish bursty writes: You have
|
||||
a 10+Gbps network and wish to transfer amounts of data less than
|
||||
a 10Gbps+ network and wish to transfer amounts of data less than
|
||||
your cache filesystem but wish to do so quickly and the time
|
||||
between bursts is long enough to migrate data.
|
||||
|
||||
@@ -27,8 +27,8 @@ level that can aggregate performance or using higher performance
|
||||
storage would probably be the better solution. If you're going to use
|
||||
mergerfs there are other tactics that may help: spreading the data
|
||||
across filesystems (see the mergerfs.dup tool) and setting
|
||||
`func.open=rand`, using `symlinkify`, or using dm-cache or a similar
|
||||
technology to add tiered cache to the underlying device itself.
|
||||
`func.open=rand` or using dm-cache or a similar technology to add
|
||||
tiered cache to the underlying device itself.
|
||||
|
||||
With #2 one could use a block cache solution as available via LVM and
|
||||
dm-cache but there is another solution which requires only mergerfs, a
|
||||
@@ -52,15 +52,27 @@ script to move files around, and a cron job to run said script.
|
||||
* Set your programs to use the **cache** pool.
|
||||
* Configure the **base** pool with the `create` policy you would like
|
||||
to lay out files as you like.
|
||||
* Save one of the below scripts or create your own. The script's
|
||||
* Use monstermuffin's
|
||||
[mergerfs-cache-mover](https://github.com/monstermuffin/mergerfs-cache-mover),
|
||||
one of the scripts below, or create your own. The script's
|
||||
responsibility is to move files from the **cache** branches (not
|
||||
pool) to the **base** pool.
|
||||
* Use `cron` (as root) to schedule the command at whatever frequency
|
||||
is appropriate for your workflow.
|
||||
|
||||
**NOTE:** Due to the additional overhead it is not recommended to nest
|
||||
or otherwise create hierarchies of mergerfs pools. It will work but
|
||||
the latency increases will further harm performance. Even when using
|
||||
passthrough IO or other features.
|
||||
|
||||
|
||||
### time based expiring
|
||||
|
||||
**NOTE:** this script is an **example**, not particularly efficient, and
|
||||
really not intended for production deployments. Until such time that
|
||||
mergerfs provides tooling for this usecase consider using
|
||||
[mergerfs-cache-mover](https://github.com/monstermuffin/mergerfs-cache-mover).
|
||||
|
||||
Move files from cache filesystem to base pool which have an access
|
||||
time older than the supplied number of days. Replace `-atime` with
|
||||
`-amin` in the script if you want minutes rather than days.
|
||||
@@ -92,6 +104,12 @@ respectively.
|
||||
|
||||
### percentage full expiring
|
||||
|
||||
**NOTE:** this script is an **example**, not particularly efficient, and
|
||||
really not intended for production deployments. Until such time that
|
||||
mergerfs provides tooling for this usecase consider using
|
||||
[mergerfs-cache-mover](https://github.com/monstermuffin/mergerfs-cache-mover).
|
||||
|
||||
|
||||
While the cache filesystem's percentage full is above the provided
|
||||
value move the oldest file from the cache filesystem to the base pool.
|
||||
|
||||
|
||||
@@ -5,16 +5,10 @@
|
||||
[Primarily Linux.](../setup/installation.md) FreeBSD is casually
|
||||
supported but not well tested.
|
||||
|
||||
With FreeBSD certain Linux functions and FUSE features are not
|
||||
supported. In many cases the absense will not be noticed however
|
||||
performance may be impacted due to a core mergerfs design decision
|
||||
relying on a Linux feature.
|
||||
|
||||
Linux allows individual threads to change credentials whereas [FreeBSD
|
||||
does not](https://wiki.freebsd.org/Per-Thread%20Credentials). As a
|
||||
result mergerfs must use a lock to ensure critical sections which need
|
||||
to change credentials are safeguarded. This will limit throughput on
|
||||
systems where requests to mergerfs come from multiple user identities.
|
||||
With FreeBSD certain Linux functions and FUSE features [are not
|
||||
supported.](../known_issues_bugs.md#freebsd-version) In many cases the
|
||||
absence will not be noticed however more advanced features will not be
|
||||
available.
|
||||
|
||||
|
||||
### Why not support MacOS?
|
||||
@@ -36,33 +30,90 @@ level API.
|
||||
|
||||
Windows, while used for NAS systems more often than MacOS, is still
|
||||
relatively uncommon when compared to Linux. [Drive
|
||||
Pool](../project_comparisons.md#stablebits-drivepool) is a reasonable
|
||||
alternative.
|
||||
Pool](../project_comparisons.md#stablebits-drivepool) is a good
|
||||
alternative. It may also be possible to use mergerfs with WSL.
|
||||
|
||||
|
||||
## What filesystems can be used as branches?
|
||||
|
||||
ext4, btrfs, xfs, f2fs, zfs, nfs, etc.
|
||||
|
||||
On the surface any filesystem should work but there could be issues
|
||||
with non-POSIX compliant filesystems such as vfat, ntfs, cifs, exfat,
|
||||
etc. When directories need to be created or files moved by mergerfs if
|
||||
the filesystem returns errors due to not supporting certain POSIX
|
||||
filesystem features it could result in the core functions failing.
|
||||
Most any filesystem should work but there could be issues with
|
||||
non-POSIX compliant filesystems such as nfs (w/ root squashing), vfat,
|
||||
ntfs, cifs, exfat, etc. When directories need to be created or files
|
||||
moved by mergerfs if the filesystem returns errors due to not
|
||||
supporting certain POSIX filesystem features it could result in the
|
||||
core functions failing.
|
||||
|
||||
Since mergerfs is not generally used with non-POSIX filesystems this
|
||||
has not been a problem for users and there are some checks for known
|
||||
edgecases but it is possible some are not accounted for. If use with a
|
||||
edge cases but it is possible some are not accounted for. If use with a
|
||||
filesystem results in issues please [file a
|
||||
ticket](https://github.com/trapexit/mergerfs/issues) with the details.
|
||||
|
||||
|
||||
## What types of storage devices does mergerfs work with?
|
||||
|
||||
Since mergerfs works at the filesystem level rather than device level
|
||||
there is no compatibility concerns based on the physical storage
|
||||
devices involved.
|
||||
|
||||
|
||||
## Can I use mergerfs without SnapRAID? SnapRAID without mergerfs?
|
||||
|
||||
[https://www.snapraid.it](https://www.snapraid.it)
|
||||
|
||||
Yes. They are completely unrelated pieces of software that just happen
|
||||
to work well together.
|
||||
|
||||
|
||||
## How does mergerfs interact with SnapRAID recovery behavior?
|
||||
|
||||
SnapRAID does not choose parity layout based on directory structure or
|
||||
relative path. As each file is discovered, SnapRAID fills parity space
|
||||
for that file based on its size using the next available parity area.
|
||||
This makes the logical parity layout depend on discovery order, file
|
||||
size, and how much data already exists on each filesystem at that
|
||||
time. Filesystems with similar used space, regardless of total
|
||||
capacity, are more likely to place newly added files into similar
|
||||
parity positions.
|
||||
|
||||
Because of that, if files are changed or removed across multiple
|
||||
branches and those files happen to overlap in parity space, recovery
|
||||
of those deleted files may be less reliable. This is not a general
|
||||
concern, is not unique to mergerfs usage, and it does not affect
|
||||
filesystem failure recovery. It is specifically a concern when trying
|
||||
to recover from accidental deletion of files spread across multiple
|
||||
filesystems.
|
||||
|
||||
In practice this mostly matters when the filesystems have very similar
|
||||
used space, the files are created across those filesystems around the
|
||||
same time, and you later want SnapRAID to recover an accidental
|
||||
deletion spanning multiple filesystems. Otherwise this is usually not
|
||||
something to worry about.
|
||||
|
||||
If, however, this is a concern it can be beneficial to keep files on
|
||||
as few filesystems as possible or otherwise keep related files
|
||||
together. See the [collocation
|
||||
section.](configuration_and_policies.md#how-can-i-ensure-files-are-collocated-on-the-same-branch)
|
||||
|
||||
|
||||
## Can I use mergerfs without NonRAID? NonRAID without mergerfs?
|
||||
|
||||
[https://github.com/qvr/nonraid](https://github.com/qvr/nonraid)
|
||||
|
||||
Yes. They are completely unrelated pieces of software that just happen
|
||||
to work well together.
|
||||
|
||||
|
||||
### How does mergerfs interact with NonRAID?
|
||||
|
||||
There are no known special interactions or concerns between mergerfs
|
||||
and NonRAID. They are separate tools and mergerfs has no known
|
||||
behavior which requires any special configuration or caveats when used
|
||||
with NonRAID.
|
||||
|
||||
|
||||
## Does mergerfs support CoW / copy-on-write / writes to read-only filesystems?
|
||||
|
||||
Not in the sense of a filesystem like BTRFS or ZFS nor in the
|
||||
@@ -80,16 +131,8 @@ pool.
|
||||
|
||||
## Can mergerfs run via Docker, Podman, Kubernetes, etc.
|
||||
|
||||
Yes. With Docker you'll need to include `--cap-add=SYS_ADMIN
|
||||
--device=/dev/fuse --security-opt=apparmor:unconfined` or similar with
|
||||
other container runtimes. You should also be running it as root or
|
||||
given sufficient caps to allow mergerfs to change user and group
|
||||
identity as well as have root like filesystem permissions. This
|
||||
ability is critical to how mergerfs works.
|
||||
|
||||
Also, as mentioned by [hotio](https://hotio.dev/containers/mergerfs),
|
||||
with Docker you should probably be mounting with `bind-propagation`
|
||||
set to `slave`.
|
||||
Yes. [See installation
|
||||
page.](../setup/installation.md#podman-docker-oci-containers)
|
||||
|
||||
|
||||
## How does mergerfs interact with user namespaces?
|
||||
@@ -106,13 +149,79 @@ It is generally recommended to not use user namespacing / id mapping
|
||||
given the complication it introduces.
|
||||
|
||||
|
||||
## Does mergerfs work with SELinux relabeling?
|
||||
|
||||
FUSE filesystems, including mergerfs, do not support per-inode
|
||||
SELinux labels (see [Red Hat bug
|
||||
1272868](https://bugzilla.redhat.com/show_bug.cgi?id=1272868) and the
|
||||
[upstream fuse-devel
|
||||
thread](https://sourceforge.net/p/fuse/mailman/fuse-devel/thread/CAN9vWDLGA4%3Dg3peKVWkX7gLRiVUUvJAQx9kiXHgqdeX6eh0MYA%40mail.gmail.com/)).
|
||||
When a container runtime such as Podman or Docker is asked to relabel
|
||||
a bind mount with the `:z` or `:Z` mount option, it first probes the
|
||||
source for `security.selinux` xattr support. On a FUSE path that
|
||||
probe returns `EOPNOTSUPP` and the runtime skips relabeling entirely.
|
||||
Files inside the mount keep the `fusefs_t` type assigned by the
|
||||
kernel. No tree walk is performed and no journal noise is produced at
|
||||
default verbosity. The runtime logs `Labeling not supported on
|
||||
"<path>"` only at debug level (for example
|
||||
`podman --log-level=debug`). The flag is therefore a no-op against a
|
||||
mergerfs mount; not harmful, but misleading.
|
||||
|
||||
Containers can still read and write a mergerfs pool without `:z`
|
||||
because two independent access checks must succeed and both can be
|
||||
satisfied without any relabel:
|
||||
|
||||
* **SELinux:** on Fedora, RHEL / CentOS Stream, and SUSE the shipped
|
||||
[container-selinux](https://github.com/containers/container-selinux)
|
||||
policy already permits the `container_t` process type to manage
|
||||
files of type `fusefs_t`. See `container_selinux(8)`.
|
||||
* **DAC:** the in-container UID and GID must still be able to read
|
||||
and write the files on the underlying branches. With rootless
|
||||
Podman this is most easily arranged by aligning the container UID
|
||||
with the host owner of the branches (for example
|
||||
`--userns=keep-id`, or Quadlet `UserNS=keep-id`).
|
||||
|
||||
The recommendation is to omit `:z` and `:Z` on bind mounts whose
|
||||
source is a mergerfs (or any other FUSE) path, and to keep them on
|
||||
bind mounts whose source is a regular local filesystem (ext4, btrfs,
|
||||
xfs, etc.) where relabeling does real work. On distributions whose
|
||||
container-selinux policy does not already include the `fusefs_t`
|
||||
allow rule, the targeted fix is to set a single label at mount time
|
||||
by adding `context="system_u:object_r:container_file_t:s0"` to the
|
||||
mergerfs mount options (for example in `/etc/fstab`). Files inside
|
||||
the pool then appear as `container_file_t`, which container-selinux
|
||||
permits `container_t` to manage everywhere. See [Red Hat's mounting
|
||||
file systems
|
||||
guide](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/selinux_users_and_administrators_guide/sect-security-enhanced_linux-working_with_selinux-mounting_file_systems).
|
||||
This is preferred over the much broader
|
||||
`--security-opt label=disable`, which removes SELinux confinement
|
||||
from the container as a whole. The trade-off of `context=` is that
|
||||
host-side processes touching the mount (rsync, backup tools, web
|
||||
UIs, cron jobs) see `container_file_t` rather than `fusefs_t` and
|
||||
may require a small SELinux policy adjustment.
|
||||
|
||||
To verify on your own system:
|
||||
|
||||
```
|
||||
podman --log-level=debug run --rm \
|
||||
-v /path/to/mergerfs:/data:z \
|
||||
alpine true 2>&1 | grep -iE 'relabel|setfilecon|Labeling'
|
||||
```
|
||||
|
||||
Expected output is a single line of the form
|
||||
`Labeling not supported on "/path/to/mergerfs"` and nothing in
|
||||
`ausearch -m AVC,SELINUX_ERR` for the mount.
|
||||
|
||||
|
||||
## Can mergerfs be used with NFS root squash?
|
||||
|
||||
If mergerfs is pooling a NFS mount then root squash should be disabled
|
||||
If mergerfs is pooling a NFS mount then root squash must be disabled
|
||||
as mergerfs needs to be able to have elevated privileges to do what it
|
||||
does.
|
||||
does (set permissions, ownership, etc.)
|
||||
|
||||
If you are exporting mergerfs over NFS then it is not really necessary.
|
||||
If you are exporting mergerfs over NFS it is not strictly necessary to
|
||||
disable root squashing but 3rd party software running as root may
|
||||
fail.
|
||||
|
||||
See the [section on remote filesystems.](../remote_filesystems.md)
|
||||
|
||||
|
||||
@@ -18,37 +18,88 @@ mergerfs [does not impact](usage_and_functionality.md) the underlying
|
||||
filesystems and can be added or removed without any impact it is
|
||||
extremely easy to test and experiment with different settings.
|
||||
|
||||
[mergerfs-webui](../tooling.md#mergerfs-webui) provides a browser-based
|
||||
way to inspect and change settings, branches, and policies on Linux,
|
||||
and edit startup configuration. It does not replace understanding
|
||||
what the options do or choosing policies suited to your use case.
|
||||
Runtime changes alone do not persist across remounts.
|
||||
|
||||
Additional reading:
|
||||
|
||||
* [Config/Options](../config/options.md)
|
||||
|
||||
|
||||
## What create policy should I use?
|
||||
|
||||
If it isn't clear to you then you should use `pfrd` as described in
|
||||
the [quickstart guide](../quickstart.md).
|
||||
|
||||
|
||||
## Why are there no policies based on file size?
|
||||
|
||||
Because it is not possible to know the final size of a file at creation
|
||||
time.
|
||||
|
||||
See [How does mergerfs handle moving and copying of
|
||||
files?](technical_behavior_and_limitations.md#how-does-mergerfs-handle-moving-and-copying-of-files)
|
||||
and [Intro to Filesystems - Creating a
|
||||
file](../intro_to_filesystems.md#creating-a-file).
|
||||
|
||||
|
||||
## Why is pfrd the default create policy?
|
||||
|
||||
Originally the default was `epmfs` however it was found to cause
|
||||
significant confusion among new users who expected mergerfs to always
|
||||
choose a branch with available space. For mergerfs v2.41.0 it was
|
||||
decided to change the default to the less restrictive policy `pfrd`.
|
||||
significant confusion among new users who assumed mergerfs to behave
|
||||
like other software which always choose a branch with most available
|
||||
space without any filtering or restrictions. The reason for using
|
||||
`epmfs` was to allow people to use mergerfs without impacting the
|
||||
layout of their data across the branches. Keeping the total entropy of
|
||||
the system in check. This, however, was not understood by users who
|
||||
did not read the documentation and caused significant support burdens.
|
||||
|
||||
For mergerfs v2.41.0 it was decided to change the default to `pfrd` to
|
||||
minimize the support burden.
|
||||
|
||||
`pfrd` was chosen because it prioritizes placement to branches based
|
||||
on free space (percentage wise) without overloading a specific branch
|
||||
as `mfs`, `lus`, or other policies could when a singular branch has
|
||||
significantly more free space. Since it is impossible to know the
|
||||
usage and access patterns of the filesystem spreading out files across
|
||||
the branches also spreads the load and therefore good for general use
|
||||
cases making it a good default.
|
||||
|
||||
Additional reading:
|
||||
|
||||
* [functions, categories and policies](../config/functions_categories_policies.md)
|
||||
|
||||
|
||||
## How can I ensure files are collocated on the same branch?
|
||||
|
||||
Many people like the idea of ensuring related files, such as all the
|
||||
files to a TV show season or songs in an album, are stored on the same
|
||||
storage device. However, most people have no actual need for this
|
||||
storage device. However, most people have no technical need for this
|
||||
behavior.
|
||||
|
||||
1. If you backup your data it is extremely likely your backup solution
|
||||
can restore only those files you are missing.
|
||||
2. Software such as **Sonarr** can manage the downloading and post
|
||||
processing of bespoke episodes which may be missing in a
|
||||
season. Either by downloading the episode individually if available
|
||||
or by downloading a full season.
|
||||
can restore only those files you are missing. There is no need to
|
||||
store data in related units to ensure restoring data is easy.
|
||||
2. Software such as [Sonarr](https://sonarr.tv) can manage the
|
||||
downloading and post processing of bespoke files / episodes which
|
||||
may be missing. Either by downloading the episode individually if
|
||||
available or by downloading a full season.
|
||||
3. There is no benefit to keeping files collocated with regard to
|
||||
drive spinup, caching, or other secondary concern.
|
||||
drive spinup, caching, or other secondary concern. In fact
|
||||
depending on usage patterns placing the data on the same underlying
|
||||
device could harm performance.
|
||||
|
||||
The main use case for wanting collocation is where the branch is going
|
||||
to be removed from the pool and you wish to have all data from some
|
||||
logical set on that device. Such as you intend to take a drive out of
|
||||
the pool to take on a trip and want a whole show on the
|
||||
drive. However, even in these situations you typically end up needing
|
||||
to curate the files anyway because it has show A but not show B.
|
||||
to be removed from the pool and used in isolation temporarily and you
|
||||
wish to have all data from some logical set on that device. Such as
|
||||
you intend to take a drive out of the pool to take on a trip and want
|
||||
a whole show or artist's works on the drive. However, even in these
|
||||
situations you typically end up needing to curate the files anyway
|
||||
because it has show A but not show B. In such cases it is more
|
||||
convient to use a separate devices and sync what you wish to it.
|
||||
|
||||
All that said you can accomplish collocation to varying degrees using
|
||||
the following methods:
|
||||
@@ -56,33 +107,37 @@ the following methods:
|
||||
1. Use
|
||||
[mergerfs.consolidate](https://github.com/trapexit/mergerfs-tools/blob/master/src/mergerfs.consolidate)
|
||||
when consolidation is needed.
|
||||
2. Use a `msp` create policy.
|
||||
2. Use a `msp` create policy to do a best effort collocation without
|
||||
needing explicit directory curation.
|
||||
3. Use `epmfs` or other `ep` create policy and manually create paths
|
||||
on the branches directly.
|
||||
4. Use a `ep` `create` policy and `rand` for `mkdir`.
|
||||
on the branches directly as you need/desire.
|
||||
4. Use a `ep` `create` policy and `pfrd` or `rand` for `mkdir`.
|
||||
|
||||
|
||||
## How can I balance files across the pool?
|
||||
|
||||
Similar to collocation there is generally little reason to balance
|
||||
files.
|
||||
files across the pool branches.
|
||||
|
||||
1. Since prediction of a filesystem's death or loss of data is near
|
||||
impossible there is little reason to balance in hopes of limiting
|
||||
data loss.
|
||||
1. Since prediction of a device's/filesystem's death or loss of data
|
||||
is near impossible there is little reason to balance in hopes of
|
||||
limiting data loss.
|
||||
2. While performance could be impacted by having too much reading or
|
||||
writing happen to singular underlying filesystems balancing won't
|
||||
help unless you have the ability to manage the access patterns to
|
||||
the pool.
|
||||
3. Over time most configurations will lead to a random distribution of
|
||||
files across the branches which is effectively "balancing."
|
||||
3. Over time the churn of adding and removing files will lead to a
|
||||
random distribution of files across the branches which is
|
||||
effectively "balancing" if using create policies which do not favor
|
||||
any particular branch.
|
||||
|
||||
If you wish to move files around or balance the pool you can:
|
||||
All that said if you wish to move files around or balance the pool you
|
||||
can:
|
||||
|
||||
1. Use `rand` or `pfrd` create policies and just use your system as
|
||||
normal.
|
||||
2. Write simple scripts using rsync or similar to move files around as
|
||||
you wish.
|
||||
2. Write scripts using rsync or similar to move files around as you
|
||||
wish.
|
||||
3. Use
|
||||
[mergerfs.balance](https://github.com/trapexit/mergerfs-tools/blob/master/src/mergerfs.balance). Keep
|
||||
in mind that this tool is really just an example of how to
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
# Downsides
|
||||
|
||||
## What are the downsides of using mergerfs?
|
||||
|
||||
mergerfs trades the features of an integrated storage system for the
|
||||
flexibility of pooling independent filesystems. It is a filesystem
|
||||
proxy, not RAID or a replacement for the underlying filesystems.
|
||||
|
||||
Some limitations are inherent to that design; others matter only for
|
||||
particular workloads, configurations, or applications. Many can be
|
||||
mitigated with appropriate settings or separate tools. The following
|
||||
is a checklist of tradeoffs to evaluate, not a list of problems every
|
||||
installation will experience.
|
||||
|
||||
### No built-in data protection or storage management
|
||||
|
||||
mergerfs does not provide redundancy, parity, integrity checks,
|
||||
snapshots, or file versioning. If a drive fails, mergerfs cannot
|
||||
recover the files on it. The other drives retain their independent
|
||||
filesystems, but that failure isolation is not redundancy or a backup.
|
||||
Protection must come from the underlying storage or separate tools,
|
||||
such as [SnapRAID](https://www.snapraid.it) or
|
||||
[NonRAID](https://github.com/qvr/nonraid).
|
||||
|
||||
It also does not automatically rebalance existing files when a branch
|
||||
is added or space usage changes. If redistribution is needed, it must
|
||||
be done separately.
|
||||
|
||||
See [Non-features](../index.md#non-features), [Project
|
||||
Comparisons](../project_comparisons.md), and [How can I balance files
|
||||
across the pool?](configuration_and_policies.md#how-can-i-balance-files-across-the-pool).
|
||||
|
||||
### No striping or contiguous pooled space
|
||||
|
||||
Each file lives entirely on one branch. Adding drives does not make a
|
||||
single-file transfer faster, and a file must fit on one eligible
|
||||
branch even if the pool reports enough total free space. For example,
|
||||
two filesystems with 6GiB free each cannot hold one 10GiB file.
|
||||
Concurrent access to different branches can still provide aggregate
|
||||
throughput.
|
||||
|
||||
See [Project Comparisons](../project_comparisons.md#raid0-jbod-span-drive-concatenation-striping)
|
||||
and [Performance](../performance.md#understanding-mergerfs-io-performance).
|
||||
|
||||
### Performance overhead
|
||||
|
||||
Filesystem requests generally pass through FUSE and the mergerfs
|
||||
process before reaching the underlying filesystem. This adds latency
|
||||
and CPU overhead compared to direct access. Directory listings and
|
||||
other metadata operations can require work across multiple branches,
|
||||
so their cost can grow as the pool grows. Page caching can also result
|
||||
in data being cached both through mergerfs and on the underlying
|
||||
filesystem.
|
||||
|
||||
[IO passthrough](../config/passthrough.md) can reduce data IO overhead,
|
||||
but does not eliminate the work of combining branches. Databases, VM
|
||||
images, and other latency-sensitive workloads should be benchmarked
|
||||
with the intended configuration rather than assumed to perform like
|
||||
direct access.
|
||||
|
||||
There is also memory overhead beyond page caching: request buffers,
|
||||
temporary directory buffers, and FUSE node bookkeeping. Usage depends
|
||||
on concurrency, buffer sizes, and the workload. Exporting the pool
|
||||
over NFS requires retaining nodes with `never-forget-nodes=true`,
|
||||
which can increase memory usage as more entries are accessed. This is
|
||||
worth considering on memory-constrained systems or large NFS exports.
|
||||
See [Resource Usage](../resource_usage.md) and [NFS exporting
|
||||
mergerfs](../remote_filesystems.md#nfs-exporting-mergerfs).
|
||||
|
||||
See [Understanding mergerfs IO
|
||||
Performance](../performance.md#understanding-mergerfs-io-performance),
|
||||
[Benchmarking](../benchmarking.md), and [Why use
|
||||
FUSE?](technical_behavior_and_limitations.md#why-use-fuse-why-not-a-kernel-based-solution).
|
||||
|
||||
### Placement and filesystem semantics need attention
|
||||
|
||||
A single mount point does not make the branches one filesystem.
|
||||
[Policies](../config/functions_categories_policies.md) determine where
|
||||
new files go and which copy is used when the same path exists on
|
||||
multiple branches. Path-preserving policies and
|
||||
[minfreespace](../config/minfreespace.md) can exclude branches from file
|
||||
creation even while the pool has free space. mergerfs cannot know a
|
||||
file's final size when it is created.
|
||||
|
||||
Hard links and renames still have underlying filesystem constraints.
|
||||
Depending on the layout and policies, an operation can return `EXDEV`
|
||||
(cross-device link) even within the pool. Applications which do not
|
||||
handle that error may fail rather than fall back to copying.
|
||||
|
||||
Directory metadata is also selected by policy rather than necessarily
|
||||
aggregated. With the default `getattr` policy of `ff`, a directory's
|
||||
timestamp comes from the first matching branch. A media scanner which
|
||||
uses that timestamp to skip unchanged directories can miss updates on
|
||||
another branch. `func.getattr=newest` can help with timestamp-based
|
||||
scans, but changes which branch supplies metadata; it does not change
|
||||
which file other operations select.
|
||||
|
||||
Corresponding directories on different branches can have different
|
||||
ownership or permissions. Since access uses the caller's credentials,
|
||||
this can result in unexpected access errors or missing entries.
|
||||
Keeping those permissions consistent and auditing with
|
||||
`fsck.mergerfs` helps. Container UID/GID mappings and non-POSIX
|
||||
branches may need additional attention; mergerfs does not bypass
|
||||
their access restrictions.
|
||||
|
||||
See [Configuration and Policies](configuration_and_policies.md),
|
||||
[rename and link](../config/rename_and_link.md), [Tips and
|
||||
Notes](../tips_notes.md), and [How does mergerfs interact with user
|
||||
namespaces?](compatibility_and_integration.md#how-does-mergerfs-interact-with-user-namespaces).
|
||||
|
||||
### Keeping drives spun down can be harder
|
||||
|
||||
Directory listings and file searches can query multiple branches,
|
||||
potentially waking drives beyond the one containing the requested
|
||||
file. If keeping disks asleep is important, this can increase power
|
||||
usage, noise, and access latency. It is not a concern for SSDs or
|
||||
drives which are kept spinning.
|
||||
|
||||
Caching and reducing background scans can limit some branch access,
|
||||
but mergerfs alone cannot guarantee that unused disks stay asleep.
|
||||
See [Limiting drive spinup](limit_drive_spinup.md) for the tradeoffs
|
||||
and possible approaches.
|
||||
|
||||
### Direct branch changes and cached views
|
||||
|
||||
Using the underlying filesystems directly is supported, but changes
|
||||
made outside the mergerfs mount are not forwarded as `inotify` or
|
||||
`fanotify` events to applications watching the pool. Changes made
|
||||
through the pool do generate notifications. If files are added
|
||||
directly to branches, applications such as media servers may need
|
||||
periodic scans, explicit refreshes, or watches on the branches
|
||||
themselves.
|
||||
|
||||
Metadata caching introduces a separate visibility tradeoff: direct
|
||||
branch changes may not appear through the pool until cached entries
|
||||
expire. Longer cache durations can reduce filesystem queries at the
|
||||
cost of less immediate visibility. Shorter durations or disabling the
|
||||
relevant caches can help if direct branch changes are common. Avoid
|
||||
uncoordinated writes to the same file through both paths, especially
|
||||
when file caching is enabled.
|
||||
|
||||
See [Does inotify and fanotify
|
||||
work?](compatibility_and_integration.md#does-inotify-and-fanotify-work),
|
||||
[Can filesystems still be used
|
||||
directly?](usage_and_functionality.md#can-filesystems-still-be-used-directly-outside-of-mergerfs-while-pooled),
|
||||
and [Caching](../config/cache.md).
|
||||
|
||||
### Branch paths do not guarantee a particular disk is mounted
|
||||
|
||||
mergerfs works on paths and does not require each branch to be a
|
||||
mount point. If an intended branch filesystem is not mounted but its
|
||||
mount-point directory still exists, mergerfs can use that directory
|
||||
on the filesystem beneath it. If writable and selected for creation,
|
||||
new files can land there instead of on the intended disk, potentially
|
||||
filling the root filesystem.
|
||||
|
||||
This is a deployment concern when pooling mounted filesystems, not a
|
||||
problem with intentionally using ordinary directories as branches.
|
||||
Configure mount dependencies or
|
||||
`branches-mount-timeout` with `branches-mount-timeout-fail=true` if
|
||||
startup should fail when branches are not ready. These are startup
|
||||
safeguards, not continuous checks that the intended disks remain
|
||||
mounted at runtime.
|
||||
|
||||
See [branches-mount-timeout](../config/branches-mount-timeout.md) and
|
||||
[What happens if a filesystem disappears at
|
||||
runtime?](usage_and_functionality.md#what-happens-if-a-filesystem-disappears-at-runtime).
|
||||
|
||||
### Dependence on branch responsiveness
|
||||
|
||||
mergerfs cannot make an unresponsive disk or remote filesystem
|
||||
responsive. A request to a blocked branch blocks the thread handling
|
||||
it. If enough threads are blocked, the pool can stop responding as
|
||||
well. Independent on-disk filesystems do not guarantee that a failing
|
||||
branch has no effect on access through the pool.
|
||||
|
||||
See [What happens if a branch filesystem
|
||||
blocks?](technical_behavior_and_limitations.md#what-happens-if-a-branch-filesystem-blocks).
|
||||
|
||||
### Compatibility limitations
|
||||
|
||||
Some filesystem features are unavailable through FUSE or require
|
||||
particular kernel versions and settings. For example, reflink requests
|
||||
(`FICLONE` / `FICLONERANGE`) are not supported through the mergerfs
|
||||
mount. Applications using `mmap` need page caching; older Linux or
|
||||
mergerfs versions require it to be explicitly enabled.
|
||||
|
||||
Advisory locks taken through the pool are not forwarded to the
|
||||
underlying branches. Local applications using only the mergerfs mount
|
||||
still have kernel-managed locks, but those locks do not coordinate
|
||||
with direct branch access or other hosts accessing a remote branch.
|
||||
|
||||
Inode identity also has tradeoffs when presenting multiple filesystems
|
||||
as one. Most applications do not need special settings, but backup or
|
||||
deduplication tools and NFS can be sensitive to inode values changing
|
||||
when files move between branches or a policy selects a different
|
||||
copy. [inodecalc](../config/inodecalc.md) offers different strategies:
|
||||
for example, `path-hash` stabilizes identity by path for NFS, but
|
||||
prevents applications from recognizing hard links by inode equality.
|
||||
The hard links themselves still work.
|
||||
|
||||
On SELinux systems, per-file relabeling is unavailable through the
|
||||
FUSE mount, so container bind-mount options `:z` and `:Z` do not
|
||||
perform their usual relabeling. This does not mean containers cannot
|
||||
use the pool: an appropriate SELinux policy or a mount-wide context
|
||||
can allow access. A mount-wide context is not a substitute for
|
||||
distinct per-file labels if the application requires them. See [Does
|
||||
mergerfs work with SELinux
|
||||
relabeling?](compatibility_and_integration.md#does-mergerfs-work-with-selinux-relabeling).
|
||||
|
||||
See [Technical Behavior and
|
||||
Limitations](technical_behavior_and_limitations.md) and [Known Issues
|
||||
and Bugs](../known_issues_bugs.md).
|
||||
|
||||
These tradeoffs do not require migrating data into a special format.
|
||||
The underlying filesystems remain usable without mergerfs, and
|
||||
mergerfs can be [removed without changing their
|
||||
data](usage_and_functionality.md#can-mergerfs-be-removed-without-affecting-the-data).
|
||||
@@ -1,22 +1,31 @@
|
||||
# Limiting drive spinup
|
||||
|
||||
## Does mergerfs do anything to prevent drive spinup?
|
||||
|
||||
No. In fact it makes it worse.
|
||||
|
||||
|
||||
## How can I setup my system to limit drive spinup?
|
||||
|
||||
TL;DR: You really can't. Not through mergerfs alone. In fact mergerfs
|
||||
makes an attempt to do so more complicated.
|
||||
TL;DR: You really can't. Not through mergerfs alone. In fact using
|
||||
mergerfs makes any general attempt to limit spinup more complicated.
|
||||
|
||||
|
||||
mergerfs is a proxy. Despite having some caching behaviors it is not
|
||||
designed to cache much more than metadata. It proxies calls between
|
||||
client software and underlying filesystems. If a client makes a
|
||||
request such as `open`, `readdir`, `stat`, etc. it must translate that
|
||||
into something that makes sense across multiple filesystems. For
|
||||
`readdir` that means running the call against all branches and
|
||||
aggregating the output. For `open` that means finding the file to open
|
||||
and doing so. The only way to find the file to open is to scan across
|
||||
all branches and sort the results and pick one. There is no practical
|
||||
way to do otherwise. Especially given so many mergerfs users expect
|
||||
out-of-band changes to "just work."
|
||||
mergerfs is a proxy. Despite having [some caching
|
||||
behaviors](../config/cache.md) it is not designed to cache much more
|
||||
than metadata. It proxies calls between client software and underlying
|
||||
filesystems. If a client makes a request such as `open`, `readdir`,
|
||||
`stat`, etc. it must translate that into something that makes sense
|
||||
across multiple filesystems. For `readdir` that means running the same
|
||||
call against all branches and aggregating the output. For `open` that
|
||||
means finding the file to open and doing so. The only way to find the
|
||||
file to open is to scan across all branches and sort the results and
|
||||
pick one. There is some variability in how much searching is done due
|
||||
to
|
||||
[policies](../config/functions_categories_policies.md#policy-descriptions)
|
||||
but not much. There is no practical way to do otherwise. Especially
|
||||
given so many mergerfs users expect out-of-band changes to "just
|
||||
work."
|
||||
|
||||
The best way to limit spinup of drives is to limit their usage at the
|
||||
client level. Meaning keeping software from interacting with the
|
||||
@@ -33,21 +42,24 @@ filesystems and on startup everything scanned and stored. From then on
|
||||
it would have to carefully update all the same data the filesystems
|
||||
do. It couldn't be kept in RAM because it would take up too much space
|
||||
so it'd have to be on a SSD or other storage device. If anything
|
||||
changed out of band it would break things in weird ways. It could
|
||||
rescan on occasion but that would require spinning up
|
||||
everything. Filesystem watches could be used to get updates when the
|
||||
filesystem changes but that would allow for race conditions and
|
||||
might keep the drives from spinning down. Something as "simple" as
|
||||
keeping the current available free space on each filesystem isn't as
|
||||
easy as one might think given reflinks, snapshots, and other block
|
||||
level dedup technologies as well as the space used includes not just
|
||||
raw file usage.
|
||||
changed out of band it would break things in weird ways (and MANY
|
||||
users depend on out of band changes working.) It could rescan on
|
||||
occasion but that would require spinning up everything. Filesystem
|
||||
watches could be used but since mergerfs is just another piece of
|
||||
software interacting with the filesystem ALL mergerfs requests would
|
||||
be echoed back to it causing lots of overhead throwing away a super
|
||||
majority of events it triggered. And something as "simple" as keeping
|
||||
the current available free space on each filesystem is not as easy as
|
||||
one might think given reflinks, snapshots, and other block level dedup
|
||||
technologies distort the meaning of usage values.
|
||||
|
||||
Even if all metadata (including xattrs) is cached some software will
|
||||
open files (media like videos and audio) to check their
|
||||
metadata. Granted a Plex or Jellyfin scan which may do that is
|
||||
different from a random directory listing but is still something to
|
||||
consider. Those "deep" scans can't be kept from waking drives.
|
||||
Only if ALL metadata and all file locations were cached could mergerfs
|
||||
then do very targeted file operations so as not to trigger a scan
|
||||
across the pool. But that will break out of band interactions. It
|
||||
would require accepting possibly bad behavior by space based policies
|
||||
since space would be imprecisely computed. It would require long scans
|
||||
of the filesystems on startup and a separate location to store all
|
||||
this data.
|
||||
|
||||
|
||||
### What if you only query already active drives?
|
||||
@@ -56,7 +68,8 @@ Let's assume that is plausible (it isn't because some drives actually
|
||||
will spin up if you ask if they are spun down... yes... really) you
|
||||
would have to either cache all the metadata on the filesystem or treat
|
||||
it like the filesystem doesn't exist. The former has all the problems
|
||||
mentioned prior and the latter would break a lot of things.
|
||||
mentioned prior and the latter would break a lot of things. Suddenly
|
||||
whole filesystems worth of data would be missing because it spun down.
|
||||
|
||||
|
||||
### Is there anything that can be done where mergerfs is involved?
|
||||
@@ -65,15 +78,77 @@ Yes, but whether it works for you depends on your tolerance for the
|
||||
complexity.
|
||||
|
||||
1. Cleanly separate writing, storing, and consuming the data.
|
||||
1. Use a SSD or dedicated and limited pool of drives for downloads / torrents.
|
||||
2. When downloaded move the files to the primary storage pool.
|
||||
3. When setting up software like Plex, Jellyfin, etc. point to the
|
||||
1. Use a SSD or dedicated and limited pool of drives for downloads / torrents.
|
||||
2. When downloaded move the files to the primary storage pool.
|
||||
3. When setting up software like Plex, Jellyfin, etc. point to the
|
||||
underlying filesystems. Not mergerfs.
|
||||
2. Add a bunch of bcache, lvmcache, dm-cache, or similar block level
|
||||
cache to your setup. After a bit of use, assuming sufficient
|
||||
storage space, you can limit the likelihood of the underlying
|
||||
spinning disks from needing to be hit.
|
||||
3. Tweak your system's settings around caching:
|
||||
* mount all your filesystems with `noatime` to disable updating
|
||||
"last accessed" time on files.
|
||||
* mount all your filesystems with `nodiratime` to disable updating
|
||||
"last accessed" time on directories.
|
||||
* `sudo sysctl -w vm.vfs_cache_pressure=10`: Prioritizes keeping
|
||||
file metadata cached in RAM.
|
||||
* `sudo sysctl -w vm.dirty_ratio=40`: Allows up to 40% of system
|
||||
memory to be filled with unwritten "dirty" data before forcing a
|
||||
disk flush.
|
||||
* `sudo sysctl -w vm.dirty_background_ratio=5`: Sets a lower
|
||||
threshold (5%) for the kernel to start writing data in the
|
||||
background without blocking applications.
|
||||
* `sudo sysctl -w vm.dirty_writeback_centisecs=6000`: Increases
|
||||
the interval between kernel writeback checks to 60 seconds
|
||||
(default is usually 5).
|
||||
* `sudo sysctl -w vm.dirty_expire_centisecs=12000`: Allows data to
|
||||
stay "dirty" in RAM for up to 120 seconds before it must be
|
||||
written to disk.
|
||||
* To make `sysctl` values permanent add the above to
|
||||
`/etc/sysctl.conf`.
|
||||
* **Note:** for systems with limited RAM the above `sysctl` values
|
||||
could cause issues.
|
||||
* **Note:** if you increase the time for which data is stored in
|
||||
RAM before being flushed to disk there is also an increased risk
|
||||
that a sudden powerloss could lead to lost data.
|
||||
4. Enable all the appropriate caches in mergerfs:
|
||||
* [https://trapexit.github.io/mergerfs/latest/config/cache/](https://trapexit.github.io/mergerfs/latest/config/cache/)
|
||||
* `cache.entry=86400`: cache file existence for 24h.
|
||||
* `cache.negative-entry=86400`: cache file non-existence for 24h.
|
||||
* `cache.attr=86400`: cache file metadata for 24h.
|
||||
* `cache.symlinks=true`: enable symlink caching.
|
||||
* `cache.readdir=true`: enable directory listing caching.
|
||||
|
||||
Remember too that while it may be a tradeoff you are willing to live
|
||||
with there is decent evidence that spinning down drives puts increased
|
||||
wear on them and can lead to their death earlier than otherwise.
|
||||
|
||||
### References
|
||||
|
||||
* [Documentation for /proc/sys/vm/](https://docs.kernel.org/admin-guide/sysctl/vm.html)
|
||||
* [How to Configure vm.swappiness and Other Virtual Memory Parameters on RHEL](https://oneuptime.com/blog/post/2026-03-04-configure-vm-swappiness-virtual-memory-rhel-9/view)
|
||||
|
||||
|
||||
### Tradeoffs
|
||||
|
||||
While it may be a trade off you are willing to live with there is
|
||||
decent evidence that spinning down drives puts material increased wear
|
||||
on them and can lead to their death earlier than otherwise. You may
|
||||
end up saving money on electricity but spending more on having to
|
||||
purchase new drives sooner.
|
||||
|
||||
|
||||
## Alternatives
|
||||
|
||||
[Project Comparisons](../project_comparisons.md) lists a few projects
|
||||
that are similar to mergerfs but offer caching. Please note that these
|
||||
projects are very young and not general purpose like mergerfs. Their
|
||||
compliance to POSIX filesystem rules is probably low enough that
|
||||
certain behaviors may not work or worse lead to data corruption.
|
||||
|
||||
I'm not suggesting not to look at those projects but filesystem
|
||||
development is complicated even when it appears to be just a layer on
|
||||
top of another. Just be careful and do your research. That stands for
|
||||
anything touching your data... mergerfs included.
|
||||
|
||||
To the degree that mergerfs can add features which help in this
|
||||
situation, so long as it doesn't interfere with other fundamental
|
||||
features, they may be added over time.
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Recommendations and Warnings
|
||||
|
||||
## What should mergerfs NOT be used for?
|
||||
|
||||
* databases: Even if the database stored data in separate files
|
||||
(mergerfs wouldn't offer much otherwise) the higher latency of the
|
||||
indirection will really harm performance. If it is a lightly used
|
||||
sqlite3 database then it should be fine.
|
||||
* VM images: For the same reasons as databases. VM images are accessed
|
||||
very aggressively and mergerfs will introduce a lot of extra latency.
|
||||
* As replacement for RAID: mergerfs is just for pooling branches. If
|
||||
you need that kind of device performance aggregation or high
|
||||
availability you should stick with RAID. However, it is fine to put
|
||||
a filesystem which is on a RAID setup in mergerfs.
|
||||
|
||||
**However, if using [passthrough](../config/passthrough.md) the above
|
||||
situations are less likely to be a concern. Best to do testing for
|
||||
your specific use case.**
|
||||
|
||||
|
||||
## It's mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them?
|
||||
|
||||
[mhddfs](https://github.com/trapexit/mhddfs) manages running as
|
||||
`root` by calling
|
||||
[getuid()](https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319)
|
||||
and if it returns `0` then it will
|
||||
[chown](http://linux.die.net/man/1/chown) the file. Not only is that a
|
||||
race condition but it doesn't handle other situations. Rather than
|
||||
attempting to simulate POSIX ACL behavior the proper way to manage
|
||||
this is to use [seteuid](http://linux.die.net/man/2/seteuid) and
|
||||
[setegid](http://linux.die.net/man/2/setegid), in effect, becoming the
|
||||
user making the original call, and perform the action as them. This is
|
||||
what mergerfs does and why mergerfs should always run as root.
|
||||
|
||||
In Linux setreuid syscalls apply only to the thread. glibc hides this
|
||||
away by using realtime signals to inform all threads to change
|
||||
credentials. Taking after Samba, mergerfs uses
|
||||
`syscall(SYS_setreuid,...)` to set the callers credentials for that
|
||||
thread only. Jumping back to `root` as necessary should escalated
|
||||
privileges be needed (for instance: to clone paths between
|
||||
filesystems).
|
||||
|
||||
For non-Linux systems, mergerfs uses a read-write lock and changes
|
||||
credentials only when necessary. If multiple threads are to be user X
|
||||
then only the first one will need to change the processes
|
||||
credentials. So long as the other threads need to be user X they will
|
||||
take a readlock allowing multiple threads to share the
|
||||
credentials. Once a request comes in to run as user Y that thread will
|
||||
attempt a write lock and change to Y's credentials when it can. If the
|
||||
ability to give writers priority is supported then that flag will be
|
||||
used so threads trying to change credentials don't starve. This isn't
|
||||
the best solution but should work reasonably well assuming there are
|
||||
few users.
|
||||
@@ -1,26 +1,76 @@
|
||||
# Technical Behavior and Limitations
|
||||
|
||||
## Do hardlinks work?
|
||||
## Do hard links work?
|
||||
|
||||
Yes. There is no option to enable or disable links (or renames.) They
|
||||
are fundamentally supported in compatible situations. That said the
|
||||
inode of a file is not necessarily indicative of two file names
|
||||
linking to the same underlying data. See also the option `inodecalc`
|
||||
for how inode values are calculated.
|
||||
Yes. [links](https://en.wikipedia.org/wiki/Hard_link) (otherwise known
|
||||
as hard links) are fundamentally supported by mergerfs. There is no
|
||||
option to enable or disable links. All comments elsewhere claiming they
|
||||
do not work are related to the setup of the user's system.
|
||||
|
||||
What mergerfs does not do is fake hard links across branches. Read the
|
||||
section [rename & link](../config/rename_and_link.md) for how it
|
||||
works.
|
||||
See the [Intro to Filesystems](../intro_to_filesystems.md) page for more
|
||||
details on what a `link` is.
|
||||
|
||||
Remember that hardlinks will **NOT** work across devices. That
|
||||
includes between the original filesystem and a mergerfs pool, between
|
||||
two separate pools of the same underlying filesystems, or bind mounts
|
||||
of paths within the mergerfs pool. The latter is common when using
|
||||
Docker or Podman. Multiple volumes (bind mounts) to the same
|
||||
underlying filesystem are considered different devices. There is no
|
||||
way to link or rename between them. You should mount in the highest
|
||||
directory in the mergerfs pool that includes all the paths you need if
|
||||
you want links and rename to work.
|
||||
A limitation of a `link` is that it **must** reference a file/inode
|
||||
within the same filesystem or device. When a request is made to the
|
||||
operating system to `link` (or `rename`) a file to a target location
|
||||
the OS will first check if the source and target are the same
|
||||
filesystem / device. If not it will return the error `EXDEV` meaning
|
||||
that the request is not able to be made due to the result being "cross
|
||||
device".
|
||||
|
||||
What catches many users off guard is that this "cross device" check is
|
||||
not very deep or involved. Even the same filesystem bind mounted to
|
||||
another location will typically be considered a different device. So
|
||||
when mergerfs is creating a union of other filesystems there is no way
|
||||
to link or rename from one of the underlying branches into
|
||||
mergerfs. They are different filesystems even if logically they seem
|
||||
related or the same. And similarly it is impossible for mergerfs to
|
||||
truly make a link between two branches which are not in fact the same
|
||||
mounted filesystem.
|
||||
|
||||
This is relevant to mergerfs users because bind mounts are very common
|
||||
when using container runtimes such as Docker, Podman, or
|
||||
Kubernetes. When you add a volume or mount to your container that is
|
||||
in fact a bind mount of that path. Meaning if you were to add
|
||||
`/mnt/mergerfs/downloads` and `/mnt/mergerfs/movies` as separate
|
||||
mounts into your container any request to link between the two
|
||||
locations within the container will fail with the `EXDEV` error even
|
||||
though it will work perfectly fine on the host. If you wish for `link`
|
||||
and `rename` to work you **MUST** mount into the container a point in
|
||||
the directory structure above what you wish to use. In this case
|
||||
`/mnt/mergerfs`.
|
||||
|
||||
Another situation where people will hit unexpected `EXDEV` errors is
|
||||
when they use a policy which makes file creation decisions based on
|
||||
existing paths. If the source of a `link` or `rename` is on branch A
|
||||
but the target directory is on branch B, given the whole point of the
|
||||
policy is to strictly enforce behavior based on what branches already
|
||||
exist, it must return `EXDEV`. Doing otherwise would fundamentally
|
||||
break the purpose of the policy. Read [rename &
|
||||
link](../config/rename_and_link.md) for more details on how mergerfs
|
||||
handles those two functions.
|
||||
|
||||
When in doubt it is trivial to test `link`:
|
||||
|
||||
```shell
|
||||
$ cd /mnt/mergerfs/
|
||||
$ touch foo
|
||||
$ ln -v foo bar
|
||||
'bar' => 'foo'
|
||||
$ stat foo bar | grep Inode
|
||||
Device: 0,173 Inode: 123456 Links: 2
|
||||
Device: 0,173 Inode: 123456 Links: 2
|
||||
```
|
||||
|
||||
For the more technical: within mergerfs the inode of a file is not
|
||||
necessarily indicative of two file names linking to the same
|
||||
underlying data. For more details as to why that is see the docs on
|
||||
[inodecalc](../config/inodecalc.md).
|
||||
|
||||
|
||||
## Are there any size limitations to files, directories, etc.?
|
||||
|
||||
Whatever the limits are of the underlying filesystems.
|
||||
|
||||
|
||||
## Do reflink, FICLONE, or FICLONERANGE work?
|
||||
@@ -101,7 +151,7 @@ You can remove the reserve by running: `tune2fs -m 0 <device>`
|
||||
|
||||
## I notice massive slowdowns of writes when enabling cache.files.
|
||||
|
||||
When file caching is enabled in any form (`cache.files!=off`) it will
|
||||
When file caching is enabled in any form (`cache.files!=off`) it may
|
||||
issue `getxattr` requests for `security.capability` prior to _every
|
||||
single write_. This will usually result in performance degradation,
|
||||
especially when using a network filesystem (such as NFS or SMB.)
|
||||
@@ -133,20 +183,21 @@ disadvantages to each one.
|
||||
|
||||
A FUSE based solution has all the downsides of FUSE:
|
||||
|
||||
- Higher IO latency due to the trips in and out of kernel space
|
||||
- Higher general overhead due to trips in and out of kernel space
|
||||
- Double caching when using page caching
|
||||
- Misc limitations due to FUSE's design
|
||||
* Higher IO latency due to the trips in and out of kernel space
|
||||
(though now minimized with passthrough IO)
|
||||
* Higher general overhead due to trips in and out of kernel space
|
||||
* Double caching when using page caching
|
||||
* Misc limitations due to FUSE's design
|
||||
|
||||
But FUSE also has a lot of upsides:
|
||||
|
||||
- Easier to offer a cross platform solution
|
||||
- Easier forward and backward compatibility
|
||||
- Easier updates for users
|
||||
- Easier and faster release cadence
|
||||
- Allows more flexibility in design and features
|
||||
- Overall easier to write, secure, and maintain
|
||||
- Much lower barrier to entry (getting code into the kernel takes a
|
||||
* Easier to offer a cross platform solution
|
||||
* Easier forward and backward compatibility
|
||||
* Easier updates for users
|
||||
* Easier and faster release cadence
|
||||
* Allows more flexibility in design and features
|
||||
* Overall easier to write, secure, and maintain
|
||||
* Much lower barrier to entry (getting code into the kernel takes a
|
||||
lot of time and effort initially)
|
||||
|
||||
|
||||
@@ -175,29 +226,81 @@ removed to simplify the codebase.
|
||||
|
||||
## How does mergerfs handle credentials?
|
||||
|
||||
|
||||
mergerfs is a multithreaded application in order to handle requests
|
||||
from the kernel concurrently. Each FUSE message has a header with
|
||||
certain details about the request including the process ID (pid) of
|
||||
the requesting application, the process' effective user id (uid), and
|
||||
group id (gid). To ensure proper POSIX filesystem behavior and
|
||||
security mergerfs must change its identity to match that of the
|
||||
requester when performing the certain functions on the underlying
|
||||
filesystem. As required by standards most Unix/POSIX based systems a
|
||||
process and all its threads are under the same uid and gid. However,
|
||||
on Linux each thread **may** have its own credentials. This allows
|
||||
mergerfs to be multithreaded and for each thread to change to the
|
||||
credentials as required by the incoming message it is
|
||||
handling. However, currently on FreeBSD this is not possible (though
|
||||
there has been
|
||||
[discussions](https://wiki.freebsd.org/Per-Thread%20Credentials)) and
|
||||
as such must change the credentials of the whole application when
|
||||
actioning messages. mergerfs does optimize this behavior by only
|
||||
changing credentials and locking the thread to do so if the process is
|
||||
currently not the same as what is necessary by the incoming
|
||||
request. As a result of this design FreeBSD may experience more
|
||||
contention and therefore lower performance than Linux.
|
||||
group id (gid).
|
||||
|
||||
Additionally, mergerfs [utilizes a cache for supplemental
|
||||
groups](../known_issues_bugs.md#supplemental-user-groups) due the the
|
||||
high cost of querying that information.
|
||||
FUSE and the kernel have two ways of managing permissions. A kernel
|
||||
side `default_permissions` option and leaving it to the FUSE
|
||||
server. When default permissions is enabled the kernel will do the
|
||||
entitlement checks and only allow requests through which should be
|
||||
allowed according to normal POSIX permissions. When not enabled it is
|
||||
the responsibility of the FUSE server, in this case mergerfs, to do
|
||||
whatever is necessary to manage entitlements.
|
||||
|
||||
Prior to mergerfs v2.42.0 it would enable `default_permissions` but
|
||||
also leveraged the uid and gid available in each FUSE request. The
|
||||
thread actioning the request would change its credentials to match
|
||||
those of the requesting application to ensure permissions were
|
||||
properly handled as well as dealing with some quirks of non-POSIX
|
||||
compatible filesystems. However, this strategy had two major
|
||||
issues. First, it was not compatible with the new `allow-idmap`
|
||||
feature of FUSE which allows a filesystem to advertise it can be used
|
||||
with id mapping which is often used with containers. Secondly, it
|
||||
caused permission issues when the kernel would say something was
|
||||
allowed but due to the way mergerfs was changing creds would
|
||||
fail. This mostly came in the form of `chroot`ed setups like used in
|
||||
containers. Neither of these tended to impact casual users but did
|
||||
break some niche or power user use cases. Since the casual user won't
|
||||
notice the subtle changes and it would enable new use cases it was
|
||||
decided that the credential handling would change.
|
||||
|
||||
As of v2.42.0 mergerfs now runs as root more generally. Either
|
||||
changing credentials when creating files (Linux) or chown'ing them
|
||||
after creation (FreeBSD).
|
||||
|
||||
As a result of this change it is now necessary for the FUSE
|
||||
`default_permissions` feature be used for proper entitlements
|
||||
management. mergerfs does allow it to be disabled but it should only
|
||||
be done so for debugging purposes.
|
||||
|
||||
|
||||
## Does mergerfs support idmap?
|
||||
|
||||
Yes, by setting [allow-idmap=true](../config/options.md) (which is the
|
||||
default.)
|
||||
|
||||
Requires that
|
||||
[kernel-permissions-check](../config/kernel-permissions-check.md) be
|
||||
enabled (the default.)
|
||||
|
||||
|
||||
## What happens if a branch filesystem blocks?
|
||||
|
||||
POSIX filesystem calls unfortunately are entirely synchronous. If the
|
||||
underlying filesystem mergerfs is interacting with freezes up or
|
||||
blocks then the thread issuing the request will block same as any
|
||||
other piece of software. If enough threads block then mergerfs will
|
||||
block. There are no timeouts or ways to truly work around this
|
||||
situation. If mergerfs spawned new threads it is very likely they too
|
||||
would end up blocked.
|
||||
|
||||
|
||||
## What information does mergerfs have when making decisions?
|
||||
|
||||
Every incoming request contains:
|
||||
|
||||
* The `tid` or `pid` of the calling thread/process
|
||||
* The `uid` and `gid` of the calling thread/process
|
||||
* The `umask` of the calling thread/process
|
||||
* The arguments of the filesystem call in question
|
||||
|
||||
Naturally the mergerfs config as well as anything queriable from the
|
||||
operating system or filesystems are also available.
|
||||
|
||||
|
||||
## Why does running `mount -a` result in new instances of mergerfs?
|
||||
|
||||
See [lazy-umount-mountpoint](../config/lazy-umount-mountpoint.md)
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Usage and Functionality
|
||||
|
||||
## What should mergerfs NOT be used for?
|
||||
|
||||
* Situations where you need large amounts of contiguous space beyond
|
||||
that available on any singular device. Such as putting 10GiB a file
|
||||
on 2 6GiB filesystems.
|
||||
* databases: Even if the database stored data in separate files
|
||||
(mergerfs wouldn't offer much otherwise) the higher latency of the
|
||||
indirection will harm performance. Though if it is a sqlite3
|
||||
database then its likely fine.
|
||||
* VM images: For the same reasons as databases. VM images are accessed
|
||||
very aggressively and mergerfs will introduce a lot of extra latency.
|
||||
* As replacement for RAID: mergerfs is just for pooling branches. If
|
||||
you need device performance aggregation or high availability you
|
||||
should stick with RAID. However, it is fine to put a filesystem
|
||||
which is on a RAID setup in mergerfs.
|
||||
|
||||
**However, if using [passthrough](../config/passthrough.md) the
|
||||
performance related issues above are less likely to be a concern. Best
|
||||
to do testing for your specific use case.**
|
||||
|
||||
|
||||
## What happens when file paths overlap?
|
||||
|
||||
It depends on the situation and the configuration of mergerfs. The
|
||||
@@ -9,13 +30,19 @@ filesystem function is requested. Which file to select or act on.
|
||||
See how [policies](../config/functions_categories_policies.md) work.
|
||||
|
||||
|
||||
## Is adding or removing mergerfs to/from my setup destructive?
|
||||
|
||||
No. [See
|
||||
below.](#can-mergerfs-be-used-with-filesystems-which-already-have-data)
|
||||
|
||||
|
||||
## Can mergerfs be used with filesystems which already have data?
|
||||
|
||||
Yes. mergerfs is really just a proxy and does **NOT** interfere with
|
||||
the normal form or function of the filesystems, mounts, or paths it
|
||||
manages. It literally is interacting with your filesystems as any
|
||||
other application does. It can not do anything that any other random
|
||||
piece of software can't do.
|
||||
piece of software can't do. There is no migration needed.
|
||||
|
||||
mergerfs is **not** a traditional filesystem that takes control over
|
||||
the underlying drive, block device, or partition. mergerfs is **not**
|
||||
@@ -23,6 +50,9 @@ RAID. It does **not** manipulate the data that passes through it. It
|
||||
does **not** shard data across filesystems. It only shards some
|
||||
**behavior** and aggregates others.
|
||||
|
||||
Think of it as a filesystem [reverse
|
||||
proxy](https://en.wikipedia.org/wiki/Reverse_proxy).
|
||||
|
||||
|
||||
## Can mergerfs be removed without affecting the data?
|
||||
|
||||
@@ -105,9 +135,33 @@ normal conditions.
|
||||
|
||||
Keep in mind that if out-of-band changes are made while also
|
||||
leveraging any caching of the filesystem layout such as with
|
||||
`cache.entry`, `cache.negative_entry`, `cache.attr`, `cache.symlinks`,
|
||||
`cache.entry`, `cache.negative-entry`, `cache.attr`, `cache.symlinks`,
|
||||
or `cache.readdir` you may experience temporary inconsistency till the
|
||||
cache expires. mergerfs is not actively watching all branches for
|
||||
changes and the kernel will have no way to know anything changed so as
|
||||
to clear or ignore the cache. This is the same issue you can have with
|
||||
[remote filesystems](../remote_filesystems.md).
|
||||
|
||||
|
||||
## What are the performance characteristics of using mergerfs?
|
||||
|
||||
mergerfs does not stripe or shard data. Instead it acts like a reverse
|
||||
proxy / multiplexer. As such it is not possible to have RAID like
|
||||
performance improvements. However, its performance is not limited like
|
||||
[block level concatenation / JBOD /
|
||||
SPAN.](../project_comparisons.md#raid0-jbod-span-drive-concatenation-striping)
|
||||
|
||||
* Single file / single stream: Max throughput limited to speed of the
|
||||
chosen underlying branch + FUSE overhead (usually 5–30% slower than
|
||||
direct access; near native with [IO
|
||||
passthrough](../config/passthrough.md)).
|
||||
* Aggregate / multi-stream: Can approach sum of all branches' speeds
|
||||
when many independent IO operations run in parallel (multiple files,
|
||||
processes, or clients).
|
||||
* Other factors: Latency-sensitive workloads suffer more (slow
|
||||
branches or network drives hurt responsiveness); directory listing
|
||||
and metadata ops slower due to combining branches.
|
||||
|
||||
Real-world performance varies heavily with workload, kernel, options,
|
||||
and hardware. See [benchmarking](../benchmarking.md) and
|
||||
[performance](../performance.md) section for testing tips.
|
||||
|
||||
@@ -36,15 +36,15 @@ and [FAQ](configuration_and_policies.md) for policy recommendations.
|
||||
|
||||
## Why do I get an "out of space" / "no space left on device" / ENOSPC error even though there appears to be lots of space available?
|
||||
|
||||
One or multiple of the below are likely causing branches
|
||||
One or multiple of the below are likely causing branches to be
|
||||
[filtered out](../config/functions_categories_policies.md#filtering).
|
||||
|
||||
* [minfreespace](../config/minfreespace.md) could be filtering out
|
||||
branches
|
||||
* if using an `ep*`
|
||||
[policy](../config/functions_categories_policies.md) there may not
|
||||
be an available branch
|
||||
* due to [branch modes](../config/branches.md#branch-mode) being set
|
||||
to to `NC` or `RO` branches will be excluded from consideration
|
||||
* using an `ep*` [policy](../config/functions_categories_policies.md)
|
||||
and there may not be an available branch
|
||||
* [branch modes](../config/branches.md#branch-mode) set to `NC` or
|
||||
`RO` branches will be excluded from consideration
|
||||
* if using `ext4` ensure you are not running into reserve limits (use
|
||||
`tune2fs -m 0` to remove the reserve)
|
||||
* it is possible that the filesystem selected by the policy has run
|
||||
@@ -56,7 +56,7 @@ within the pool (behavior modified by [statfs](../config/statfs.md)
|
||||
and [statfs_ignore](../config/statfs.md#statfs_ignore)). It does not
|
||||
represent a contiguous space. In the same way that read-only
|
||||
filesystems, those with quotas, or reserved space report the full
|
||||
theoretical space available. Not the practical usable space.
|
||||
theoretical space available but not the practical usable space.
|
||||
|
||||
|
||||
## Why isn't the create policy working?
|
||||
@@ -81,7 +81,7 @@ where it was created.
|
||||
$ sudo touch /mnt/mergerfs/new-file
|
||||
$ sudo getfattr -n user.mergerfs.allpaths /mnt/mergerfs/new-file
|
||||
# file: mnt/mergerfs/new-file
|
||||
user.mergerfs.allpaths="/mnt/hdd/drive1/new-file"
|
||||
user.mergerfs.allpaths="/mnt/hdd/disk0/new-file"
|
||||
```
|
||||
|
||||
If the location of the file is where it should be according to the
|
||||
@@ -119,7 +119,7 @@ may be able to read some files and directories on one filesystem but
|
||||
not another resulting in an incomplete set. And if one of the branches
|
||||
it can access is empty then it will return an empty list.
|
||||
|
||||
Try using [mergerfs.fsck](https://github.com/trapexit/mergerfs-tools)
|
||||
Try using [fsck.mergerfs](../tooling.md#fsckmergerfs)
|
||||
tool to check for and fix inconsistencies in permissions. If you
|
||||
aren't seeing anything at all be sure that the basic permissions are
|
||||
correct. The user and group values are correct and that directories
|
||||
@@ -134,7 +134,7 @@ the use of mergerfs from [container platforms such as
|
||||
Docker.](compatibility_and_integration.md)
|
||||
|
||||
|
||||
## Why are file moves, renames, links/hardlinks failing?
|
||||
## Why are file moves, renames, links/hard links failing?
|
||||
|
||||
mergerfs fully supports rename and link functions to the degree that
|
||||
is possible in a union filesystem. Problems related to rename and link
|
||||
@@ -225,18 +225,34 @@ the software's author and request proper handling of `EXDEV` errors.
|
||||
### Additional Reading
|
||||
|
||||
* [rename and link](../config/rename_and_link.md)
|
||||
* [Do hardlinks work?](technical_behavior_and_limitations.md#do-hardlinks-work)
|
||||
* [Do hard links work?](technical_behavior_and_limitations.md#do-hard-links-work)
|
||||
* [How does mergerfs handle moving and copying of files?](technical_behavior_and_limitations.md#how-does-mergerfs-handle-moving-and-copying-of-files)
|
||||
* [Moving files and directories fails with Samba](../known_issues_bugs.md#moving-files-and-directories-fails-with-samba)
|
||||
|
||||
|
||||
## Why is rtorrent failing with ENODEV (No such device)?
|
||||
## Why is software needing mmap failing?
|
||||
|
||||
Be sure to set
|
||||
[cache.files=partial|full|auto-full|per-process](../config/cache.md)
|
||||
or use Linux kernel v6.6 or above. rtorrent and some other
|
||||
applications use [mmap](http://linux.die.net/man/2/mmap) to read and
|
||||
write to files and offer no fallback to traditional methods.
|
||||
See [Known Issues and Bugs#3rd Party
|
||||
Software](../known_issues_bugs.md#3rd-party-software) and
|
||||
[QuickStart](../quickstart.md).
|
||||
|
||||
|
||||
## Why don't quick scans fail to pick up new media?
|
||||
|
||||
Media consumption software like Plex, Jellyfin, Emby, Airsonic,
|
||||
Navidrome, etc. will use timestamps of directories to see if it should
|
||||
be scanned. Be sure to set `func.getattr=newest` to catch that.
|
||||
|
||||
A full scan would still pick up all media and for those concerned
|
||||
about device activity it is recommened to disable quick scans and
|
||||
leave periodic full scans so the activity is managed. Or to configure
|
||||
your software which manages downloads (such as *Arr family of
|
||||
software) to trigger specific scans when files are added to the pool.
|
||||
|
||||
Additional reading:
|
||||
|
||||
* [Does inotify and fanotify
|
||||
work?](compatibility_and_integration.md#does-inotify-and-fanotify-work)
|
||||
|
||||
|
||||
## Why don't I see mergerfs options in mount command or /proc/mounts?
|
||||
|
||||
+78
-19
@@ -1,30 +1,65 @@
|
||||
# mergerfs - a featureful union filesystem
|
||||
|
||||
**mergerfs** is a
|
||||
[FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace) based
|
||||
[union filesystem](https://en.wikipedia.org/wiki/Union_mount) geared
|
||||
towards simplifying storage and management of files across numerous
|
||||
commodity storage devices. It is similar to [**mhddfs**, **unionfs**,
|
||||
**aufs**, **DrivePool**, etc.](project_comparisons.md)
|
||||
**mergerfs** is a [union
|
||||
filesystem](https://en.wikipedia.org/wiki/Union_mount) that makes
|
||||
multiple filesystems and/or directories appear as a single unified
|
||||
directory. mergerfs is designed to simplify how you manage files
|
||||
across multiple independent filesystems without the complexity,
|
||||
fragility, or cost of RAID or similar storage aggregation
|
||||
technologies.
|
||||
|
||||
mergerfs as a smart pooling layer allows you to combine any number of
|
||||
[existing
|
||||
filesystems](faq/usage_and_functionality.md#can-mergerfs-be-used-with-filesystems-which-already-have-data);
|
||||
whether they are on hard drives, SSDs, network shares, or other
|
||||
mounted storage; into what looks like one large filesystem, while
|
||||
still maintaining direct access to each individual filesystem. Unlike
|
||||
RAID, there's no rebuild process if a device fails. You only lose
|
||||
those files that were on that specific filesystem. You can also add or
|
||||
remove filesystems at any time without restructuring the pool.
|
||||
|
||||
mergerfs excels at cost-effective storage expansion, making it ideal
|
||||
for media libraries, backups, archival data, and other write-sometimes,
|
||||
read-often workloads where you need lots of space but don't want
|
||||
the overhead of traditional storage aggregation technologies.
|
||||
|
||||
**Key advantages:**
|
||||
|
||||
* [Non-destructive](faq/usage_and_functionality.md#is-adding-or-removing-mergerfs-tofrom-my-setup-destructive) adding and removing of filesystems (even at runtime)
|
||||
* Mix and match filesystems of any [size](faq/technical_behavior_and_limitations.md#are-there-any-size-limitations-to-files-directories-etc),
|
||||
[type](faq/compatibility_and_integration.md#what-filesystems-can-be-used-as-branches),
|
||||
or [underlying
|
||||
device](faq/compatibility_and_integration.md#what-types-of-storage-devices-does-mergerfs-work-with)
|
||||
* No parity calculations or rebuild times
|
||||
* Does not require hard drives to be spinning if not in use
|
||||
* [Direct access to files](faq/usage_and_functionality.md#can-filesystems-still-be-used-directly-outside-of-mergerfs-while-pooled) on individual filesystems when needed
|
||||
* Flexible [policies](config/functions_categories_policies.md) for controlling where new files are created
|
||||
|
||||
For users seeking alternatives to mhddfs, unionfs, aufs, or DrivePool,
|
||||
mergerfs offers a mature, actively maintained solution with extensive
|
||||
configuration options and documentation. See the [project comparisons
|
||||
for more details.](project_comparisons.md)
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Logically combine numerous filesystems/paths into a single
|
||||
mount point (JBOFS: Just a Bunch of FileSystems)
|
||||
mount point (JBOFS: Just a Bunch of FileSystems, think filesystem
|
||||
[reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy))
|
||||
* Combine paths of the same or different filesystems
|
||||
* Ability to add or remove filesystems/paths without impacting the
|
||||
rest of the data
|
||||
* Unaffected by individual filesystem failure
|
||||
* Configurable file selection and creation placement
|
||||
* Configurable file creation placement
|
||||
* File IO [passthrough](config/passthrough.md) for near native IO
|
||||
performance (where supported)
|
||||
* Works with filesystems of any size
|
||||
* Works with filesystems of [almost any
|
||||
type](faq/compatibility_and_integration.md#what-filesystems-can-be-used-as-branches)
|
||||
* Ignore read-only filesystems when creating files
|
||||
* Hard link [copy-on-write / CoW](config/link_cow.md)
|
||||
* [Runtime configurable](runtime_interface.md)
|
||||
* [Hard links](faq/technical_behavior_and_limitations.md#do-hard-links-work)
|
||||
* Hard link [copy-on-write / CoW](config/link-cow.md)
|
||||
* [Runtime configurability](runtime_interface.md)
|
||||
* Support for extended attributes (xattrs)
|
||||
* Support for file attributes (chattr)
|
||||
* Support for POSIX ACLs
|
||||
@@ -32,18 +67,23 @@ commodity storage devices. It is similar to [**mhddfs**, **unionfs**,
|
||||
|
||||
## Non-features
|
||||
|
||||
* Read/write overlay on top of read-only filesystem like OverlayFS
|
||||
* RAID like redundancy (see [SnapRAID](https://www.snapraid.it) and
|
||||
[NonRAID](https://github.com/qvr/nonraid))
|
||||
* RAID like performance improvements
|
||||
* LVM/RAID style block device aggregation
|
||||
* Data integrity checks, snapshots, file versioning
|
||||
* Read/write overlays on top of read-only filesystems (like OverlayFS)
|
||||
* File whiteout
|
||||
* RAID like parity calculation (see [SnapRAID](https://www.snapraid.it))
|
||||
* Redundancy
|
||||
* Splitting of files across branches
|
||||
* Active rebalancing of content
|
||||
* [Actively attempt to keep hard drives spun down](faq/limit_drive_spinup.md)
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
mergerfs logically merges multiple filesystem paths together. Not
|
||||
block devices, not filesystem mounts, just paths. It acts as a proxy
|
||||
to the underlying filesystem paths. Combining the behaviors of some
|
||||
to the underlying filesystem. Combining the behaviors of some
|
||||
functions and being a selector for others.
|
||||
|
||||
When the contents of a directory are requested mergerfs combines the
|
||||
@@ -56,15 +96,16 @@ which branch will be selected for the creation.
|
||||
For functions which change attributes or remove the file the behavior
|
||||
may be applied to all instances found.
|
||||
|
||||
Read more about [policies
|
||||
here](config/functions_categories_policies.md).
|
||||
The way in which mergerfs behaves is controlled by the
|
||||
[config / options / settings](config/options.md). More specifically by
|
||||
[policies](config/functions_categories_policies.md).
|
||||
|
||||
|
||||
### Visualization
|
||||
|
||||
```
|
||||
A + B = C
|
||||
/disk1 /disk2 /merged
|
||||
/mnt/hdd/disk0 /mnt/hdd/disk1 /media
|
||||
| | |
|
||||
+-- /dir1 +-- /dir1 +-- /dir1
|
||||
| | | | | |
|
||||
@@ -74,7 +115,7 @@ A + B = C
|
||||
| | |
|
||||
+-- /dir2 | +-- /dir2
|
||||
| | | | |
|
||||
| *-- file4 | | +-- file4
|
||||
| +-- file4 | | +-- file4
|
||||
| | |
|
||||
| +-- /dir3 +-- /dir3
|
||||
| | | | |
|
||||
@@ -84,7 +125,25 @@ A + B = C
|
||||
+-- file7 +-- file7 +-- file7
|
||||
```
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
Head to the [quick start guide](quickstart.md).
|
||||
|
||||
|
||||
## About This Documentation
|
||||
|
||||
* Like the software the documentation changes over time. Ensure that
|
||||
you are reading the documentation related to the version of the
|
||||
software you are using.
|
||||
* The documentation is explicit, literal, and reasonably thorough. If
|
||||
a suspected feature is not mentioned it does not exist. Do not read
|
||||
into the wording. What is described is how it functions. If you feel
|
||||
like something is not explained sufficiently or missing please [ask
|
||||
in one of the supported forums](support.md#contact-issue-submission)
|
||||
and the docs will be updated.
|
||||
* The search feature of MkDocs is not great. Searching for "literal
|
||||
strings" will generally not work. Alternative solutions are being
|
||||
investigated.
|
||||
* While not intended for end users nor completely accurate some might
|
||||
find the AI generated docs at https://deepwiki.com/trapexit/mergerfs
|
||||
interesting or useful.
|
||||
|
||||
@@ -51,6 +51,8 @@ those needing that knowledge.
|
||||
* [file descriptor](https://en.wikipedia.org/wiki/File_descriptor): A
|
||||
handle used by software, provided by the operating system, to
|
||||
reference open files.
|
||||
* [mmap](https://en.wikipedia.org/wiki/Mmap): A way to abstract access
|
||||
to a file by making it appear as a region of memory.
|
||||
|
||||
|
||||
## Files
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user