docs: clarify branch resolution and mount ordering

This commit is contained in:
Antonio SJ Musumeci
2026-09-21 13:46:38 -05:00
committed by trapexit
parent 515b0549ef
commit cfb925e5ae
10 changed files with 138 additions and 35 deletions
@@ -38,6 +38,32 @@ These files and xattrs are used to help `mergerfs` know whether or not
a directory happens to be a branch or is the underlying directory
mount point.
## Paths which appear after startup
The [`branches` resolution process](branches.md#resolution) runs before
this option starts waiting. A missing literal path is retained as a
branch, so a configuration such as `/mnt/hdd/disk0/foobar` can work
when `/mnt/hdd/disk0` is mounted asynchronously and `foobar` only
becomes visible afterward, provided the path then meets one of the
"mounted" conditions above.
For each branch which is not ready, mergerfs first runs `mount` once
with the full branch path as the target and then polls that same path
until the timeout expires. It does not create the path, try to mount
its parent, or rerun `mount`. In the example above, the initial
`mount /mnt/hdd/disk0/foobar` may fail while the path is absent. If
another service mounts `/mnt/hdd/disk0` and
`/mnt/hdd/disk0/foobar` appears and meets one of the "mounted"
conditions before the deadline, mergerfs detects it during polling
and continues.
Globs are different. If `/mnt/hdd/disk0/*` has no matches during
branch resolution, the literal path `/mnt/hdd/disk0/*` is retained.
The mount wait does not expand it again after `/mnt/hdd/disk0`
becomes available. Ensure the parent filesystem is mounted before
mergerfs, configure a literal branch path, or update `branches`
through the runtime API once the paths exist.
**NOTE:** If on a `systemd` based system and using `fstab` it is a
good idea to set the mount option
+32 -5
View File
@@ -14,7 +14,7 @@ Branches currently have two options which can be set. A
included in a policy calculation and a individual
[minfreespace](#minfreespace) value. The values are set by
prepending an `=` at the end of a branch designation and using commas
as delimiters. Example: `/mnt/drive=RW,1234`
as delimiters. Example: `/mnt/hdd/disk0=RW,1234`
### branch mode
@@ -34,6 +34,33 @@ Same purpose and syntax as the [global option](minfreespace.md) but
specific to the branch. Defaults to the global value.
### resolution
Each colon-delimited branch designation is resolved independently when
the `branches` option is parsed, both at startup and when it is updated
through the runtime API:
1. The path portion is expanded as a glob.
2. If the glob matches one or more directories, each match becomes a
branch.
3. If the glob has no matches, mergerfs logs a notice and keeps the
configured path unchanged as a single, literal branch.
4. mergerfs resolves each resulting path with `realpath`. If that
succeeds, the canonical path is stored. If it fails, typically
because the path does not exist yet, the unchanged path is stored.
5. A path which exists but is not a directory is logged and skipped. A
path which does not exist is logged but retained.
Consequently, a literal branch such as `/mnt/hdd/disk0/foobar` may be
configured before it exists and can become usable after
`/mnt/hdd/disk0` is mounted. Branch resolution is not repeated in the
background, however. If `/mnt/hdd/disk0/*` has no matches when it is
resolved, mergerfs stores the literal path `/mnt/hdd/disk0/*`;
directories which match it later are not discovered automatically.
Update `branches` through the runtime API or restart mergerfs to
resolve the glob again.
### globbing
To make it easier to include multiple branches mergerfs supports
@@ -42,18 +69,18 @@ MUST be escaped when using via the shell else the shell itself will
apply the glob itself.**
```
# mergerfs /mnt/hdd\*:/mnt/ssd /media
# mergerfs /mnt/hdd/\*:/mnt/ssd/\* /media
```
The above line will use all directories in /mnt prefixed with **hdd**
as well as **ssd**.
The above line will use all directories within `/mnt/hdd` and
`/mnt/ssd`.
To have the pool mounted at boot or otherwise accessible from related
tools use `/etc/fstab`.
```
# <file system> <mount point> <type> <options> <dump> <pass>
/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0
/mnt/hdd/*:/mnt/ssd/* /media mergerfs minfreespace=16G 0 0
```
**NOTE:** The globbing is done at mount or when updated using the
+2 -1
View File
@@ -2,7 +2,8 @@
If using path preservation and a `rename` fails with `EXDEV`:
1. Move file from `/branch/a/b/c` to `/branch/.mergerfs_rename_exdev/a/b/c`.
1. Move file from `/mnt/hdd/disk0/a/b/c` to
`/mnt/hdd/disk0/.mergerfs_rename_exdev/a/b/c`.
2. symlink the rename's `newpath` to the moved file.
The `target` value is determined by the value of `rename-exdev`.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -105,7 +105,7 @@ The way in which mergerfs behaves is controlled by the
```
A + B = C
/disk1 /disk2 /merged
/mnt/hdd/disk0 /mnt/hdd/disk1 /media
| | |
+-- /dir1 +-- /dir1 +-- /dir1
| | | | | |
+3 -3
View File
@@ -92,15 +92,15 @@ If you always want the directory information from the one with the
most recent mtime then use the `newest` policy for `getattr`.
### 'mv /mnt/pool/foo /mnt/disk1/foo' removes 'foo'
### 'mv /mnt/pool/foo /mnt/hdd/disk0/foo' removes 'foo'
This is not a bug.
Run in verbose mode to better understand what's happening:
```
$ mv -v /mnt/pool/foo /mnt/disk1/foo
copied '/mnt/pool/foo' -> '/mnt/disk1/foo'
$ mv -v /mnt/pool/foo /mnt/hdd/disk0/foo
copied '/mnt/pool/foo' -> '/mnt/hdd/disk0/foo'
removed '/mnt/pool/foo'
$ ls /mnt/pool/foo
ls: cannot access '/mnt/pool/foo': No such file or directory
+2 -2
View File
@@ -23,9 +23,9 @@ and offers better performance. As of 2020, the author of mhddfs has
Below is an example of mhddfs and mergerfs setup to work similarly.
```
mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool
mhddfs -o mlimit=4G,allow_other /mnt/hdd/disk0,/mnt/hdd/disk1 /mnt/pool
mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool
mergerfs -o minfreespace=4G,category.create=ff /mnt/hdd/disk0:/mnt/hdd/disk1 /mnt/pool
```
+62 -14
View File
@@ -66,24 +66,69 @@ branches](config/branches.md#branch-setup).
```
# Two positional options: `:` colon separated branches and mountpoint
mergerfs -o opt[,opt...] /branch0:/branch1 /mountpoint
mergerfs -o opt[,opt...] /mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media
# or list of positional options where the last is the mountpoint
mergerfs -o opt[,opt...] /branch0 /branch1 /mountpoint
mergerfs -o opt[,opt...] /mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2 /media
# or set mountpoint via -o mountpoint
mergerfs -o mountpoint=/mountpoint /branch0 /branch1
mergerfs -o mountpoint=/media /mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
```
```
mergerfs -o cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false /mnt/hdd0:/mnt/hdd1 /media
mergerfs -o cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false /mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media
```
### /etc/fstab
```
/mnt/hdd0:/mnt/hdd1 /media mergerfs cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false 0 0
/mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media mergerfs cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false 0 0
```
### systemd mount dependencies
On a `systemd` based system, no custom service or setup script is
needed when the branch filesystems and mergerfs are all configured in
`/etc/fstab`. Add
[`x-systemd.requires-mounts-for=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.mount.html#x-systemd.wants-mounts-for=)
once for each branch mount target in the mergerfs entry:
```fstab
LABEL=disk0 /mnt/hdd/disk0 ext4 defaults 0 2
LABEL=disk1 /mnt/hdd/disk1 ext4 defaults 0 2
LABEL=disk2 /mnt/hdd/disk2 ext4 defaults 0 2
/mnt/hdd/disk0:/mnt/hdd/disk1:/mnt/hdd/disk2 /media mergerfs cache.files=off,category.create=pfrd,func.getattr=newest,dropcacheonclose=false,x-systemd.requires-mounts-for=/mnt/hdd/disk0,x-systemd.requires-mounts-for=/mnt/hdd/disk1,x-systemd.requires-mounts-for=/mnt/hdd/disk2 0 0
```
Replace the example labels and filesystem types with those used by the
system. `systemd` creates `Requires=` and `After=` dependencies from
the mergerfs mount to `mnt-hdd-disk0.mount`,
`mnt-hdd-disk1.mount`, and `mnt-hdd-disk2.mount`. It starts those
mounts first and does not start the mergerfs mount if a required mount
fails. This checks the mount units, not merely whether the
`/mnt/hdd/diskN` directories exist.
After editing `/etc/fstab`, reload the generated units:
```shell
sudo systemctl daemon-reload
```
The dependencies apply the next time `/media` is mounted and on
subsequent boots.
When using a service such as the
[`mergerfs-media.service`](#systemd-simple) example below instead of an
`/etc/fstab` mergerfs entry, add the equivalent directive to its
`[Unit]` section:
```systemd
RequiresMountsFor=/mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
```
`RequiresMountsFor=` adds both the requirement and ordering
dependencies, so separate `Requires=` and `After=` entries for these
mounts are unnecessary.
### /etc/fstab w/ config file
For more complex setups it can be useful to separate out the config.
@@ -114,10 +159,9 @@ resolve the symlinks and use the real path.
`ls -lh /etc/mergerfs/branches/media/*`
```text
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd00 -> /mnt/hdd/hdd00
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd01 -> /mnt/hdd/hdd01
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd02 -> /mnt/hdd/hdd02
lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd03 -> /mnt/hdd/hdd03
lrwxrwxrwx 1 root root 14 Aug 4 2023 disk0 -> /mnt/hdd/disk0
lrwxrwxrwx 1 root root 14 Aug 4 2023 disk1 -> /mnt/hdd/disk1
lrwxrwxrwx 1 root root 14 Aug 4 2023 disk2 -> /mnt/hdd/disk2
```
### systemd (simple)
@@ -127,6 +171,7 @@ lrwxrwxrwx 1 root root 21 Aug 4 2023 hdd03 -> /mnt/hdd/hdd03
```systemd title="mergerfs-media.service" linenums="1"
[Unit]
Description=mergerfs /media service
RequiresMountsFor=/mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
After=local-fs.target network.target
[Service]
@@ -138,8 +183,9 @@ ExecStart=/usr/bin/mergerfs \
-o category.create=pfrd \
-o func.getattr=newest \
-o dropcacheonclose=false \
/mnt/hdd0 \
/mnt/hdd1 \
/mnt/hdd/disk0 \
/mnt/hdd/disk1 \
/mnt/hdd/disk2 \
/media
ExecStop=/usr/bin/umount /media
# Or if you need fusermount
@@ -193,8 +239,9 @@ WantedBy=default.target
```systemd title="mergerfs-media.service" linenums="1"
[Unit]
Description=mergerfs /media service
RequiresMountsFor=/mnt/hdd/disk0 /mnt/hdd/disk1 /mnt/hdd/disk2
Requires=setup-for-mergerfs.service
After=local-fs.target network.target prepare-for-mergerfs.service
After=local-fs.target network.target setup-for-mergerfs.service
[Service]
Type=simple
@@ -205,8 +252,9 @@ ExecStart=/usr/bin/mergerfs \
-o category.create=pfrd \
-o func.getattr=newest \
-o dropcacheonclose=false \
/mnt/hdd0 \
/mnt/hdd1 \
/mnt/hdd/disk0 \
/mnt/hdd/disk1 \
/mnt/hdd/disk2 \
/media
ExecStop=/usr/bin/umount /media
# Or if you need fusermount
+5 -5
View File
@@ -110,7 +110,7 @@ situation and continue to be able to access the xattr interface.
```
[trapexit:/mnt/mergerfs] $ getfattr -d .mergerfs
user.mergerfs.branches="/mnt/a=RW:/mnt/b=RW"
user.mergerfs.branches="/mnt/hdd/disk0=RW:/mnt/hdd/disk1=RW"
user.mergerfs.minfreespace="4294967295"
user.mergerfs.moveonenospc="false"
...
@@ -122,9 +122,9 @@ user.mergerfs.category.search="mfs"
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.create .mergerfs
user.mergerfs.category.search="prfd"
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.branches -v '+</mnt/c=RO' .mergerfs
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.branches -v '+</mnt/hdd/disk2=RO' .mergerfs
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.branches .mergerfs
user.mergerfs.branches="/mnt/c=RO:/mnt/a=RW:/mnt/b=RW"
user.mergerfs.branches="/mnt/hdd/disk2=RO:/mnt/hdd/disk0=RW:/mnt/hdd/disk1=RW"
```
### Commands
@@ -172,7 +172,7 @@ to set them will result in an error.
```
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.fullpath test
user.mergerfs.fullpath="/mnt/a/test"
user.mergerfs.fullpath="/mnt/hdd/disk0/test"
[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.allpaths .
user.mergerfs.allpaths="/mnt/a\000/mnt/b\000/mnt/c"
user.mergerfs.allpaths="/mnt/hdd/disk0\000/mnt/hdd/disk1\000/mnt/hdd/disk2"
```
+4 -3
View File
@@ -105,7 +105,8 @@ to `.bashrc`.
### Docker and Podman usage
Assume `/mnt/fs0` and `/mnt/fs1` are pooled with mergerfs at `/media`.
Assume `/mnt/hdd/disk0` and `/mnt/hdd/disk1` are pooled with mergerfs
at `/media`.
All mergerfs branch paths _must_ be bind mounted into the container at
the same path as found on the host so the preload library can see
@@ -134,8 +135,8 @@ docker run \
-e LD_PRELOAD=/usr/lib/mergerfs/preload.so \
-v /usr/lib/mergerfs/preload.so:/usr/lib/mergerfs/preload.so:ro \
-v /media:/media \
-v /mnt/fs0:/mnt/fs0 \
-v /mnt/fs1:/mnt/fs1 \
-v /mnt/hdd/disk0:/mnt/hdd/disk0 \
-v /mnt/hdd/disk1:/mnt/hdd/disk1 \
ubuntu:latest \
bash
```