mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-19 16:16:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10e8bd9fa3 | ||
|
|
5ce428ca59 | ||
|
|
2f769e75c4 | ||
|
|
a646fe016d | ||
|
|
f45a52d621 | ||
|
|
576ff3694e | ||
|
|
4cb866cee9 | ||
|
|
43ddff6ca9 | ||
|
|
bf4b39065b | ||
|
|
4b9f3de814 | ||
|
|
903d39f968 | ||
|
|
e6c8fa3fad | ||
|
|
9952c58a4a | ||
|
|
be8bb5d2a7 | ||
|
|
66f0d9e649 | ||
|
|
7b355b279d | ||
|
|
0f2f78c5b8 | ||
|
|
eb57425315 | ||
|
|
489ab23e89 | ||
|
|
4c55eb0cc0 | ||
|
|
389785265b | ||
|
|
a6717990a8 | ||
|
|
f4b8efce20 | ||
|
|
8cdb7174c4 | ||
|
|
4625a3cf43 | ||
|
|
55245e290a | ||
|
|
322fa5778a | ||
|
|
5eb3c8cbef | ||
|
|
5d827566f6 | ||
|
|
6058ac0716 | ||
|
|
4d82ed9324 | ||
|
|
026dc5f750 | ||
|
|
08e1bef5a9 | ||
|
|
b5f6cd70de | ||
|
|
58ab7f7fa8 | ||
|
|
c46c41c740 | ||
|
|
7a84555db8 | ||
|
|
0b0f0a1b9e | ||
|
|
0fffabfbe3 | ||
|
|
6b3f703ba9 | ||
|
|
50ad648ce4 | ||
|
|
2dbcd2b07a | ||
|
|
752a15953a | ||
|
|
23d365f67b | ||
|
|
f77d1d9929 | ||
|
|
4204c807bc | ||
|
|
e6a6ab967b | ||
|
|
52e4d60e7c | ||
|
|
38919add61 | ||
|
|
a7244184bd | ||
|
|
86bda61aa7 | ||
|
|
10e5829b8e | ||
|
|
4c4c27a93e | ||
|
|
72ac1fc0d1 | ||
|
|
5ca928e436 | ||
|
|
5d013f4a18 | ||
|
|
7cbd88ac81 | ||
|
|
24af276542 | ||
|
|
8cb7195c3e | ||
|
|
c8e178bf71 | ||
|
|
529a953e30 | ||
|
|
02b657edff | ||
|
|
ddf6a2f105 | ||
|
|
40a4762cac | ||
|
|
2323c16316 | ||
|
|
bac4b33ef8 | ||
|
|
1baa706d37 | ||
|
|
9e3331dab4 | ||
|
|
61cded5b34 | ||
|
|
060cae6270 | ||
|
|
229d8517c0 | ||
|
|
cc9381f578 | ||
|
|
5f2221155c | ||
|
|
7da8dd6b7a | ||
|
|
2b019b84b0 | ||
|
|
741c559df1 | ||
|
|
3a66a68edb | ||
|
|
af1c0d78e8 | ||
|
|
80d56ac94e | ||
|
|
41881f9d05 | ||
|
|
1ca70521a4 |
@@ -0,0 +1,7 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [trapexit] # [user]
|
||||
patreon: trapexit
|
||||
open_collective: trapexit
|
||||
ko_fi: trapexit
|
||||
custom: ['https://www.subscribestar.com/trapexit','https://paypal.me/trapexit']
|
||||
+9
-1
@@ -26,9 +26,17 @@ matrix:
|
||||
dist: xenial
|
||||
compiler: clang
|
||||
sudo: required
|
||||
- os: linux
|
||||
dist: bionic
|
||||
compiler: gcc
|
||||
sudo: required
|
||||
- os: linux
|
||||
dist: bionic
|
||||
compiler: clang
|
||||
sudo: required
|
||||
|
||||
script:
|
||||
- sudo -E apt-get install fakeroot
|
||||
- sudo -E make install-build-pkgs
|
||||
- make
|
||||
- make -j$(($(nproc) + 1))
|
||||
- make deb
|
||||
|
||||
@@ -37,8 +37,7 @@ endif
|
||||
endif
|
||||
|
||||
USE_XATTR = 1
|
||||
|
||||
FUSE_CFLAGS = -D_FILE_OFFSET_BITS=64 -Ilibfuse/include
|
||||
UGID_USE_RWLOCK = 0
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
DEBUG_FLAGS := -g
|
||||
@@ -58,29 +57,30 @@ else
|
||||
LTO_FLAGS :=
|
||||
endif
|
||||
|
||||
UGID_USE_RWLOCK = 0
|
||||
|
||||
OPTS = -O2
|
||||
SRC = $(wildcard src/*.cpp)
|
||||
OBJS = $(SRC:src/%.cpp=build/%.o)
|
||||
DEPS = $(SRC:src/%.cpp=build/%.d)
|
||||
MANPAGE = mergerfs.1
|
||||
CXXFLAGS += \
|
||||
$(OPTS) \
|
||||
CXXFLAGS = \
|
||||
-O2 \
|
||||
$(DEBUG_FLAGS) \
|
||||
$(STATIC_FLAGS) \
|
||||
$(LTO_FLAGS) \
|
||||
-Wall \
|
||||
-Wno-unused-result \
|
||||
$(FUSE_CFLAGS) \
|
||||
-DFUSE_USE_VERSION=29 \
|
||||
-MMD \
|
||||
-MMD
|
||||
FUSE_FLAGS = \
|
||||
-Ilibfuse/include \
|
||||
-D_FILE_OFFSET_BITS=64 \
|
||||
-DFUSE_USE_VERSION=29
|
||||
MFS_FLAGS = \
|
||||
-DUSE_XATTR=$(USE_XATTR) \
|
||||
-DUGID_USE_RWLOCK=$(UGID_USE_RWLOCK)
|
||||
LDFLAGS += \
|
||||
LDFLAGS = \
|
||||
-pthread \
|
||||
-lrt
|
||||
|
||||
DESTDIR =
|
||||
PREFIX = /usr/local
|
||||
EXEC_PREFIX = $(PREFIX)
|
||||
DATAROOTDIR = $(PREFIX)/share
|
||||
@@ -108,13 +108,10 @@ objects: version build/stamp
|
||||
$(MAKE) $(OBJS)
|
||||
|
||||
build/mergerfs: libfuse objects
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(OBJS) -o $@ libfuse/build/libfuse.a $(LDFLAGS)
|
||||
$(CXX) $(CXXFLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) $(OBJS) -o $@ libfuse/build/libfuse.a $(LDFLAGS)
|
||||
|
||||
mergerfs: build/mergerfs
|
||||
|
||||
build/mount.mergerfs: build/mergerfs
|
||||
$(LN) -fs "$<" "$@"
|
||||
|
||||
changelog:
|
||||
ifeq ($(GIT_REPO),1)
|
||||
$(GIT2DEBCL) --name mergerfs > ChangeLog
|
||||
@@ -131,7 +128,7 @@ build/stamp:
|
||||
$(TOUCH) $@
|
||||
|
||||
build/%.o: src/%.cpp
|
||||
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
$(CXX) $(CXXFLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
.PHONY: clean
|
||||
clean: rpm-clean
|
||||
@@ -145,15 +142,15 @@ ifeq ($(GIT_REPO),1)
|
||||
endif
|
||||
|
||||
.PHONY: install
|
||||
install: install-base install-mount.mergerfs install-man
|
||||
install: install-base install-mount-tools install-man
|
||||
|
||||
install-base: build/mergerfs
|
||||
$(MKDIR) -p "$(INSTALLBINDIR)"
|
||||
$(INSTALL) -v -m 0755 build/mergerfs "$(INSTALLBINDIR)/mergerfs"
|
||||
|
||||
install-mount.mergerfs: build/mount.mergerfs
|
||||
install-mount-tools: install-base
|
||||
$(MKDIR) -p "$(INSTALLBINDIR)"
|
||||
$(CP) -a build/mount.mergerfs "$(INSTALLBINDIR)/mount.mergerfs"
|
||||
$(MAKE) -C libfuse install
|
||||
|
||||
install-man: $(MANPAGE)
|
||||
$(MKDIR) -p "$(INSTALLMAN1DIR)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
% mergerfs(1) mergerfs user manual
|
||||
% Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
% 2019-05-12
|
||||
% 2020-02-09
|
||||
|
||||
# NAME
|
||||
|
||||
@@ -31,6 +31,7 @@ mergerfs -o<options> <branches> <mountpoint>
|
||||
* Handles pool of read-only and read/write drives
|
||||
* Can turn read-only files into symlinks to underlying file
|
||||
* Hard link copy-on-write / CoW
|
||||
* supports POSIX ACLs
|
||||
|
||||
|
||||
# How it works
|
||||
@@ -58,41 +59,64 @@ A + B = C
|
||||
+-- file6
|
||||
```
|
||||
|
||||
mergerfs does **not** support the copy-on-write (CoW) behavior found in **aufs** and **overlayfs**. You can **not** mount a read-only filesystem and write to it. However, mergerfs will ignore read-only drives when creating new files so you can mix read-write and read-only drives.
|
||||
mergerfs does **not** support the copy-on-write (CoW) behavior found in **aufs** and **overlayfs**. You can **not** mount a read-only filesystem and write to it. However, mergerfs will ignore read-only drives when creating new files so you can mix read-write and read-only drives. It also does **not** split data across drives. It is not RAID0 / striping. It is simply a union.
|
||||
|
||||
|
||||
# OPTIONS
|
||||
|
||||
### mount options
|
||||
|
||||
* **allow_other**: a libfuse option which allows users besides the one which ran mergerfs to see the filesystem. This is required for most use-cases.
|
||||
* **direct_io**: causes FUSE to bypass caching which can increase write speeds at the detriment of reads. Note that not enabling `direct_io` will cause double caching of files and therefore less memory for caching generally (enable **dropcacheonclose** to help with this problem). However, `mmap` does not work when `direct_io` is enabled.
|
||||
* **minfreespace=value**: the minimum space value used for creation policies. Understands 'K', 'M', and 'G' to represent kilobyte, megabyte, and gigabyte respectively. (default: 4G)
|
||||
* **moveonenospc=true|false**: when enabled (set to **true**) if a **write** fails with **ENOSPC** or **EDQUOT** a scan of all drives will be done looking for the drive with the most free space which is at least the size of the file plus the amount which failed to write. An attempt to move the file to that drive will occur (keeping all metadata possible) and if successful the original is unlinked and the write retried. (default: false)
|
||||
* **use_ino**: causes mergerfs to supply file/directory inodes rather than libfuse. While not a default it is recommended it be enabled so that linked files share the same inode value.
|
||||
* **dropcacheonclose=true|false**: when a file is requested to be closed call `posix_fadvise` on it first to instruct the kernel that we no longer need the data and it can drop its cache. Recommended when **direct_io** is not enabled to limit double caching. (default: false)
|
||||
* **symlinkify=true|false**: when enabled (set to **true**) and a file is not 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=value**: time to wait, in seconds, to activate the **symlinkify** behavior. (default: 3600)
|
||||
* **nullrw=true|false**: turns reads and writes into no-ops. The request will succeed but do nothing. Useful for benchmarking mergerfs. (default: false)
|
||||
* **ignorepponrename=true|false**: ignore path preserving on rename. Typically rename and link act differently depending on the policy of `create` (read below). Enabling this will cause rename and link to always use the non-path preserving behavior. This means files, when renamed or linked, will stay on the same drive. (default: false)
|
||||
* **security_capability=true|false**: If false return ENOATTR when xattr security.capability is queried. (default: true)
|
||||
* **allow_other**: A libfuse option which allows users besides the one which ran mergerfs to see the filesystem. This is required for most use-cases.
|
||||
* **minfreespace=SIZE**: The minimum space value used for creation policies. Understands 'K', 'M', and 'G' to represent kilobyte, megabyte, and gigabyte respectively. (default: 4G)
|
||||
* **moveonenospc=BOOL**: When enabled if a **write** fails with **ENOSPC** or **EDQUOT** a scan of all drives will be done looking for the drive with the most free space which is at least the size of the file plus the amount which failed to write. An attempt to move the file to that drive will occur (keeping all metadata possible) and if successful the original is unlinked and the write retried. (default: false)
|
||||
* **use_ino**: Causes mergerfs to supply file/directory inodes rather than libfuse. While not a default it is recommended it be enabled so that linked files share the same inode value.
|
||||
* **dropcacheonclose=BOOL**: When a file is requested to be closed call `posix_fadvise` on it first to instruct the kernel that we no longer need the data and it can drop its cache. Recommended when **cache.files=partial|full|auto-full** to limit double caching. (default: false)
|
||||
* **symlinkify=BOOL**: When enabled and a file is not 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=INT**: 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 mergerfs. (default: false)
|
||||
* **ignorepponrename=BOOL**: Ignore path preserving on rename. Typically rename and link act differently depending on the policy of `create` (read below). Enabling this will cause rename and link to always use the non-path preserving behavior. This means files, when renamed or linked, will stay on the same drive. (default: false)
|
||||
* **security_capability=BOOL**: If false return ENOATTR when xattr security.capability is queried. (default: true)
|
||||
* **xattr=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=true|false**: When enabled if a regular file is opened which has a link count > 1 it will copy the file to a temporary file and rename over the original. Breaking the link and providing a basic copy-on-write function similar to cow-shell. (default: false)
|
||||
* **link_cow=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 cow-shell. (default: false)
|
||||
* **statfs=base|full**: Controls how statfs works. 'base' means it will always use all branches in statfs calculations. 'full' is in effect path preserving and only includes drives where the path exists. (default: base)
|
||||
* **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)
|
||||
* **threads=num**: number of threads to use in multithreaded mode. When set to zero (the default) it will attempt to discover and use the number of logical cores. If the lookup fails it will fall back to using 4. If the thread count is set negative it will look up the number of cores then divide by the absolute value. ie. threads=-2 on an 8 core machine will result in 8 / 2 = 4 threads. There will always be at least 1 thread. NOTE: higher number of threads increases parallelism but usually decreases throughput. (default: number of cores) *NOTE2:* the option is unavailable when built with system libfuse.
|
||||
* **fsname=name**: 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>=<policy>**: sets the specific FUSE function's policy. See below for the list of value types. Example: **func.getattr=newest**
|
||||
* **category.<category>=<policy>**: Sets policy of all FUSE functions in the provided category. Example: **category.create=mfs**
|
||||
* **cache.open=<int>**: 'open' policy cache timeout in seconds. (default: 0)
|
||||
* **cache.statfs=<int>**: 'statfs' cache timeout in seconds. (default: 0)
|
||||
* **cache.attr=<int>**: file attribute cache timeout in seconds. (default: 1)
|
||||
* **cache.entry=<int>**: file name lookup cache timeout in seconds. (default: 1)
|
||||
* **cache.negative_entry=<int>**: negative file name lookup cache timeout in seconds. (default: 0)
|
||||
* **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 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=INT**: Set the max number of pages per FUSE message. Only available on Linux >= 4.20 and ignored otherwise. (min: 1; max: 256; default: 256)
|
||||
* **threads=INT**: Number of threads to use in multithreaded mode. When set to zero it will attempt to discover and use the number of logical cores. If the lookup fails it will fall back to using 4. If the thread count is set negative it will look up the number of cores then divide by the absolute value. ie. threads=-2 on an 8 core machine will result in 8 / 2 = 4 threads. There will always be at least 1 thread. NOTE: higher number of threads increases parallelism but usually decreases throughput. (default: 0)
|
||||
* **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=POLICY**: Sets the specific FUSE function's policy. See below for the list of value types. Example: **func.getattr=newest**
|
||||
* **category.CATEGORY=POLICY**: Sets policy of all FUSE functions in the provided category. See POLICIES section for defaults. Example: **category.create=mfs**
|
||||
* **cache.open=INT**: 'open' policy cache timeout in seconds. (default: 0)
|
||||
* **cache.statfs=INT**: 'statfs' cache timeout in seconds. (default: 0)
|
||||
* **cache.attr=INT**: File attribute cache timeout in seconds. (default: 1)
|
||||
* **cache.entry=INT**: File name lookup cache timeout in seconds. (default: 1)
|
||||
* **cache.negative_entry=INT**: Negative file name lookup cache timeout in seconds. (default: 0)
|
||||
* **cache.files=libfuse|off|partial|full|auto-full**: File page caching mode (default: libfuse)
|
||||
* **cache.writeback=BOOL**: Enable kernel writeback caching (default: false)
|
||||
* **cache.symlinks=BOOL**: Cache symlinks (if supported by kernel) (default: false)
|
||||
* **cache.readdir=BOOL**: Cache readdir (if supported by kernel) (default: false)
|
||||
* **direct_io**: deprecated - Bypass page cache. Use `cache.files=off` instead. (default: false)
|
||||
* **kernel_cache**: deprecated - Do not invalidate data cache on file open. Use `cache.files=full` instead. (default: false)
|
||||
* **auto_cache**: deprecated - Invalidate data cache if file mtime or size change. Use `cache.files=auto-full` instead. (default: false)
|
||||
* **async_read**: deprecated - Perform reads asynchronously. Use `async_read=true` instead.
|
||||
* **sync_read**: deprecated - Perform reads synchronously. Use `async_read=false` instead.
|
||||
|
||||
|
||||
**NOTE:** Options are evaluated in the order listed so if the options are **func.rmdir=rand,category.action=ff** the **action** category setting will override the **rmdir** setting.
|
||||
|
||||
|
||||
#### Value Types
|
||||
|
||||
* BOOL = 'true' | 'false'
|
||||
* INT = [0,MAX_INT]
|
||||
* SIZE = 'NNM'; NN = INT, M = 'K' | 'M' | 'G' | 'T'
|
||||
* STR = string
|
||||
* FUNC = FUSE function
|
||||
* CATEGORY = FUSE function category
|
||||
* POLICY = mergerfs function policy
|
||||
|
||||
|
||||
### branches
|
||||
|
||||
The 'branches' (formerly 'srcmounts') argument is a colon (':') delimited list of paths to be pooled together. It does not matter if the paths are on the same or different drives nor does it matter the filesystem. Used and available space will not be duplicated for paths on the same device and any features which aren't supported by the underlying filesystem (such as file attributes or extended attributes) will return the appropriate errors.
|
||||
@@ -107,11 +131,11 @@ Each branch can have a suffix of `=RW` (read / write), `=RO` (read-only), or `=N
|
||||
|
||||
The above line will use all mount points in /mnt prefixed with **disk** and the **cdrom**.
|
||||
|
||||
To have the pool mounted at boot or otherwise accessable from related tools use **/etc/fstab**.
|
||||
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/disk*:/mnt/cdrom /media/drives fuse.mergerfs allow_other,use_ino 0 0
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/disk*:/mnt/cdrom /media/drives mergerfs allow_other,use_ino 0 0
|
||||
```
|
||||
|
||||
**NOTE:** the globbing is done at mount or xattr update time (see below). If a new directory is added matching the glob after the fact it will not be automatically included.
|
||||
@@ -119,9 +143,18 @@ To have the pool mounted at boot or otherwise accessable from related tools use
|
||||
**NOTE:** for mounting via **fstab** to work you must have **mount.fuse** installed. For Ubuntu/Debian it is included in the **fuse** package.
|
||||
|
||||
|
||||
### fuse_msg_size
|
||||
|
||||
FUSE applications communicate with the kernel over a special character device: `/dev/fuse`. A large portion of the overhead associated with FUSE is the cost of going back and forth from user space and kernel space over that device. Generally speaking the fewer trips needed the better the performance will be. Reducing the number of trips can be done a number of ways. Kernel level caching and increasing message sizes being two significant ones. When it comes to reads and writes if the message size is doubled the number of trips are approximately halved.
|
||||
|
||||
In Linux 4.20 a new feature was added allowing the negotiation of the max message size. Since the size is in multiples of [pages](https://en.wikipedia.org/wiki/Page_(computer_memory)) the feature is called `max_pages`. There is a maximum `max_pages` value of 256 (1MiB) and minimum of 1 (4KiB). The default used by Linux >=4.20, and hardcoded value used before 4.20, is 32 (128KiB). In mergerfs its referred to as `fuse_msg_size` to make it clear what it impacts and provide some abstraction.
|
||||
|
||||
Since there should be no downsides to increasing `fuse_msg_size` / `max_pages`, outside a minor bump in RAM usage due to larger message buffers, mergerfs defaults the value to 256. On kernels before 4.20 the value has no effect. The reason the value is configurable is to enable experimentation and benchmarking. See the BENCHMARKING section for examples.
|
||||
|
||||
|
||||
### symlinkify
|
||||
|
||||
Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degredation. This feature will turn non-directories which are not writable into symlinks to the original file found by the `readlink` policy after the mtime and ctime are older than the timeout.
|
||||
Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degradation. This feature will turn non-directories which are not writable into symlinks to the original file found by the `readlink` policy after the mtime and ctime are older than the timeout.
|
||||
|
||||
**WARNING:** The current implementation has a known issue in which if the file is open and being used when the file is converted to a symlink then the application which has that file open will receive an error when using it. This is unlikely to occur in practice but is something to keep in mind.
|
||||
|
||||
@@ -130,34 +163,11 @@ Due to the levels of indirection introduced by mergerfs and the underlying techn
|
||||
|
||||
### nullrw
|
||||
|
||||
Due to how FUSE works there is an overhead to all requests made to a FUSE filesystem. Meaning that even a simple passthrough will have some slowdown. However, generally the overhead is minimal in comparison to the cost of the underlying I/O. By disabling the underlying I/O we can test the theoretical performance boundries.
|
||||
Due to how FUSE works there is an overhead to all requests made to a FUSE filesystem that wouldn't exist for an in kernel one. Meaning that even a simple passthrough will have some slowdown. However, generally the overhead is minimal in comparison to the cost of the underlying I/O. By disabling the underlying I/O we can test the theoretical performance boundaries.
|
||||
|
||||
By enabling `nullrw` mergerfs will work as it always does **except** that all reads and writes will be no-ops. A write will succeed (the size of the write will be returned as if it were successful) but mergerfs does nothing with the data it was given. Similarly a read will return the size requested but won't touch the buffer.
|
||||
|
||||
Example:
|
||||
```
|
||||
$ dd if=/dev/zero of=/path/to/mergerfs/mount/benchmark ibs=1M obs=512 count=1024 conv=fdatasync
|
||||
1024+0 records in
|
||||
2097152+0 records out
|
||||
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 15.4067 s, 69.7 MB/s
|
||||
|
||||
$ dd if=/dev/zero of=/path/to/mergerfs/mount/benchmark ibs=1M obs=1M count=1024 conv=fdatasync
|
||||
1024+0 records in
|
||||
1024+0 records out
|
||||
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.219585 s, 4.9 GB/s
|
||||
|
||||
$ dd if=/path/to/mergerfs/mount/benchmark of=/dev/null bs=512 count=102400 conv=fdatasync
|
||||
102400+0 records in
|
||||
102400+0 records out
|
||||
52428800 bytes (52 MB, 50 MiB) copied, 0.757991 s, 69.2 MB/s
|
||||
|
||||
$ dd if=/path/to/mergerfs/mount/benchmark of=/dev/null bs=1M count=1024 conv=fdatasync
|
||||
1024+0 records in
|
||||
1024+0 records out
|
||||
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.18405 s, 5.8 GB/s
|
||||
```
|
||||
|
||||
It's important to test with different `obs` (output block size) values since the relative overhead is greater with smaller values. As you can see above the size of a read or write can massively impact theoretical performance. If an application performs much worse through mergerfs it could very well be that it doesn't optimally size its read and write requests. In such cases contact the mergerfs author so it can be investigated.
|
||||
See the BENCHMARKING section for suggestions on how to test.
|
||||
|
||||
|
||||
### xattr
|
||||
@@ -184,7 +194,7 @@ When using policies which are based on a branch's available space the base path
|
||||
| action | chmod, chown, link, removexattr, rename, rmdir, setxattr, truncate, unlink, utimens |
|
||||
| create | create, mkdir, mknod, symlink |
|
||||
| search | access, getattr, getxattr, ioctl (directories), listxattr, open, readlink |
|
||||
| N/A | fchmod, fchown, futimens, ftruncate, fallocate, fgetattr, fsync, ioctl (files), read, readdir, release, statfs, write |
|
||||
| N/A | fchmod, fchown, futimens, ftruncate, fallocate, fgetattr, fsync, ioctl (files), read, readdir, release, statfs, write, copy_file_range |
|
||||
|
||||
In cases where something may be searched (to confirm a directory exists across all source mounts) **getattr** will be used.
|
||||
|
||||
@@ -221,14 +231,14 @@ If all branches are filtered an error will be returned. Typically **EROFS** or *
|
||||
| eplfs (existing path, least free space) | Of all the branches on which the relative path exists choose the drive with the least free space. |
|
||||
| eplus (existing path, least used space) | Of all the branches on which the relative path exists choose the drive with the least used space. |
|
||||
| epmfs (existing path, most free space) | Of all the branches on which the relative path exists choose the drive with the most free space. |
|
||||
| eprand (existing path, random) | Calls **epall** and then randomizes. |
|
||||
| eprand (existing path, random) | Calls **epall** and then randomizes. Returns 1. |
|
||||
| erofs | Exclusively return **-1** with **errno** set to **EROFS** (read-only filesystem). |
|
||||
| ff (first found) | Search category: same as **epff**. Action category: same as **epff**. Create category: Given the order of the drives, as defined at mount time or configured at runtime, act on the first one found. |
|
||||
| lfs (least free space) | Search category: same as **eplfs**. Action category: same as **eplfs**. Create category: Pick the drive with the least available free space. |
|
||||
| lus (least used space) | Search category: same as **eplus**. Action category: same as **eplus**. Create category: Pick the drive with the least used space. |
|
||||
| mfs (most free space) | Search category: same as **epmfs**. Action category: same as **epmfs**. Create category: Pick the drive with the most available free space. |
|
||||
| newest | Pick the file / directory with the largest mtime. |
|
||||
| rand (random) | Calls **all** and then randomizes. |
|
||||
| rand (random) | Calls **all** and then randomizes. Returns 1. |
|
||||
|
||||
|
||||
#### Defaults ####
|
||||
@@ -264,7 +274,7 @@ The plan is to rewrite mergerfs to use the low level API so these invasive libfu
|
||||
|
||||
`rename` and `link` are tricky functions in a union filesystem. `rename` only works within a single filesystem or device. If a rename can't be done atomically due to the source and destination paths existing on different mount points it will return **-1** with **errno = EXDEV** (cross device). So if a `rename`'s source and target are on different drives within the pool it creates an issue.
|
||||
|
||||
Originally mergerfs would return EXDEV whenever a rename was requested which was cross directory in any way. This made the code simple and was technically complient with POSIX requirements. However, many applications fail to handle EXDEV at all and treat it as a normal error or otherwise handle it poorly. Such apps include: gvfsd-fuse v1.20.3 and prior, Finder / CIFS/SMB client in Apple OSX 10.9+, NZBGet, Samba's recycling bin feature.
|
||||
Originally mergerfs would return EXDEV whenever a rename was requested which was cross directory in any way. This made the code simple and was technically compliant with POSIX requirements. However, many applications fail to handle EXDEV at all and treat it as a normal error or otherwise handle it poorly. Such apps include: gvfsd-fuse v1.20.3 and prior, Finder / CIFS/SMB client in Apple OSX 10.9+, NZBGet, Samba's recycling bin feature.
|
||||
|
||||
As a result a compromise was made in order to get most software to work while still obeying mergerfs' policies. Below is the basic logic.
|
||||
|
||||
@@ -301,12 +311,12 @@ The above behavior will help minimize the likelihood of EXDEV being returned but
|
||||
|
||||
#### readdir ####
|
||||
|
||||
[readdir](http://linux.die.net/man/3/readdir) is different from all other filesystem functions. While it could have it's own set of policies to tweak its behavior at this time it provides a simple union of files and directories found. Remember that any action or information queried about these files and directories come from the respective function. For instance: an **ls** is a **readdir** and for each file/directory returned **getattr** is called. Meaning the policy of **getattr** is responsible for choosing the file/directory which is the source of the metadata you see in an **ls**.
|
||||
[readdir](http://linux.die.net/man/3/readdir) is different from all other filesystem functions. While it could have its own set of policies to tweak its behavior at this time it provides a simple union of files and directories found. Remember that any action or information queried about these files and directories come from the respective function. For instance: an **ls** is a **readdir** and for each file/directory returned **getattr** is called. Meaning the policy of **getattr** is responsible for choosing the file/directory which is the source of the metadata you see in an **ls**.
|
||||
|
||||
|
||||
#### statfs / statvfs ####
|
||||
|
||||
[statvfs](http://linux.die.net/man/2/statvfs) normalizes the source drives based on the fragment size and sums the number of adjusted blocks and inodes. This means you will see the combined space of all sources. Total, used, and free. The sources however are dedupped based on the drive so multiple sources on the same drive will not result in double counting it's space. Filesystems mounted further down the tree of the branch will not be included when checking the mount's stats.
|
||||
[statvfs](http://linux.die.net/man/2/statvfs) normalizes the source drives based on the fragment size and sums the number of adjusted blocks and inodes. This means you will see the combined space of all sources. Total, used, and free. The sources however are dedupped based on the drive so multiple sources on the same drive will not result in double counting its space. Filesystems mounted further down the tree of the branch will not be included when checking the mount's stats.
|
||||
|
||||
The options `statfs` and `statfs_ignore` can be used to modify `statfs` behavior.
|
||||
|
||||
@@ -331,7 +341,7 @@ $ make deb
|
||||
$ sudo dpkg -i ../mergerfs_version_arch.deb
|
||||
```
|
||||
|
||||
#### Fedora
|
||||
#### RHEL / CentOS /Fedora
|
||||
```
|
||||
$ su -
|
||||
# cd mergerfs
|
||||
@@ -342,7 +352,7 @@ $ su -
|
||||
|
||||
#### Generically
|
||||
|
||||
Have git, g++, make, python, automake, libtool installed.
|
||||
Have git, g++, make, python installed.
|
||||
|
||||
```
|
||||
$ cd mergerfs
|
||||
@@ -377,7 +387,12 @@ Any changes made at runtime are **not** persisted. If you wish for values to per
|
||||
|
||||
##### Keys #####
|
||||
|
||||
Use `xattr -l /mountpoint/.mergerfs` to see all supported keys. Some are informational and therefore read-only.
|
||||
Use `xattr -l /mountpoint/.mergerfs` to see all supported keys. Some are informational and therefore read-only. `setxattr` will return EINVAL on read-only keys.
|
||||
|
||||
|
||||
##### Values #####
|
||||
|
||||
Same as the command line.
|
||||
|
||||
|
||||
###### user.mergerfs.branches ######
|
||||
@@ -400,32 +415,11 @@ Used to query or modify the list of branches. When modifying there are several s
|
||||
The `=NC`, `=RO`, `=RW` syntax works just as on the command line.
|
||||
|
||||
|
||||
###### minfreespace ######
|
||||
|
||||
Input: interger with an optional multiplier suffix. **K**, **M**, or **G**.
|
||||
|
||||
Output: value in bytes
|
||||
|
||||
|
||||
###### moveonenospc ######
|
||||
|
||||
Input: **true** and **false**
|
||||
|
||||
Ouput: **true** or **false**
|
||||
|
||||
|
||||
###### categories / funcs ######
|
||||
|
||||
Input: short policy string as described elsewhere in this document
|
||||
|
||||
Output: the policy string except for categories where its funcs have multiple types. In that case it will be a comma separated list
|
||||
|
||||
|
||||
##### Example #####
|
||||
|
||||
```
|
||||
[trapexit:/mnt/mergerfs] $ xattr -l .mergerfs
|
||||
user.mergerfs.branches: /mnt/a:/mnt/b
|
||||
user.mergerfs.branches: /mnt/a=RW:/mnt/b=RW
|
||||
user.mergerfs.minfreespace: 4294967295
|
||||
user.mergerfs.moveonenospc: false
|
||||
...
|
||||
@@ -455,10 +449,10 @@ newest
|
||||
|
||||
While they won't show up when using [listxattr](http://linux.die.net/man/2/listxattr) **mergerfs** offers a number of special xattrs to query information about the files served. To access the values you will need to issue a [getxattr](http://linux.die.net/man/2/getxattr) for one of the following:
|
||||
|
||||
* **user.mergerfs.basepath:** the base mount point for the file given the current getattr policy
|
||||
* **user.mergerfs.relpath:** the relative path of the file from the perspective of the mount point
|
||||
* **user.mergerfs.fullpath:** the full path of the original file given the getattr policy
|
||||
* **user.mergerfs.allpaths:** a NUL ('\0') separated list of full paths to all files found
|
||||
* **user.mergerfs.basepath**: the base mount point for the file given the current getattr policy
|
||||
* **user.mergerfs.relpath**: the relative path of the file from the perspective of the mount point
|
||||
* **user.mergerfs.fullpath**: the full path of the original file given the getattr policy
|
||||
* **user.mergerfs.allpaths**: a NUL ('\0') separated list of full paths to all files found
|
||||
|
||||
```
|
||||
[trapexit:/mnt/mergerfs] $ ls
|
||||
@@ -475,6 +469,20 @@ A B C
|
||||
```
|
||||
|
||||
|
||||
# UPGRADE
|
||||
|
||||
mergerfs can be upgraded live by mounting on top of the previous version. Simply install the new version of mergerfs and follow the instructions below.
|
||||
|
||||
Add `nonempty` to your mergerfs option list and call mergerfs again or if using `/etc/fstab` call for it to mount again. Existing open files and such will continue to work fine though they won't see runtime changes since any such change would be the new mount. If you plan on changing settings with the new mount you should / could apply those before mounting the new version.
|
||||
|
||||
```
|
||||
$ sudo mount /mnt/mergerfs
|
||||
$ mount | grep mergerfs
|
||||
media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
```
|
||||
|
||||
|
||||
# TOOLING
|
||||
|
||||
* https://github.com/trapexit/mergerfs-tools
|
||||
@@ -483,6 +491,7 @@ A B C
|
||||
* mergerfs.dedup: Will help identify and optionally remove duplicate files
|
||||
* mergerfs.dup: Ensure there are at least N copies of a file across the pool
|
||||
* mergerfs.balance: Rebalance files across drives by moving them from the most filled to the least filled
|
||||
* mergerfs.consolidate: move files within a single mergerfs directory to the drive with most free space
|
||||
* mergerfs.mktrash: Creates FreeDesktop.org Trash specification compatible directories on a mergerfs mount
|
||||
* https://github.com/trapexit/scorch
|
||||
* scorch: A tool to help discover silent corruption of files and keep track of files
|
||||
@@ -494,16 +503,41 @@ A B C
|
||||
|
||||
#### page caching
|
||||
|
||||
The kernel performs caching of data pages on all files not opened with `O_DIRECT`. Due to mergerfs using FUSE and therefore being a userland process the kernel can double cache the content being read through mergerfs. Once from the underlying filesystem and once for mergerfs. Using `direct_io` and/or `dropcacheonclose` help minimize the double caching. `direct_io` will instruct the kernel to bypass the page cache for files opened through mergerfs. `dropcacheonclose` will cause mergerfs to instruct the kernel to flush a file's page cache for which it had opened when closed. If most data is read once its probably best to enable both (read above for details and limitations).
|
||||
https://en.wikipedia.org/wiki/Page_cache
|
||||
|
||||
If a cache is desired for mergerfs do not enable `direct_io` and instead possibly use `auto_cache` or `kernel_cache`. By default FUSE will invalidate cached pages when a file is opened. By using `auto_cache` it will instead use `getattr` to check if a file has changed when the file is opened and if so will flush the cache. `ac_attr_timeout` is the timeout for keeping said cache. Alternatively `kernel_cache` will keep the cache across opens unless invalidated through other means. You should only uses these if you do not plan to write/modify the same files through mergerfs and the underlying filesystem at the same time. It could lead to corruption. Then again doing so without caching can also cause issues.
|
||||
tl;dr:
|
||||
* cache.files=off: Disables page caching. Underlying files cached, mergerfs files are not.
|
||||
* cache.files=partial: Enables page caching. Underlying files cached, mergerfs files cached while open.
|
||||
* cache.files=full: Enables page caching. Underlying files cached, mergerfs files cached across opens.
|
||||
* cache.files=auto-full: Enables page caching. Underlying files cached, mergerfs files cached across opens if mtime and size are unchanged since previous open.
|
||||
* cache.files=libfuse: follow traditional libfuse `direct_io`, `kernel_cache`, and `auto_cache` arguments.
|
||||
|
||||
It's a difficult balance between memory usage, cache bloat & duplication, and performance. Ideally mergerfs would be able to disable caching for the files it reads/writes but allow page caching for itself. That would limit the FUSE overhead. However, there isn't good way to achieve this.
|
||||
|
||||
FUSE, which mergerfs uses, offers a number of page caching modes. mergerfs tries to simplify their use via the `cache.files` option. It can and should replace usage of `direct_io`, `kernel_cache`, and `auto_cache`.
|
||||
|
||||
Due to mergerfs using FUSE and therefore being a userland process proxying existing filesystems the kernel will double cache the content being read and written through mergerfs. Once from the underlying filesystem and once from mergerfs (it sees them as two separate entities). Using `cache.files=off` will keep the double caching from happening by disabling caching of mergerfs but this has the side effect that *all* read and write calls will be passed to mergerfs which may be slower than enabling caching, you lose shared `mmap` support which can affect apps such as rtorrent, and no read-ahead will take place. The kernel will still cache the underlying filesystem data but that only helps so much given mergerfs will still process all requests.
|
||||
|
||||
If you do enable file page caching, `cache.files=partial|full|auto-full`, you should also enable `dropcacheonclose` which will cause mergerfs to instruct the kernel to flush the underlying file's page cache when the file is closed. This behavior is the same as the rsync fadvise / drop cache patch and Feh's nocache project.
|
||||
|
||||
If most files are read once through and closed (like media) it is best to enable `dropcacheonclose` regardless of caching mode in order to minimize buffer bloat.
|
||||
|
||||
It is difficult to balance memory usage, cache bloat & duplication, and performance. Ideally mergerfs would be able to disable caching for the files it reads/writes but allow page caching for itself. That would limit the FUSE overhead. However, there isn't a good way to achieve this. It would need to open all files with O_DIRECT which places limitations on the what underlying filesystems would be supported and complicates the code.
|
||||
|
||||
kernel documentation: https://www.kernel.org/doc/Documentation/filesystems/fuse-io.txt
|
||||
|
||||
|
||||
#### entry & attribute caching
|
||||
|
||||
Given the relatively high cost of FUSE due to the kernel <-> userspace round trips there are kernel side caches for file entries and attributes. The entry cache limits the `lookup` calls to mergerfs which ask if a file exists. The attribute cache limits the need to make `getattr` calls to mergerfs which provide file attributes (mode, size, type, etc.). As with the page cache these should not be used if the underlying filesystems are being manipulated at the same time as it could lead to odd behavior or data corruption. The options for setting these are `cache.entry` and `cache.negative_entry` for the entry cache and `cache.attr` for the attributes cache. `cache.negative_entry` refers to the timeout for negative responses to lookups (non-existant files).
|
||||
Given the relatively high cost of FUSE due to the kernel <-> userspace round trips there are kernel side caches for file entries and attributes. The entry cache limits the `lookup` calls to mergerfs which ask if a file exists. The attribute cache limits the need to make `getattr` calls to mergerfs which provide file attributes (mode, size, type, etc.). As with the page cache these should not be used if the underlying filesystems are being manipulated at the same time as it could lead to odd behavior or data corruption. The options for setting these are `cache.entry` and `cache.negative_entry` for the entry cache and `cache.attr` for the attributes cache. `cache.negative_entry` refers to the timeout for negative responses to lookups (non-existent files).
|
||||
|
||||
|
||||
#### writeback caching
|
||||
|
||||
When `cache.files` is enabled the default is for it to perform writethrough caching. This behavior won't help improve performance as each write still goes one for one through the filesystem. By enabling the FUSE writeback cache small writes may be aggregated by the kernel and then sent to mergerfs as one larger request. This can greatly improve the throughput for apps which write to files inefficiently. The amount the kernel can aggregate is limited by the size of a FUSE message. Read the `fuse_msg_size` section for more details.
|
||||
|
||||
There is a small side effect as a result of enabling wrtieback caching. Underlying files won't ever be opened with O_APPEND or O_WRONLY. The former because the kernel then manages append mode and the latter because the kernel may request file data from mergerfs to populate the write cache. The O_APPEND change means that if a file is changed outside of mergerfs it could lead to corruption as the kernel won't know the end of the file has changed. That said any time you use caching you should keep from using the same file outside of mergerfs at the same time.
|
||||
|
||||
Note that if an application is properly sizing writes then writeback caching will have little or no effect. It will only help with writes of sizes below the FUSE message size (128K on older kernels, 1M on newer).
|
||||
|
||||
|
||||
#### policy caching
|
||||
@@ -522,9 +556,14 @@ Of the syscalls used by mergerfs in policies the `statfs` / `statvfs` call is pe
|
||||
Example: If the create policy is `mfs` and the timeout is 60 then for that 60 seconds the same drive will be returned as the target for creates because the available space won't be updated for that time.
|
||||
|
||||
|
||||
#### writeback caching
|
||||
#### symlink caching
|
||||
|
||||
writeback caching is a technique for improving write speeds by batching writes at a faster device and then bulk writing to the slower device. With FUSE the kernel will wait for a number of writes to be made and then send it to the filesystem as one request. mergerfs currently uses a slightly modified and vendored libfuse 2.9.7 which does not support writeback caching. However, a prototype port to libfuse 3.x has been made and the writeback cache appears to work as expected (though performance improvements greatly depend on the way the client app writes data). Once the port is complete and thoroughly tested writeback caching will be available.
|
||||
As of version 4.20 Linux supports symlink caching. Significant performance increases can be had in workloads which use a lot of symlinks. Setting `cache.symlinks=true` will result in requesting symlink caching from the kernel only if supported. As a result its safe to enable it on systems prior to 4.20. That said it is disabled by default for now. You can see if caching is enabled by querying the xattr `user.mergerfs.cache.symlinks` but given it must be requested at startup you can not change it at runtime.
|
||||
|
||||
|
||||
#### readdir caching
|
||||
|
||||
As of version 4.20 Linux supports readdir caching. This can have a significant impact on directory traversal. Especially when combined with entry (`cache.entry`) and attribute (`cache.attr`) caching. Setting `cache.readdir=true` will result in requesting readdir caching from the kernel on each `opendir`. If the kernel doesn't support readdir caching setting the option to `true` has no effect. This option is configurable at runtime via xattr `user.mergerfs.cache.readdir`.
|
||||
|
||||
|
||||
#### tiered caching
|
||||
@@ -599,13 +638,75 @@ done
|
||||
```
|
||||
|
||||
|
||||
# PERFORMANCE
|
||||
|
||||
mergerfs is at its core just a proxy and therefore its theoretical max performance is that of the underlying devices. However, given it is a FUSE filesystem working from userspace there is an increase in overhead relative to kernel based solutions. That said the performance can match the theoretical max but it depends greatly on the system's configuration. Especially when adding network filesystems into the mix there are many variables which can impact performance. Drive speeds and latency, network speeds and lattices, general concurrency, read/write sizes, etc. Unfortunately, given the number of variables it has been difficult to find a single set of settings which provide optimal performance. If you're having performance issues please look over the suggestions below.
|
||||
|
||||
NOTE: be sure to read about these features before changing them
|
||||
|
||||
* enable (or disable) `splice_move`, `splice_read`, and `splice_write`
|
||||
* increase cache timeouts `cache.attr`, `cache.entry`, `cache.negative_entry`
|
||||
* enable (or disable) page caching (`cache.files`)
|
||||
* enable `cache.open`
|
||||
* enable `cache.statfs`
|
||||
* enable `cache.symlinks`
|
||||
* enable `cache.readdir`
|
||||
* change the number of worker threads
|
||||
* disable `security_capability` and/or `xattr`
|
||||
* disable `posix_acl`
|
||||
* disable `async_read`
|
||||
* test theoretical performance using `nullrw` or mounting a ram disk
|
||||
* use `symlinkify` if your data is largely static
|
||||
* use tiered cache drives
|
||||
* use lvm and lvm cache to place a SSD in front of your HDDs (howto coming)
|
||||
|
||||
If you come across a setting that significantly impacts performance please contact trapexit so he may investigate further.
|
||||
|
||||
|
||||
# BENCHMARKING
|
||||
|
||||
Filesystems are complicated. They do many things and many of those are interconnected. Additionally, the OS, drivers, hardware, etc. all can impact performance. Therefore, when benchmarking, it is **necessary** that the test focus as narrowly as possible.
|
||||
|
||||
For most throughput is the key benchmark. To test throughput `dd` is useful but **must** be used with the correct settings in order to ensure the filesystem or device is actually being tested. The OS can and will cache data. Without forcing synchronous reads and writes and/or disabling caching the values returned will not be representative of the device's true performance.
|
||||
|
||||
When benchmarking through mergerfs ensure you only use 1 branch to remove any possibility of the policies complicating the situation. Benchmark the underlying filesystem first and then mount mergerfs over it and test again. If you're experience speeds below your expectation you will need to narrow down precisely which component is leading to the slowdown. Preferably test the following in the order listed (but not combined).
|
||||
|
||||
1. Enable `nullrw` mode with `nullrw=true`. This will effectively make reads and writes no-ops. Removing the underlying device / filesystem from the equation. This will give us the top theoretical speeds.
|
||||
2. Mount mergerfs over `tmpfs`. `tmpfs` is a RAM disk. Extremely high speed and very low latency. This is a more realistic best case scenario. Example: `mount -t tmpfs -o size=2G tmpfs /tmp/tmpfs`
|
||||
3. Mount mergerfs over a local drive. NVMe, SSD, HDD, etc. If you have more than one I'd suggest testing each of them as drives and/or controllers (their drivers) could impact performance.
|
||||
4. Finally, if you intend to use mergerfs with a network filesystem, either as the source of data or to combine with another through mergerfs, test each of those alone as above.
|
||||
|
||||
Once you find the component which has the performance issue you can do further testing with different options to see if they impact performance. For reads and writes the most relevant would be: `cache.files`, `async_read`, `splice_move`, `splice_read`, `splice_write`. Less likely but relevant when using NFS or with certain filesystems would be `security_capability`, `xattr`, and `posix_acl`. If you find a specific system, drive, filesystem, controller, etc. that performs poorly contact trapexit so he may investigate further.
|
||||
|
||||
Sometimes the problem is really the application accessing or writing data through mergerfs. Some software use small buffer sizes which can lead to more requests and therefore greater overhead. You can test this out yourself by replace `bs=1M` in the examples below with `ibs` or `obs` and using a size of `512` instead of `1M`. In one example test using `nullrw` the write speed dropped from 4.9GB/s to 69.7MB/s when moving from `1M` to `512`. Similar results were had when testing reads. Small writes overhead may be improved by leveraging a write cache but in casual tests little gain was found. More tests will need to be done before this feature would become available. If you have an app that appears slow with mergerfs it could be due to this. Contact trapexit so he may investigate further.
|
||||
|
||||
|
||||
### write benchmark
|
||||
|
||||
With synchronized IO
|
||||
```
|
||||
$ dd if=/dev/zero of=/mnt/mergerfs/1GB.file bs=1M count=1024 oflag=dsync,nocache conv=fdatasync status=progress
|
||||
```
|
||||
|
||||
Without synchronized IO
|
||||
```
|
||||
$ dd if=/dev/zero of=/mnt/mergerfs/1GB.file bs=1M count=1024 oflag=nocache conv=fdatasync status=progress
|
||||
```
|
||||
|
||||
### read benchmark
|
||||
|
||||
```
|
||||
$ dd if=/mnt/mergerfs/1GB.file of=/dev/null bs=1M count=1024 iflag=nocache conv=fdatasync status=progress
|
||||
```
|
||||
|
||||
|
||||
# TIPS / NOTES
|
||||
|
||||
* **use_ino** will only work when used with mergerfs 2.18.0 and above.
|
||||
* Run mergerfs as `root` (with **allow_other**) unless you're merging paths which are owned by the same user otherwise strange permission issues may arise.
|
||||
* https://github.com/trapexit/backup-and-recovery-howtos : A set of guides / howtos on creating a data storage system, backing it up, maintaining it, and recovering from failure.
|
||||
* If you don't see some directories and files you expect in a merged point or policies seem to skip drives be sure the user has permission to all the underlying directories. Use `mergerfs.fsck` to audit the drive for out of sync permissions.
|
||||
* Do **not** use `direct_io` if you expect applications (such as rtorrent) to [mmap](http://linux.die.net/man/2/mmap) files. It is not currently supported in FUSE w/ `direct_io` enabled. Enabling `dropcacheonclose` is recommended when `direct_io` is disabled.
|
||||
* Do **not** use `cache.files=off` (or `direct_io`) if you expect applications (such as rtorrent) to [mmap](http://linux.die.net/man/2/mmap) files. Shared mmap is not currently supported in FUSE w/ `direct_io` enabled. Enabling `dropcacheonclose` is recommended when `cache.files=partial|full|auto-full` or `direct_io=false`.
|
||||
* Since POSIX functions give only a singular error or success its difficult to determine the proper behavior when applying the function to multiple targets. **mergerfs** will return an error only if all attempts of an action fail. Any success will lead to a success returned. This means however that some odd situations may arise.
|
||||
* [Kodi](http://kodi.tv), [Plex](http://plex.tv), [Subsonic](http://subsonic.org), etc. can use directory [mtime](http://linux.die.net/man/2/stat) to more efficiently determine whether to scan for new content rather than simply performing a full scan. If using the default **getattr** policy of **ff** its possible those programs will miss an update on account of it returning the first directory found's **stat** info and its a later directory on another mount which had the **mtime** recently updated. To fix this you will want to set **func.getattr=newest**. Remember though that this is just **stat**. If the file is later **open**'ed or **unlink**'ed and the policy is different for those then a completely different file or directory could be acted on.
|
||||
* Some policies mixed with some functions may result in strange behaviors. Not that some of these behaviors and race conditions couldn't happen outside **mergerfs** but that they are far more likely to occur on account of the attempt to merge together multiple sources of data which could be out of sync due to the different policies.
|
||||
@@ -623,11 +724,11 @@ The reason this is the default is because any other policy would be more expensi
|
||||
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/disk1/foo' removes 'foo'
|
||||
|
||||
This is not a bug.
|
||||
|
||||
Run in verbose mode to better undertand what's happening:
|
||||
Run in verbose mode to better understand what's happening:
|
||||
|
||||
```
|
||||
$ mv -v /mnt/pool/foo /mnt/disk1/foo
|
||||
@@ -644,11 +745,7 @@ If you want to move files to one drive just copy them there and use mergerfs.ded
|
||||
|
||||
#### cached memory appears greater than it should be
|
||||
|
||||
Use the `direct_io` option as described above. Due to what mergerfs is doing there ends up being two caches of a file under normal usage. One from the underlying filesystem and one from mergerfs. Enabling `direct_io` removes the mergerfs cache. This saves on memory but means the kernel needs to communicate with mergerfs more often and can therefore result in slower speeds.
|
||||
|
||||
Since enabling `direct_io` disables `mmap` this is not an ideal situation however write speeds should be increased.
|
||||
|
||||
If `direct_io` is disabled it is probably a good idea to enable `dropcacheonclose` to minimize double caching.
|
||||
Use `cache.files=off` or `direct_io=true`. See the section on page caching.
|
||||
|
||||
|
||||
#### NFS clients returning ESTALE / Stale file handle
|
||||
@@ -665,7 +762,7 @@ Try enabling the `use_ino` option. Some have reported that it fixes the issue.
|
||||
|
||||
#### rtorrent fails with ENODEV (No such device)
|
||||
|
||||
Be sure to turn off `direct_io`. rtorrent and some other applications use [mmap](http://linux.die.net/man/2/mmap) to read and write to files and offer no failback to traditional methods. FUSE does not currently support mmap while using `direct_io`. There may be a performance penalty on writes with `direct_io` off as well as the problem of double caching but it's the only way to get such applications to work. If the performance loss is too high for other apps you can mount mergerfs twice. Once with `direct_io` enabled and one without it. Be sure to set `dropcacheonclose=true` if not using `direct_io`.
|
||||
Be sure to set `cache.files=partial|full|auto-full` or turn off `direct_io`. rtorrent and some other applications use [mmap](http://linux.die.net/man/2/mmap) to read and write to files and offer no fallback to traditional methods. FUSE does not currently support mmap while using `direct_io`. There may be a performance penalty on writes with `direct_io` off as well as the problem of double caching but it's the only way to get such applications to work. If the performance loss is too high for other apps you can mount mergerfs twice. Once with `direct_io` enabled and one without it. Be sure to set `dropcacheonclose=true` if not using `direct_io`.
|
||||
|
||||
|
||||
#### rtorrent fails with files >= 4GiB
|
||||
@@ -675,16 +772,21 @@ This is a kernel bug with mmap and FUSE on 32bit platforms. A fix should become
|
||||
https://marc.info/?l=linux-fsdevel&m=155550785230874&w=2
|
||||
|
||||
|
||||
#### Crashing on OpenVZ
|
||||
|
||||
There appears to be a bug in the OpenVZ kernel with regard to how it handles ioctl calls. It is making invalid requests which leads to a crash. As of 2019-12-10 there is a bug report filed with OpenVZ but it is not yet fixed.
|
||||
|
||||
|
||||
#### Plex doesn't work with mergerfs
|
||||
|
||||
It does. If you're trying to put Plex's config / metadata on mergerfs you have to leave `direct_io` off because Plex is using sqlite which apparently needs mmap. mmap doesn't work with `direct_io`. To fix this place the data elsewhere or disable `direct_io` (with `dropcacheonclose=true`).
|
||||
It does. If you're trying to put Plex's config / metadata on mergerfs you have to leave `direct_io` off because Plex is using sqlite3 which apparently needs mmap. mmap doesn't work with `direct_io`. To fix this place the data elsewhere or disable `direct_io` (with `dropcacheonclose=true`). Sqlite3 does not need mmap but the developer needs to fall back to standard IO if mmap fails.
|
||||
|
||||
If the issue is that scanning doesn't seem to pick up media then be sure to set `func.getattr=newest` as mentioned above.
|
||||
|
||||
|
||||
#### mmap performance is really bad
|
||||
|
||||
There [is a bug](https://lkml.org/lkml/2016/3/16/260) in caching which affects overall performance of mmap through FUSE in Linux 4.x kernels. It is fixed in [4.4.10 and 4.5.4](https://lkml.org/lkml/2016/5/11/59).
|
||||
There [is/was a bug](https://lkml.org/lkml/2016/3/16/260) in caching which affects overall performance of mmap through FUSE in Linux 4.x kernels. It is fixed in [4.4.10 and 4.5.4](https://lkml.org/lkml/2016/5/11/59).
|
||||
|
||||
|
||||
#### When a program tries to move or rename a file it fails
|
||||
@@ -700,7 +802,7 @@ Ideally the offending software would be fixed and it is recommended that if you
|
||||
|
||||
Workaround: Copy the file/directory and then remove the original rather than move.
|
||||
|
||||
This isn't an issue with Samba but some SMB clients. GVFS-fuse v1.20.3 and prior (found in Ubuntu 14.04 among others) failed to handle certain error codes correctly. Particularly **STATUS_NOT_SAME_DEVICE** which comes from the **EXDEV** which is returned by **rename** when the call is crossing mount points. When a program gets an **EXDEV** it needs to explicitly take an alternate action to accomplish it's goal. In the case of **mv** or similar it tries **rename** and on **EXDEV** falls back to a manual copying of data between the two locations and unlinking the source. In these older versions of GVFS-fuse if it received **EXDEV** it would translate that into **EIO**. This would cause **mv** or most any application attempting to move files around on that SMB share to fail with a IO error.
|
||||
This isn't an issue with Samba but some SMB clients. GVFS-fuse v1.20.3 and prior (found in Ubuntu 14.04 among others) failed to handle certain error codes correctly. Particularly **STATUS_NOT_SAME_DEVICE** which comes from the **EXDEV** which is returned by **rename** when the call is crossing mount points. When a program gets an **EXDEV** it needs to explicitly take an alternate action to accomplish its goal. In the case of **mv** or similar it tries **rename** and on **EXDEV** falls back to a manual copying of data between the two locations and unlinking the source. In these older versions of GVFS-fuse if it received **EXDEV** it would translate that into **EIO**. This would cause **mv** or most any application attempting to move files around on that SMB share to fail with a IO error.
|
||||
|
||||
[GVFS-fuse v1.22.0](https://bugzilla.gnome.org/show_bug.cgi?id=734568) and above fixed this issue but a large number of systems use the older release. On Ubuntu the version can be checked by issuing `apt-cache showpkg gvfs-fuse`. Most distros released in 2015 seem to have the updated release and will work fine but older systems may not. Upgrading gvfs-fuse or the distro in general will address the problem.
|
||||
|
||||
@@ -709,7 +811,7 @@ In Apple's MacOSX 10.9 they replaced Samba (client and server) with their own pr
|
||||
|
||||
#### Trashing files occasionally fails
|
||||
|
||||
This is the same issue as with Samba. `rename` returns `EXDEV` (in our case that will really only happen with path preserving policies like `epmfs`) and the software doesn't handle the situtation well. This is unfortunately a common failure of software which moves files around. The standard indicates that an implementation `MAY` choose to support non-user home directory trashing of files (which is a `MUST`). The implementation `MAY` also support "top directory trashes" which many probably do.
|
||||
This is the same issue as with Samba. `rename` returns `EXDEV` (in our case that will really only happen with path preserving policies like `epmfs`) and the software doesn't handle the situation well. This is unfortunately a common failure of software which moves files around. The standard indicates that an implementation `MAY` choose to support non-user home directory trashing of files (which is a `MUST`). The implementation `MAY` also support "top directory trashes" which many probably do.
|
||||
|
||||
To create a `$topdir/.Trash` directory as defined in the standard use the [mergerfs-tools](https://github.com/trapexit/mergerfs-tools) tool `mergerfs.mktrash`.
|
||||
|
||||
@@ -722,23 +824,31 @@ Make sure to use the `use_ino` option.
|
||||
|
||||
Due to the overhead of [getgroups/setgroups](http://linux.die.net/man/2/setgroups) mergerfs utilizes a cache. This cache is opportunistic and per thread. Each thread will query the supplemental groups for a user when that particular thread needs to change credentials and will keep that data for the lifetime of the thread. This means that if a user is added to a group it may not be picked up without the restart of mergerfs. However, since the high level FUSE API's (at least the standard version) thread pool dynamically grows and shrinks it's possible that over time a thread will be killed and later a new thread with no cache will start and query the new data.
|
||||
|
||||
The gid cache uses fixed storage to simplify the design and be compatible with older systems which may not have C++11 compilers. There is enough storage for 256 users' supplemental groups. Each user is allowed upto 32 supplemental groups. Linux >= 2.6.3 allows upto 65535 groups per user but most other *nixs allow far less. NFS allowing only 16. The system does handle overflow gracefully. If the user has more than 32 supplemental groups only the first 32 will be used. If more than 256 users are using the system when an uncached user is found it will evict an existing user's cache at random. So long as there aren't more than 256 active users this should be fine. If either value is too low for your needs you will have to modify `gidcache.hpp` to increase the values. Note that doing so will increase the memory needed by each thread.
|
||||
The gid cache uses fixed storage to simplify the design and be compatible with older systems which may not have C++11 compilers. There is enough storage for 256 users' supplemental groups. Each user is allowed up to 32 supplemental groups. Linux >= 2.6.3 allows up to 65535 groups per user but most other *nixs allow far less. NFS allowing only 16. The system does handle overflow gracefully. If the user has more than 32 supplemental groups only the first 32 will be used. If more than 256 users are using the system when an uncached user is found it will evict an existing user's cache at random. So long as there aren't more than 256 active users this should be fine. If either value is too low for your needs you will have to modify `gidcache.hpp` to increase the values. Note that doing so will increase the memory needed by each thread.
|
||||
|
||||
While not a bug some users have found when using containers that supplemental groups defined inside the container don't work properly with regard to permissions. This is expected as mergerfs lives outside the container and therefore is querying the host's group database. There might be a hack to work around this (make mergerfs read the /etc/group file in the container) but it is not yet implemented and would be limited to Linux and the /etc/group DB. Preferably users would mount in the host group file into the containers or use a standard shared user & groups technology like NIS or LDAP.
|
||||
|
||||
|
||||
#### mergerfs or libfuse crashing
|
||||
|
||||
**NOTE:** as of mergerfs 2.22.0 it includes the most recent version of libfuse (or requires libfuse-2.9.7) so any crash should be reported. For older releases continue reading...
|
||||
First... always upgrade to the latest version unless told otherwise.
|
||||
|
||||
If using mergerfs below 2.22.0:
|
||||
|
||||
If suddenly the mergerfs mount point disappears and `Transport endpoint is not connected` is returned when attempting to perform actions within the mount directory **and** the version of libfuse (use `mergerfs -v` to find the version) is older than `2.9.4` its likely due to a bug in libfuse. Affected versions of libfuse can be found in Debian Wheezy, Ubuntu Precise and others.
|
||||
|
||||
In order to fix this please install newer versions of libfuse. If using a Debian based distro (Debian,Ubuntu,Mint) you can likely just install newer versions of [libfuse](https://packages.debian.org/unstable/libfuse2) and [fuse](https://packages.debian.org/unstable/fuse) from the repo of a newer release.
|
||||
|
||||
If using mergerfs at or above 2.22.0:
|
||||
|
||||
First upgrade if possible, check the known bugs section, and contact trapexit.
|
||||
|
||||
|
||||
#### mergerfs appears to be crashing or exiting
|
||||
|
||||
There seems to be an issue with Linux version `4.9.0` and above in which an invalid message appears to be transmitted to libfuse (used by mergerfs) causing it to exit. No messages will be printed in any logs as its not a proper crash. Debugging of the issue is still ongoing and can be followed via the [fuse-devel thread](https://sourceforge.net/p/fuse/mailman/message/35662577).
|
||||
|
||||
#### mergerfs under heavy load and memory preasure leads to kernel panic
|
||||
#### mergerfs under heavy load and memory pressure leads to kernel panic
|
||||
|
||||
https://lkml.org/lkml/2016/9/14/527
|
||||
|
||||
@@ -787,14 +897,14 @@ NOTE: This is only relevant to mergerfs versions at or below v2.25.x and should
|
||||
|
||||
Not *really* a bug. The FUSE library will move files when asked to delete them as a way to deal with certain edge cases and then later delete that file when its clear the file is no longer needed. This however can lead to two issues. One is that these hidden files are noticed by `rm -rf` or `find` when scanning directories and they may try to remove them and they might have disappeared already. There is nothing *wrong* about this happening but it can be annoying. The second issue is that a directory might not be able to removed on account of the hidden file being still there.
|
||||
|
||||
Using the **hard_remove** option will make it so these temporary files are not used and files are deleted immedately. That has a side effect however. Files which are unlinked and then they are still used (in certain forms) will result in an error (ENOENT).
|
||||
Using the **hard_remove** option will make it so these temporary files are not used and files are deleted immediately. That has a side effect however. Files which are unlinked and then they are still used (in certain forms) will result in an error (ENOENT).
|
||||
|
||||
|
||||
# FAQ
|
||||
|
||||
#### How well does mergerfs scale? Is it "production ready?"
|
||||
|
||||
Users have reported running mergerfs on everything from a Raspberry Pi to dual socket Xeon systems with >20 cores. I'm aware of at least a few companies which use mergerfs in production. [Open Media Vault](https://www.openmediavault.org) includes mergerfs is it's sole solution for pooling drives.
|
||||
Users have reported running mergerfs on everything from a Raspberry Pi to dual socket Xeon systems with >20 cores. I'm aware of at least a few companies which use mergerfs in production. [Open Media Vault](https://www.openmediavault.org) includes mergerfs as its sole solution for pooling drives.
|
||||
|
||||
|
||||
#### Can mergerfs be used with drives which already have data / are in use?
|
||||
@@ -809,11 +919,40 @@ MergerFS is **not** a traditional filesystem. MergerFS is **not** RAID. It does
|
||||
See the previous question's answer.
|
||||
|
||||
|
||||
#### What policies should I use?
|
||||
|
||||
Unless you're doing something more niche the average user is probably best off using `mfs` for `category.create`. It will spread files out across your branches based on available space. You may want to use `lus` if you prefer a slightly different distribution of data if you have a mix of smaller and larger drives. Generally though `mfs`, `lus`, or even `rand` are good for the general use case. If you are starting with an imbalanced pool you can use the tool **mergerfs.balance** to redistribute files across the pool.
|
||||
|
||||
If you really wish to try to colocate files based on directory you can set `func.create` to `epmfs` or similar and `func.mkdir` to `rand` or `eprand` depending on if you just want to colocate generally or on specific branches. Either way the *need* to colocate is rare. For instance: if you wish to remove the drive regularly and want the data to predictably be on that drive or if you don't use backup at all and don't wish to replace that data piecemeal. In which case using path preservation can help but will require some manual attention. Colocating after the fact can be accomplished using the **mergerfs.consolidate** tool.
|
||||
|
||||
Ultimately there is no correct answer. It is a preference or based on some particular need. mergerfs is very easy to test and experiment with. I suggest creating a test setup and experimenting to get a sense of what you want.
|
||||
|
||||
The reason `mfs` is not the default `category.create` policy is historical. When/if a 3.X gets released it will be changed to minimize confusion people often have with path preserving policies.
|
||||
|
||||
|
||||
#### What settings should I use?
|
||||
|
||||
Depends on what features you want. Generally speaking there are no "wrong" settings. All settings are performance or feature related. The best bet is to read over the available options and choose what fits your situation. If something isn't clear from the documentation please reach out and the documentation will be improved.
|
||||
|
||||
That said, for the average person, the following should be fine:
|
||||
|
||||
`-o use_ino,cache.files=off,dropcacheonclose=true,allow_other,category.create=mfs`
|
||||
|
||||
|
||||
#### Why are all my files ending up on 1 drive?!
|
||||
|
||||
Did you start with empty drives? Did you explicitly configure a `category.create` policy?
|
||||
|
||||
The default create policy is `epmfs`. That is a path preserving algorithm. With such a policy for `mkdir` and `create` with a set of empty drives it will naturally select only 1 drive when the first directory is created. Anything, files or directories, created in that first directory will be placed on the same branch because it is preserving paths.
|
||||
|
||||
This catches a lot of new users off guard but changing the default would break the setup for many existing users. If you do not care about path preservation and wish your files to be spread across all your drives change to `mfs` or similar policy as described above.
|
||||
|
||||
|
||||
#### Do hard links work?
|
||||
|
||||
Yes. You need to use `use_ino` to support proper reporting of inodes.
|
||||
|
||||
What mergerfs does not do is fake hard links across branches. Read the section "rename & link" for how it.
|
||||
What mergerfs does not do is fake hard links across branches. Read the section "rename & link" for how it works.
|
||||
|
||||
|
||||
#### Does mergerfs support CoW / copy-on-write?
|
||||
@@ -823,7 +962,7 @@ Not in the sense of a filesystem like BTRFS or ZFS nor in the overlayfs or aufs
|
||||
|
||||
#### Why can't I see my files / directories?
|
||||
|
||||
It's almost always a permissions issue. Unlike mhddfs, which runs as root and attempts to access content as such, mergerfs always changes it's credentials to that of the caller. This means that if the user does not have access to a file or directory than neither will mergerfs. However, because mergerfs is creating a union of paths it may be able to read some files and directories on one drive but not another resulting in an incomplete set.
|
||||
It's almost always a permissions issue. Unlike mhddfs and unionfs-fuse, which runs as root and attempts to access content as such, mergerfs always changes its credentials to that of the caller. This means that if the user does not have access to a file or directory than neither will mergerfs. However, because mergerfs is creating a union of paths it may be able to read some files and directories on one drive but not another resulting in an incomplete set.
|
||||
|
||||
Whenever you run into a split permission issue (seeing some but not all files) try using [mergerfs.fsck](https://github.com/trapexit/mergerfs-tools) tool to check for and fix the mismatch. If you aren't seeing anything at all be sure that the basic permissions are correct. The user and group values are correct and that directories have their executable bit set. A common mistake by users new to Linux is to `chmod -R 644` when they should have `chmod -R u=rwX,go=rX`.
|
||||
|
||||
@@ -834,12 +973,17 @@ If using a network filesystem such as NFS, SMB, CIFS (Samba) be sure to pay clos
|
||||
|
||||
Are you using a path preserving policy? The default policy for file creation is `epmfs`. That means only the drives with the path preexisting will be considered when creating a file. If you don't care about where files and directories are created you likely shouldn't be using a path preserving policy and instead something like `mfs`.
|
||||
|
||||
This can be especially apparent when filling an empty pool from an external source. If you do want path preservation you'll need to perform the manual act of creating paths on the drives you want the data to land on before transfering your data. Setting `func.mkdir=epall` can simplify managing path perservation for `create`.
|
||||
This can be especially apparent when filling an empty pool from an external source. If you do want path preservation you'll need to perform the manual act of creating paths on the drives you want the data to land on before transferring your data. Setting `func.mkdir=epall` can simplify managing path preservation for `create`.
|
||||
|
||||
|
||||
#### Is my OS's libfuse needed for mergerfs to work?
|
||||
|
||||
No. Normally `mount.fuse` is needed to get mergerfs (or any FUSE filesystem to mount using the `mount` command but in vendoring the libfuse library the `mount.fuse` app has been renamed to `mount.mergerfs` meaning the filesystem type in `fstab` can simply be `mergerfs`.
|
||||
|
||||
|
||||
#### Why was libfuse embedded into mergerfs?
|
||||
|
||||
1. A significant number of users use mergerfs on distros with old versions of libfuse which have serious bugs. Requiring updated versions of libfuse on those distros isn't pratical (no package offered, user inexperience, etc.). The only practical way to provide a stable runtime on those systems was to "vendor" / embed the library into the project.
|
||||
1. A significant number of users use mergerfs on distros with old versions of libfuse which have serious bugs. Requiring updated versions of libfuse on those distros isn't practical (no package offered, user inexperience, etc.). The only practical way to provide a stable runtime on those systems was to "vendor" / embed the library into the project.
|
||||
2. mergerfs was written to use the high level API. There are a number of limitations in the HLAPI that make certain features difficult or impossible to implement. While some of these features could be patched into newer versions of libfuse without breaking the public API some of them would require hacky code to provide backwards compatibility. While it may still be worth working with upstream to address these issues in future versions, since the library needs to be vendored for stability and compatibility reasons it is preferable / easier to modify the API. Longer term the plan is to rewrite mergerfs to use the low level API.
|
||||
|
||||
|
||||
@@ -870,24 +1014,33 @@ While aufs can offer better peak performance mergerfs provides more configurabil
|
||||
|
||||
#### Why use mergerfs over unionfs?
|
||||
|
||||
UnionFS is more like aufs then mergerfs in that it offers overlay / CoW features. If you're just looking to create a union of drives and want flexibility in file/directory placement then mergerfs offers that whereas unionfs is more for overlaying RW filesystems over RO ones.
|
||||
UnionFS is more like aufs than mergerfs in that it offers overlay / CoW features. If you're just looking to create a union of drives and want flexibility in file/directory placement then mergerfs offers that whereas unionfs is more for overlaying RW filesystems over RO ones.
|
||||
|
||||
|
||||
#### Why use mergerfs over LVM/ZFS/BTRFS/RAID0 drive concatenation / striping?
|
||||
|
||||
With simple JBOD / drive concatenation / stripping / RAID0 a single drive failure will result in full pool failure. mergerfs performs a similar behavior without the possibility of catastrophic failure and the difficulties in recovery. Drives may fail however all other data will continue to be accessable.
|
||||
With simple JBOD / drive concatenation / stripping / RAID0 a single drive failure will result in full pool failure. mergerfs performs a similar function without the possibility of catastrophic failure and the difficulties in recovery. Drives may fail, however, all other data will continue to be accessible.
|
||||
|
||||
When combined with something like [SnapRaid](http://www.snapraid.it) and/or an offsite backup solution you can have the flexibilty of JBOD without the single point of failure.
|
||||
When combined with something like [SnapRaid](http://www.snapraid.it) and/or an offsite backup solution you can have the flexibility of JBOD without the single point of failure.
|
||||
|
||||
|
||||
#### Why use mergerfs over ZFS?
|
||||
|
||||
MergerFS is not intended to be a replacement for ZFS. MergerFS is intended to provide flexible pooling of arbitrary drives (local or remote), of arbitrary sizes, and arbitrary filesystems. For `write once, read many` usecases such as bulk media storage. Where data integrity and backup is managed in other ways. In that situation ZFS can introduce major maintance and cost burdens as described [here](http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html).
|
||||
MergerFS is not intended to be a replacement for ZFS. MergerFS is intended to provide flexible pooling of arbitrary drives (local or remote), of arbitrary sizes, and arbitrary filesystems. For `write once, read many` usecases such as bulk media storage. Where data integrity and backup is managed in other ways. In that situation ZFS can introduce a number of costs and limitations as described [here](http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html), [here](https://markmcb.com/2020/01/07/five-years-of-btrfs/), and [here](https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSWhyNoRealReshaping).
|
||||
|
||||
|
||||
#### 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 kill performance. If it is a lightly used SQLITE database then it may be fine but you'll need to test.
|
||||
|
||||
* VM images: For the same reasons as databases. VM images are accessed very aggressively and mergerfs will introduce too much latency (if it works at all).
|
||||
|
||||
* 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.
|
||||
|
||||
|
||||
#### Can drives be written to directly? Outside of mergerfs while pooled?
|
||||
|
||||
Yes, however its not recommended to use the same file from within the pool and from without at the same time. Especially if using caching of any kind (cache.entry, cache.attr, ac_attr_timeout, cache.negative_entry, auto_cache, kernel_cache).
|
||||
Yes, however its not recommended to use the same file from within the pool and from without at the same time. Especially if using caching of any kind (cache.files, cache.entry, cache.attr, cache.negative_entry, cache.symlinks, cache.readdir, etc.).
|
||||
|
||||
|
||||
#### 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?
|
||||
@@ -917,9 +1070,9 @@ Yes. While some users have reported problems it appears to always be related to
|
||||
|
||||
#### How are inodes calculated?
|
||||
|
||||
mergerfs-inode = (original-inode | (device-id << 32))
|
||||
https://github.com/trapexit/mergerfs/blob/master/src/fs_inode.hpp
|
||||
|
||||
While `ino_t` is 64 bits only a few filesystems use more than 32. Similarly, while `dev_t` is also 64 bits it was traditionally 16 bits. Bitwise or'ing them together should work most of the time. While totally unique inodes are preferred the overhead which would be needed does not seem to outweighted by the benefits.
|
||||
Originally tried to simply OR st_ino and (st_dev << 32) for 64bit systems. After a number of years someone finally ran into a collision that lead to some problems. Traditionally `dev_t` was 16bit and `ino_t` was 32bit so merging into one 64bit value worked but with both types being able to be up to 64bit that is no longer as simple. A proper hash seems like the best compromise. While totally unique inodes are preferred the overhead which would be needed does not seem to be outweighed by the benefits.
|
||||
|
||||
While atypical, yes, inodes can be reused and not refer to the same file. The internal id used to reference a file in FUSE is different from the inode value presented. The former is the `nodeid` and is actually a tuple of (nodeid,generation). That tuple is not user facing. The inode is merely metadata passed through the kernel and found using the `stat` family of calls or `readdir`.
|
||||
|
||||
@@ -936,10 +1089,11 @@ Note that this does *not* affect the inode that libfuse
|
||||
and the kernel use internally (also called the "nodeid").
|
||||
```
|
||||
|
||||
Generally collision, if it occurs, shouldn't be a problem. You can turn off the calculation by not using `use_ino`. In the future it might be worth creating different strategies for users to select from.
|
||||
|
||||
#### I notice massive slowdowns of writes over NFS
|
||||
|
||||
Due to how NFS works and interacts with FUSE when not using `direct_io` its possible that a getxattr for `security.capability` will be issued prior to any write. This will usually result in a massive slowdown for writes. Using `direct_io` will keep this from happening (and generally good to enable unless you need the features it disables) but the `security_capability` option can also help by short circuiting the call and returning `ENOATTR`.
|
||||
Due to how NFS works and interacts with FUSE when not using `cache.files=off` or `direct_io` its possible that a getxattr for `security.capability` will be issued prior to any write. This will usually result in a massive slowdown for writes. Using `cache.files=off` or `direct_io` will keep this from happening (and generally good to enable unless you need the features it disables) but the `security_capability` option can also help by short circuiting the call and returning `ENOATTR`.
|
||||
|
||||
You could also set `xattr` to `noattr` or `nosys` to short circuit or stop all xattr requests.
|
||||
|
||||
@@ -962,20 +1116,6 @@ In Linux setreuid syscalls apply only to the thread. GLIBC hides this away by us
|
||||
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.
|
||||
|
||||
|
||||
# PERFORMANCE TWEAKING
|
||||
|
||||
* try adding (or removing) `direct_io`
|
||||
* try adding (or removing) `auto_cache`
|
||||
* try adding (or removing) `kernel_cache`
|
||||
* try adding (or removing) `splice_move`, `splice_read`, and `splice_write`
|
||||
* try increasing cache timeouts `cache.attr`, `cache.entry`, `cache.negative_entry`
|
||||
* try changing the number of worker threads
|
||||
* try disabling `security_capability` or `xattr`
|
||||
* test theoretical performance using `nullrw` or mounting a ram disk
|
||||
* use `symlinkify` if your data is largely static and you need native speed reads
|
||||
* use lvm and lvm cache to place a SSD in front of your HDDs (howto coming)
|
||||
|
||||
|
||||
# SUPPORT
|
||||
|
||||
Filesystems are very complex and difficult to debug. mergerfs, while being just a proxy of sorts, is also very difficult to debug given the large number of possible settings it can have itself and the massive number of environments it can run in. When reporting on a suspected issue **please, please** include as much of the below information as possible otherwise it will be difficult or impossible to diagnose. Also please make sure to read all of the above documentation as it includes nearly every known system or user issue previously encountered.
|
||||
@@ -1010,12 +1150,15 @@ Filesystems are very complex and difficult to debug. mergerfs, while being just
|
||||
This software is free to use and released under a very liberal license. That said if you like this software and would like to support its development donations are welcome.
|
||||
|
||||
* PayPal: https://paypal.me/trapexit
|
||||
* GitHub Sponsors: https://github.com/sponsors/trapexit
|
||||
* Patreon: https://www.patreon.com/trapexit
|
||||
* SubscribeStar: https://www.subscribestar.com/trapexit
|
||||
* Ko-Fi: https://ko-fi.com/trapexit
|
||||
* Bitcoin (BTC): 12CdMhEPQVmjz3SSynkAEuD5q9JmhTDCZA
|
||||
* Bitcoin Cash (BCH): 1AjPqZZhu7GVEs6JFPjHmtsvmDL4euzMzp
|
||||
* Ethereum (ETH): 0x09A166B11fCC127324C7fc5f1B572255b3046E94
|
||||
* Litecoin (LTC): LXAsq6yc6zYU3EbcqyWtHBrH1Ypx4GjUjm
|
||||
* ZCoin (XZC): a8L5Vz35KdCQe7Y7urK2pcCGau7JsqZ5Gw
|
||||
|
||||
|
||||
# LINKS
|
||||
|
||||
+46
-26
@@ -1,5 +1,4 @@
|
||||
VERSION = "2.9.7-mergerfs_2.26.0"
|
||||
OPT = -O2
|
||||
VERSION = 2.9.7-mergerfs_2.29.0
|
||||
|
||||
ifeq ($(DEBUG),1)
|
||||
DEBUG_FLAGS := -g
|
||||
@@ -7,9 +6,20 @@ else
|
||||
DEBUG_FLAGS :=
|
||||
endif
|
||||
|
||||
DESTDIR = "/"
|
||||
BINDIR = "/bin"
|
||||
SBINDIR = "/sbin"
|
||||
DESTDIR =
|
||||
PREFIX = /usr/local
|
||||
EXEC_PREFIX = $(PREFIX)
|
||||
DATAROOTDIR = $(PREFIX)/share
|
||||
DATADIR = $(DATAROOTDIR)
|
||||
BINDIR = $(EXEC_PREFIX)/bin
|
||||
SBINDIR = $(EXEC_PREFIX)/sbin
|
||||
MANDIR = $(DATAROOTDIR)/man
|
||||
MAN1DIR = $(MANDIR)/man1
|
||||
|
||||
INSTALLBINDIR = $(DESTDIR)$(BINDIR)
|
||||
INSTALLSBINDIR = $(DESTDIR)$(SBINDIR)
|
||||
INSTALLMAN1DIR = $(DESTDIR)$(MAN1DIR)
|
||||
|
||||
SRC = \
|
||||
lib/buffer.c \
|
||||
lib/cuse_lowlevel.c \
|
||||
@@ -27,24 +37,26 @@ SRC = \
|
||||
OBJS = $(SRC:lib/%.c=build/%.o)
|
||||
DEPS = $(SRC:lib/%.c=build/%.d)
|
||||
|
||||
CFLAGS += \
|
||||
$(OPT) \
|
||||
CFLAGS = \
|
||||
-O2 \
|
||||
$(DEBUG_FLAGS) \
|
||||
-Wall \
|
||||
-pipe \
|
||||
'-D_FILE_OFFSET_BITS=64' \
|
||||
'-D_REENTRANT' \
|
||||
'-DFUSE_USE_VERSION=29' \
|
||||
'-DFUSERMOUNT_DIR="/usr/local/bin"' \
|
||||
'-DPACKAGE_VERSION=$(VERSION)' \
|
||||
-MMD
|
||||
FUSERMOUNT_DIR = $(BINDIR)
|
||||
FUSE_FLAGS = \
|
||||
-Iinclude \
|
||||
-Ibuild \
|
||||
-MMD
|
||||
LDFLAGS += \
|
||||
-D_REENTRANT \
|
||||
-D_FILE_OFFSET_BITS=64 \
|
||||
-DFUSE_USE_VERSION=29 \
|
||||
-DPACKAGE_VERSION=\"$(VERSION)\" \
|
||||
-DFUSERMOUNT_DIR=\"$(FUSERMOUNT_DIR)\"
|
||||
LDFLAGS = \
|
||||
-lrt \
|
||||
-pthread
|
||||
|
||||
all: build/libfuse.a
|
||||
all: build/libfuse.a mount.mergerfs mergerfs-fusermount
|
||||
|
||||
build/config.h: build/stamp
|
||||
ecfd/build | tee build/config.h
|
||||
@@ -59,26 +71,34 @@ objects: build/config.h
|
||||
build/libfuse.a: objects
|
||||
ar rcs build/libfuse.a $(OBJS)
|
||||
|
||||
build/mergerfs-mount: build/config.h util/fusermount.c lib/mount_util.c
|
||||
$(CC) $(CFLAGS) -Ilib -o mergerfs-mount util/fusermount.c lib/mount_util.c
|
||||
build/mergerfs-fusermount: build/config.h util/fusermount.c lib/mount_util.c
|
||||
$(CC) $(CFLAGS) $(FUSE_FLAGS) -Ilib -o build/mergerfs-fusermount util/fusermount.c lib/mount_util.c
|
||||
|
||||
build/mount.mergerfs: build/libfuse.a util/mount.fuse.c
|
||||
$(CC) $(CFLAGS) -o mount.mergerfs util/mount.fuse.c build/libfuse.a $(LDFLAGS)
|
||||
mergerfs-fusermount: build/mergerfs-fusermount
|
||||
|
||||
build/mount.mergerfs: build/libfuse.a util/mount.mergerfs.c
|
||||
$(CC) $(CFLAGS) $(FUSE_FLAGS) -o build/mount.mergerfs util/mount.mergerfs.c build/libfuse.a $(LDFLAGS)
|
||||
|
||||
mount.mergerfs: build/mount.mergerfs
|
||||
|
||||
build/%.o: lib/%.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
$(CC) $(CFLAGS) $(FUSE_FLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
||||
distclean: clean
|
||||
|
||||
install: build/mergerfs-mount build/mount.mergerfs
|
||||
install -D build/mergerfs-mount "$(DESTDIR)$(BINDIR)/mergerfs-mount"
|
||||
chown root:root "$(DESTDIR)$(BINDIR)/mergerfs-mount"
|
||||
chmod u+s "$(DESTDIR)$(BINDIR)/mergerfs-mount"
|
||||
install -D build/mount.mergerfs "$(DESTDIR)$(SBINDIR)/mount.mergerfs"
|
||||
strip:
|
||||
strip --strip-all build/mount.mergerfs
|
||||
strip --strip-all build/mergerfs-fusermount
|
||||
|
||||
.PHONY: objects
|
||||
install: mergerfs-fusermount mount.mergerfs strip
|
||||
install -D build/mergerfs-fusermount "$(INSTALLBINDIR)/mergerfs-fusermount"
|
||||
install -D build/mount.mergerfs "$(INSTALLSBINDIR)/mount.mergerfs"
|
||||
chown root:root "$(INSTALLBINDIR)/mergerfs-fusermount"
|
||||
chmod u+s "$(INSTALLBINDIR)/mergerfs-fusermount"
|
||||
|
||||
.PHONY: objects strip
|
||||
|
||||
-include $(DEPS)
|
||||
|
||||
+34
-1
@@ -129,7 +129,7 @@ struct fuse_operations {
|
||||
* while open. Helps manage the fact the kernel usually does
|
||||
* not send fh with getattr requests.
|
||||
*/
|
||||
int (*prepare_hide)(const char *name_, uint64_t *fh_, int type_);
|
||||
int (*prepare_hide)(const char *name_, uint64_t *fh_);
|
||||
int (*free_hide)(const uint64_t fh_);
|
||||
|
||||
/** Remove a file */
|
||||
@@ -608,6 +608,30 @@ struct fuse_operations {
|
||||
*/
|
||||
int (*fallocate) (const char *, int, off_t, off_t,
|
||||
struct fuse_file_info *);
|
||||
|
||||
/**
|
||||
* 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 char *path_in,
|
||||
struct fuse_file_info *fi_in,
|
||||
off_t offset_in,
|
||||
const char *path_out,
|
||||
struct fuse_file_info *fi_out,
|
||||
off_t offset_out,
|
||||
size_t size,
|
||||
int flags);
|
||||
};
|
||||
|
||||
/** Extra context that may be needed by some filesystems
|
||||
@@ -921,6 +945,15 @@ int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
|
||||
void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn);
|
||||
void fuse_fs_destroy(struct fuse_fs *fs);
|
||||
|
||||
int fuse_fs_prepare_hide(struct fuse_fs *fs, const char *path, uint64_t *fh);
|
||||
int fuse_fs_free_hide(struct fuse_fs *fs, uint64_t fh);
|
||||
ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs,
|
||||
const char *path_in,
|
||||
struct fuse_file_info *fi_in, off_t off_in,
|
||||
const char *path_out,
|
||||
struct fuse_file_info *fi_out, off_t off_out,
|
||||
size_t len, int flags);
|
||||
|
||||
int fuse_notify_poll(struct fuse_pollhandle *ph);
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,9 @@
|
||||
#error Please add -D_FILE_OFFSET_BITS=64 to your compile flags!
|
||||
#endif
|
||||
|
||||
#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
|
||||
#define FUSE_MAX_MAX_PAGES 256
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -42,49 +45,53 @@ extern "C" {
|
||||
*
|
||||
* Changed in version 2.5
|
||||
*/
|
||||
struct fuse_file_info {
|
||||
/** Open flags. Available in open() and release() */
|
||||
int flags;
|
||||
struct
|
||||
fuse_file_info
|
||||
{
|
||||
/** Open flags. Available in open() and release() */
|
||||
int flags;
|
||||
|
||||
/** Old file handle, don't use */
|
||||
unsigned long fh_old;
|
||||
/** In case of a write operation indicates if this was caused by a
|
||||
writepage */
|
||||
uint32_t writepage : 1;
|
||||
|
||||
/** In case of a write operation indicates if this was caused by a
|
||||
writepage */
|
||||
int writepage;
|
||||
/** Can be filled in by open, to use direct I/O on this file.
|
||||
Introduced in version 2.4 */
|
||||
uint32_t direct_io : 1;
|
||||
|
||||
/** Can be filled in by open, to use direct I/O on this file.
|
||||
Introduced in version 2.4 */
|
||||
unsigned int direct_io : 1;
|
||||
/** Can be filled in by open, to indicate, that cached file data
|
||||
need not be invalidated. Introduced in version 2.4 */
|
||||
uint32_t keep_cache : 1;
|
||||
|
||||
/** Can be filled in by open, to indicate, that cached file data
|
||||
need not be invalidated. Introduced in version 2.4 */
|
||||
unsigned int keep_cache : 1;
|
||||
/** Indicates a flush operation. Set in flush operation, also
|
||||
maybe set in highlevel lock operation and lowlevel release
|
||||
operation. Introduced in version 2.6 */
|
||||
uint32_t flush : 1;
|
||||
|
||||
/** Indicates a flush operation. Set in flush operation, also
|
||||
maybe set in highlevel lock operation and lowlevel release
|
||||
operation. Introduced in version 2.6 */
|
||||
unsigned int flush : 1;
|
||||
/** Can be filled in by open, to indicate that the file is not
|
||||
seekable. Introduced in version 2.8 */
|
||||
uint32_t nonseekable : 1;
|
||||
|
||||
/** Can be filled in by open, to indicate that the file is not
|
||||
seekable. Introduced in version 2.8 */
|
||||
unsigned int nonseekable : 1;
|
||||
/* Indicates that flock locks for this file should be
|
||||
released. If set, lock_owner shall contain a valid value.
|
||||
May only be set in ->release(). Introduced in version
|
||||
2.9 */
|
||||
uint32_t flock_release : 1;
|
||||
|
||||
/* Indicates that flock locks for this file should be
|
||||
released. If set, lock_owner shall contain a valid value.
|
||||
May only be set in ->release(). Introduced in version
|
||||
2.9 */
|
||||
unsigned int flock_release : 1;
|
||||
/* Requests the kernel to cache entries returned by readdir */
|
||||
uint32_t cache_readdir : 1;
|
||||
|
||||
/** Padding. Do not use*/
|
||||
unsigned int padding : 27;
|
||||
uint32_t auto_cache : 1;
|
||||
|
||||
/** File handle. May be filled in by filesystem in open().
|
||||
Available in all other file operations */
|
||||
uint64_t fh;
|
||||
/** Padding. Do not use*/
|
||||
uint32_t padding : 24;
|
||||
|
||||
/** Lock owner id. Available in locking operations and flush */
|
||||
uint64_t lock_owner;
|
||||
/** File handle. May be filled in by filesystem in open().
|
||||
Available in all other file operations */
|
||||
uint64_t fh;
|
||||
|
||||
/** Lock owner id. Available in locking operations and flush */
|
||||
uint64_t lock_owner;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -100,18 +107,26 @@ struct fuse_file_info {
|
||||
* FUSE_CAP_SPLICE_MOVE: ability to move data to the fuse device with splice()
|
||||
* FUSE_CAP_SPLICE_READ: ability to use splice() to read from the fuse device
|
||||
* FUSE_CAP_IOCTL_DIR: ioctl support on directories
|
||||
* FUSE_CAP_CACHE_SYMLINKS: cache READLINK responses
|
||||
*/
|
||||
#define FUSE_CAP_ASYNC_READ (1 << 0)
|
||||
#define FUSE_CAP_POSIX_LOCKS (1 << 1)
|
||||
#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
|
||||
#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
|
||||
#define FUSE_CAP_BIG_WRITES (1 << 5)
|
||||
#define FUSE_CAP_DONT_MASK (1 << 6)
|
||||
#define FUSE_CAP_SPLICE_WRITE (1 << 7)
|
||||
#define FUSE_CAP_SPLICE_MOVE (1 << 8)
|
||||
#define FUSE_CAP_SPLICE_READ (1 << 9)
|
||||
#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
|
||||
#define FUSE_CAP_IOCTL_DIR (1 << 11)
|
||||
#define FUSE_CAP_ASYNC_READ (1 << 0)
|
||||
#define FUSE_CAP_POSIX_LOCKS (1 << 1)
|
||||
#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
|
||||
#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
|
||||
#define FUSE_CAP_BIG_WRITES (1 << 5)
|
||||
#define FUSE_CAP_DONT_MASK (1 << 6)
|
||||
#define FUSE_CAP_SPLICE_WRITE (1 << 7)
|
||||
#define FUSE_CAP_SPLICE_MOVE (1 << 8)
|
||||
#define FUSE_CAP_SPLICE_READ (1 << 9)
|
||||
#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
|
||||
#define FUSE_CAP_IOCTL_DIR (1 << 11)
|
||||
#define FUSE_CAP_ASYNC_DIO (1 << 15)
|
||||
#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
|
||||
#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
|
||||
#define FUSE_CAP_POSIX_ACL (1 << 19)
|
||||
#define FUSE_CAP_CACHE_SYMLINKS (1 << 20)
|
||||
#define FUSE_CAP_MAX_PAGES (1 << 21)
|
||||
|
||||
|
||||
/**
|
||||
* Ioctl flags
|
||||
@@ -148,11 +163,6 @@ struct fuse_conn_info {
|
||||
*/
|
||||
unsigned proto_minor;
|
||||
|
||||
/**
|
||||
* Is asynchronous read supported (read-write)
|
||||
*/
|
||||
unsigned async_read;
|
||||
|
||||
/**
|
||||
* Maximum size of the write buffer
|
||||
*/
|
||||
@@ -183,10 +193,15 @@ struct fuse_conn_info {
|
||||
*/
|
||||
unsigned congestion_threshold;
|
||||
|
||||
/**
|
||||
* Max pages
|
||||
*/
|
||||
uint16_t max_pages;
|
||||
|
||||
/**
|
||||
* For future use.
|
||||
*/
|
||||
unsigned reserved[23];
|
||||
unsigned reserved[22];
|
||||
};
|
||||
|
||||
struct fuse_session;
|
||||
@@ -203,7 +218,8 @@ struct fuse_pollhandle;
|
||||
* @param args argument vector
|
||||
* @return the communication channel on success, NULL on failure
|
||||
*/
|
||||
struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args);
|
||||
struct fuse_chan *fuse_mount(const char *mountpoint,
|
||||
struct fuse_args *args);
|
||||
|
||||
/**
|
||||
* Umount a FUSE mountpoint
|
||||
|
||||
@@ -38,12 +38,50 @@
|
||||
*
|
||||
* Protocol changelog:
|
||||
*
|
||||
* 7.1:
|
||||
* - add the following messages:
|
||||
* FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
|
||||
* FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
|
||||
* FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
|
||||
* FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
|
||||
* FUSE_RELEASEDIR
|
||||
* - add padding to messages to accommodate 32-bit servers on 64-bit kernels
|
||||
*
|
||||
* 7.2:
|
||||
* - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
|
||||
* - add FUSE_FSYNCDIR message
|
||||
*
|
||||
* 7.3:
|
||||
* - add FUSE_ACCESS message
|
||||
* - add FUSE_CREATE message
|
||||
* - add filehandle to fuse_setattr_in
|
||||
*
|
||||
* 7.4:
|
||||
* - add frsize to fuse_kstatfs
|
||||
* - clean up request size limit checking
|
||||
*
|
||||
* 7.5:
|
||||
* - add flags and max_write to fuse_init_out
|
||||
*
|
||||
* 7.6:
|
||||
* - add max_readahead to fuse_init_in and fuse_init_out
|
||||
*
|
||||
* 7.7:
|
||||
* - add FUSE_INTERRUPT message
|
||||
* - add POSIX file lock support
|
||||
*
|
||||
* 7.8:
|
||||
* - add lock_owner and flags fields to fuse_release_in
|
||||
* - add FUSE_BMAP message
|
||||
* - add FUSE_DESTROY message
|
||||
*
|
||||
* 7.9:
|
||||
* - new fuse_getattr_in input argument of GETATTR
|
||||
* - add lk_flags in fuse_lk_in
|
||||
* - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
|
||||
* - add blksize field to fuse_attr
|
||||
* - add file flags field to fuse_read_in and fuse_write_in
|
||||
* - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
|
||||
*
|
||||
* 7.10
|
||||
* - add nonseekable open flag
|
||||
@@ -54,7 +92,7 @@
|
||||
* - add POLL message and NOTIFY_POLL notification
|
||||
*
|
||||
* 7.12
|
||||
* - add umask flag to input argument of open, mknod and mkdir
|
||||
* - add umask flag to input argument of create, mknod and mkdir
|
||||
* - add notification messages for invalidation of inodes and
|
||||
* directory entries
|
||||
*
|
||||
@@ -119,6 +157,21 @@
|
||||
*
|
||||
* 7.28
|
||||
* - add FUSE_COPY_FILE_RANGE
|
||||
* - add FOPEN_CACHE_DIR
|
||||
* - add FUSE_MAX_PAGES, add max_pages to init_out
|
||||
* - add FUSE_CACHE_SYMLINKS
|
||||
*
|
||||
* 7.29
|
||||
* - add FUSE_NO_OPENDIR_SUPPORT flag
|
||||
*
|
||||
* 7.30
|
||||
* - add FUSE_EXPLICIT_INVAL_DATA
|
||||
* - add FUSE_IOCTL_COMPAT_X32
|
||||
*
|
||||
* 7.31
|
||||
* - add FUSE_WRITE_KILL_PRIV flag
|
||||
* - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
|
||||
* - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_FUSE_H
|
||||
@@ -154,7 +207,7 @@
|
||||
#define FUSE_KERNEL_VERSION 7
|
||||
|
||||
/** Minor version number of this interface */
|
||||
#define FUSE_KERNEL_MINOR_VERSION 27
|
||||
#define FUSE_KERNEL_MINOR_VERSION 31
|
||||
|
||||
/** The node ID of the root inode */
|
||||
#define FUSE_ROOT_ID 1
|
||||
@@ -222,10 +275,14 @@ struct fuse_file_lock {
|
||||
* FOPEN_DIRECT_IO: bypass page cache for this open file
|
||||
* FOPEN_KEEP_CACHE: don't invalidate the data cache on open
|
||||
* FOPEN_NONSEEKABLE: the file is not seekable
|
||||
* FOPEN_CACHE_DIR: allow caching this directory
|
||||
* FOPEN_STREAM: the file is stream-like (no file position at all)
|
||||
*/
|
||||
#define FOPEN_DIRECT_IO (1 << 0)
|
||||
#define FOPEN_KEEP_CACHE (1 << 1)
|
||||
#define FOPEN_NONSEEKABLE (1 << 2)
|
||||
#define FOPEN_CACHE_DIR (1 << 3)
|
||||
#define FOPEN_STREAM (1 << 4)
|
||||
|
||||
/**
|
||||
* INIT request/reply flags
|
||||
@@ -252,6 +309,11 @@ struct fuse_file_lock {
|
||||
* FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc
|
||||
* FUSE_POSIX_ACL: filesystem supports posix acls
|
||||
* FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED
|
||||
* FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages
|
||||
* FUSE_CACHE_SYMLINKS: cache READLINK responses
|
||||
* FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir
|
||||
* FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request
|
||||
* FUSE_MAP_ALIGNMENT: map_alignment field is valid
|
||||
*/
|
||||
#define FUSE_ASYNC_READ (1 << 0)
|
||||
#define FUSE_POSIX_LOCKS (1 << 1)
|
||||
@@ -275,6 +337,11 @@ struct fuse_file_lock {
|
||||
#define FUSE_HANDLE_KILLPRIV (1 << 19)
|
||||
#define FUSE_POSIX_ACL (1 << 20)
|
||||
#define FUSE_ABORT_ERROR (1 << 21)
|
||||
#define FUSE_MAX_PAGES (1 << 22)
|
||||
#define FUSE_CACHE_SYMLINKS (1 << 23)
|
||||
#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
|
||||
#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
|
||||
#define FUSE_MAP_ALIGNMENT (1 << 26)
|
||||
|
||||
/**
|
||||
* CUSE INIT request/reply flags
|
||||
@@ -304,9 +371,11 @@ struct fuse_file_lock {
|
||||
*
|
||||
* FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed
|
||||
* FUSE_WRITE_LOCKOWNER: lock_owner field is valid
|
||||
* FUSE_WRITE_KILL_PRIV: kill suid and sgid bits
|
||||
*/
|
||||
#define FUSE_WRITE_CACHE (1 << 0)
|
||||
#define FUSE_WRITE_LOCKOWNER (1 << 1)
|
||||
#define FUSE_WRITE_KILL_PRIV (1 << 2)
|
||||
|
||||
/**
|
||||
* Read flags
|
||||
@@ -321,6 +390,7 @@ struct fuse_file_lock {
|
||||
* FUSE_IOCTL_RETRY: retry with new iovecs
|
||||
* FUSE_IOCTL_32BIT: 32bit ioctl
|
||||
* FUSE_IOCTL_DIR: is a directory
|
||||
* FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t)
|
||||
*
|
||||
* FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
|
||||
*/
|
||||
@@ -329,6 +399,7 @@ struct fuse_file_lock {
|
||||
#define FUSE_IOCTL_RETRY (1 << 2)
|
||||
#define FUSE_IOCTL_32BIT (1 << 3)
|
||||
#define FUSE_IOCTL_DIR (1 << 4)
|
||||
#define FUSE_IOCTL_COMPAT_X32 (1 << 5)
|
||||
|
||||
#define FUSE_IOCTL_MAX_IOV 256
|
||||
|
||||
@@ -339,6 +410,13 @@ struct fuse_file_lock {
|
||||
*/
|
||||
#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
|
||||
|
||||
/**
|
||||
* Fsync flags
|
||||
*
|
||||
* FUSE_FSYNC_FDATASYNC: Sync data only, not metadata
|
||||
*/
|
||||
#define FUSE_FSYNC_FDATASYNC (1 << 0)
|
||||
|
||||
enum fuse_opcode {
|
||||
FUSE_LOOKUP = 1,
|
||||
FUSE_FORGET = 2, /* no reply */
|
||||
@@ -385,9 +463,15 @@ enum fuse_opcode {
|
||||
FUSE_RENAME2 = 45,
|
||||
FUSE_LSEEK = 46,
|
||||
FUSE_COPY_FILE_RANGE = 47,
|
||||
FUSE_SETUPMAPPING = 48,
|
||||
FUSE_REMOVEMAPPING = 49,
|
||||
|
||||
/* CUSE specific operations */
|
||||
CUSE_INIT = 4096,
|
||||
|
||||
/* Reserved opcodes: helpful to detect structure endian-ness */
|
||||
CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
|
||||
FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
|
||||
};
|
||||
|
||||
enum fuse_notify_code {
|
||||
@@ -614,7 +698,9 @@ struct fuse_init_out {
|
||||
uint16_t congestion_threshold;
|
||||
uint32_t max_write;
|
||||
uint32_t time_gran;
|
||||
uint32_t unused[9];
|
||||
uint16_t max_pages;
|
||||
uint16_t map_alignment;
|
||||
uint32_t unused[8];
|
||||
};
|
||||
|
||||
#define CUSE_INIT_INFO_MAX 4096
|
||||
|
||||
@@ -25,12 +25,13 @@
|
||||
|
||||
#include "fuse_common.h"
|
||||
|
||||
#include <utime.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <utime.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -44,7 +45,7 @@ extern "C" {
|
||||
#define FUSE_ROOT_ID 1
|
||||
|
||||
/** Inode number type */
|
||||
typedef unsigned long fuse_ino_t;
|
||||
typedef uint64_t fuse_ino_t;
|
||||
|
||||
/** Request pointer type */
|
||||
typedef struct fuse_req *fuse_req_t;
|
||||
@@ -88,7 +89,8 @@ struct fuse_entry_param {
|
||||
* it as an error.
|
||||
*
|
||||
*/
|
||||
unsigned long generation;
|
||||
uint64_t generation;
|
||||
|
||||
|
||||
/** Inode attributes.
|
||||
*
|
||||
@@ -122,20 +124,10 @@ struct fuse_ctx {
|
||||
};
|
||||
|
||||
struct fuse_forget_data {
|
||||
uint64_t ino;
|
||||
fuse_ino_t ino;
|
||||
uint64_t nlookup;
|
||||
};
|
||||
|
||||
/* 'to_set' flags in setattr */
|
||||
#define FUSE_SET_ATTR_MODE (1 << 0)
|
||||
#define FUSE_SET_ATTR_UID (1 << 1)
|
||||
#define FUSE_SET_ATTR_GID (1 << 2)
|
||||
#define FUSE_SET_ATTR_SIZE (1 << 3)
|
||||
#define FUSE_SET_ATTR_ATIME (1 << 4)
|
||||
#define FUSE_SET_ATTR_MTIME (1 << 5)
|
||||
#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
|
||||
#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
|
||||
|
||||
/* ----------------------------------------------------------- *
|
||||
* Request methods and replies *
|
||||
* ----------------------------------------------------------- */
|
||||
@@ -233,7 +225,7 @@ struct fuse_lowlevel_ops {
|
||||
* @param ino the inode number
|
||||
* @param nlookup the number of lookups to forget
|
||||
*/
|
||||
void (*forget) (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup);
|
||||
void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
|
||||
|
||||
/**
|
||||
* Get file attributes
|
||||
@@ -1021,6 +1013,52 @@ struct fuse_lowlevel_ops {
|
||||
*/
|
||||
void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
|
||||
off_t offset, off_t length, struct fuse_file_info *fi);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* If this request is answered with an error code of ENOSYS, this is
|
||||
* treated as a permanent failure with error code EOPNOTSUPP,
|
||||
* i.e. all
|
||||
* future copy_file_range() requests will fail with EOPNOTSUPP
|
||||
* without
|
||||
* being send to the filesystem process.
|
||||
*
|
||||
* Valid replies:
|
||||
* fuse_reply_write
|
||||
* fuse_reply_err
|
||||
*
|
||||
* @param req request handle
|
||||
* @param ino_in the inode number of the source file
|
||||
* @param off_in starting point from were the data should be read
|
||||
* @param fi_in file information of the source file
|
||||
* @param ino_out the inode number of the destination file
|
||||
* @param off_out starting point where the data should be written
|
||||
* @param fi_out file information of the destination file
|
||||
* @param len maximum size of the data to copy
|
||||
* @param flags passed along with the copy_file_range() syscall
|
||||
*/
|
||||
void (*copy_file_range)(fuse_req_t req,
|
||||
fuse_ino_t ino_in,
|
||||
off_t off_in,
|
||||
struct fuse_file_info *fi_in,
|
||||
fuse_ino_t ino_out,
|
||||
off_t off_out,
|
||||
struct fuse_file_info *fi_out,
|
||||
size_t len,
|
||||
int flags);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
size_t fuse_buf_size(const struct fuse_bufvec *bufv)
|
||||
{
|
||||
size_t i;
|
||||
size_t size = 0;
|
||||
size_t size = 0;
|
||||
|
||||
for (i = 0; i < bufv->count; i++) {
|
||||
if (bufv->buf[i].size == SIZE_MAX)
|
||||
@@ -229,8 +229,8 @@ static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
|
||||
int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
|
||||
|
||||
if (!src_is_fd && !dst_is_fd) {
|
||||
void *dstmem = dst->mem + dst_off;
|
||||
void *srcmem = src->mem + src_off;
|
||||
char *dstmem = dst->mem + dst_off;
|
||||
char *srcmem = src->mem + src_off;
|
||||
|
||||
if (dstmem != srcmem) {
|
||||
if (dstmem + len <= srcmem || srcmem + len <= dstmem)
|
||||
|
||||
+620
-464
File diff suppressed because it is too large
Load Diff
@@ -83,16 +83,21 @@ static void fuse_kern_chan_destroy(struct fuse_chan *ch)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
#define MIN_BUFSIZE 0x21000
|
||||
|
||||
struct fuse_chan *fuse_kern_chan_new(int fd)
|
||||
struct fuse_chan *
|
||||
fuse_kern_chan_new(int fd_)
|
||||
{
|
||||
struct fuse_chan_ops op = {
|
||||
.receive = fuse_kern_chan_receive,
|
||||
.send = fuse_kern_chan_send,
|
||||
.destroy = fuse_kern_chan_destroy,
|
||||
};
|
||||
size_t bufsize = getpagesize() + 0x1000;
|
||||
bufsize = bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : bufsize;
|
||||
return fuse_chan_new(&op, fd, bufsize, NULL);
|
||||
long pagesize;
|
||||
size_t bufsize;
|
||||
struct fuse_chan_ops op =
|
||||
{
|
||||
.receive = fuse_kern_chan_receive,
|
||||
.send = fuse_kern_chan_send,
|
||||
.destroy = fuse_kern_chan_destroy,
|
||||
};
|
||||
|
||||
pagesize = sysconf(_SC_PAGESIZE);
|
||||
|
||||
bufsize = ((FUSE_MAX_MAX_PAGES * pagesize) + 0x1000);
|
||||
|
||||
return fuse_chan_new(&op, fd_, bufsize, NULL);
|
||||
}
|
||||
|
||||
+185
-132
@@ -54,35 +54,43 @@ static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
|
||||
pagesize = getpagesize();
|
||||
}
|
||||
|
||||
static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
|
||||
static
|
||||
void
|
||||
convert_stat(const struct stat *stbuf_,
|
||||
struct fuse_attr *attr_)
|
||||
{
|
||||
attr->ino = stbuf->st_ino;
|
||||
attr->mode = stbuf->st_mode;
|
||||
attr->nlink = stbuf->st_nlink;
|
||||
attr->uid = stbuf->st_uid;
|
||||
attr->gid = stbuf->st_gid;
|
||||
attr->rdev = stbuf->st_rdev;
|
||||
attr->size = stbuf->st_size;
|
||||
attr->blksize = stbuf->st_blksize;
|
||||
attr->blocks = stbuf->st_blocks;
|
||||
attr->atime = stbuf->st_atime;
|
||||
attr->mtime = stbuf->st_mtime;
|
||||
attr->ctime = stbuf->st_ctime;
|
||||
attr->atimensec = ST_ATIM_NSEC(stbuf);
|
||||
attr->mtimensec = ST_MTIM_NSEC(stbuf);
|
||||
attr->ctimensec = ST_CTIM_NSEC(stbuf);
|
||||
attr_->ino = stbuf_->st_ino;
|
||||
attr_->mode = stbuf_->st_mode;
|
||||
attr_->nlink = stbuf_->st_nlink;
|
||||
attr_->uid = stbuf_->st_uid;
|
||||
attr_->gid = stbuf_->st_gid;
|
||||
attr_->rdev = stbuf_->st_rdev;
|
||||
attr_->size = stbuf_->st_size;
|
||||
attr_->blksize = stbuf_->st_blksize;
|
||||
attr_->blocks = stbuf_->st_blocks;
|
||||
attr_->atime = stbuf_->st_atime;
|
||||
attr_->mtime = stbuf_->st_mtime;
|
||||
attr_->ctime = stbuf_->st_ctime;
|
||||
attr_->atimensec = ST_ATIM_NSEC(stbuf_);
|
||||
attr_->mtimensec = ST_MTIM_NSEC(stbuf_);
|
||||
attr_->ctimensec = ST_CTIM_NSEC(stbuf_);
|
||||
}
|
||||
|
||||
static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
|
||||
static
|
||||
void
|
||||
convert_attr(const struct fuse_setattr_in *attr_,
|
||||
struct stat *stbuf_)
|
||||
{
|
||||
stbuf->st_mode = attr->mode;
|
||||
stbuf->st_uid = attr->uid;
|
||||
stbuf->st_gid = attr->gid;
|
||||
stbuf->st_size = attr->size;
|
||||
stbuf->st_atime = attr->atime;
|
||||
stbuf->st_mtime = attr->mtime;
|
||||
ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
|
||||
ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
|
||||
stbuf_->st_mode = attr_->mode;
|
||||
stbuf_->st_uid = attr_->uid;
|
||||
stbuf_->st_gid = attr_->gid;
|
||||
stbuf_->st_size = attr_->size;
|
||||
stbuf_->st_atime = attr_->atime;
|
||||
stbuf_->st_mtime = attr_->mtime;
|
||||
stbuf_->st_ctime = attr_->ctime;
|
||||
ST_ATIM_NSEC_SET(stbuf_,attr_->atimensec);
|
||||
ST_MTIM_NSEC_SET(stbuf_,attr_->mtimensec);
|
||||
ST_CTIM_NSEC_SET(stbuf_,attr_->ctimensec);
|
||||
}
|
||||
|
||||
static size_t iov_length(const struct iovec *iov, size_t count)
|
||||
@@ -297,9 +305,11 @@ static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
|
||||
return send_reply(req, 0, arg, argsize);
|
||||
}
|
||||
|
||||
int fuse_reply_err(fuse_req_t req, int err)
|
||||
int
|
||||
fuse_reply_err(fuse_req_t req_,
|
||||
int err_)
|
||||
{
|
||||
return send_reply(req, -err, NULL, 0);
|
||||
return send_reply(req_,-err_,NULL,0);
|
||||
}
|
||||
|
||||
void fuse_reply_none(fuse_req_t req)
|
||||
@@ -352,6 +362,8 @@ static void fill_open(struct fuse_open_out *arg,
|
||||
arg->open_flags |= FOPEN_KEEP_CACHE;
|
||||
if (f->nonseekable)
|
||||
arg->open_flags |= FOPEN_NONSEEKABLE;
|
||||
if (f->cache_readdir)
|
||||
arg->open_flags |= FOPEN_CACHE_DIR;
|
||||
}
|
||||
|
||||
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
|
||||
@@ -1041,7 +1053,6 @@ static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
if (arg->getattr_flags & FUSE_GETATTR_FH) {
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
fip = &fi;
|
||||
}
|
||||
}
|
||||
@@ -1052,36 +1063,46 @@ static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
fuse_reply_err(req, ENOSYS);
|
||||
}
|
||||
|
||||
static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
static
|
||||
void
|
||||
do_setattr(fuse_req_t req_,
|
||||
fuse_ino_t nodeid_,
|
||||
const void *inarg_)
|
||||
{
|
||||
struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
|
||||
struct stat stbuf;
|
||||
struct fuse_file_info *fi;
|
||||
struct fuse_file_info fi_store;
|
||||
struct fuse_setattr_in *arg;
|
||||
|
||||
if (req->f->op.setattr) {
|
||||
struct fuse_file_info *fi = NULL;
|
||||
struct fuse_file_info fi_store;
|
||||
struct stat stbuf;
|
||||
memset(&stbuf, 0, sizeof(stbuf));
|
||||
convert_attr(arg, &stbuf);
|
||||
if (arg->valid & FATTR_FH) {
|
||||
arg->valid &= ~FATTR_FH;
|
||||
memset(&fi_store, 0, sizeof(fi_store));
|
||||
fi = &fi_store;
|
||||
fi->fh = arg->fh;
|
||||
fi->fh_old = fi->fh;
|
||||
}
|
||||
arg->valid &=
|
||||
FUSE_SET_ATTR_MODE |
|
||||
FUSE_SET_ATTR_UID |
|
||||
FUSE_SET_ATTR_GID |
|
||||
FUSE_SET_ATTR_SIZE |
|
||||
FUSE_SET_ATTR_ATIME |
|
||||
FUSE_SET_ATTR_MTIME |
|
||||
FUSE_SET_ATTR_ATIME_NOW |
|
||||
FUSE_SET_ATTR_MTIME_NOW;
|
||||
if(req_->f->op.setattr == NULL)
|
||||
return (void)fuse_reply_err(req_,ENOSYS);
|
||||
|
||||
req->f->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
|
||||
} else
|
||||
fuse_reply_err(req, ENOSYS);
|
||||
fi = NULL;
|
||||
arg = (struct fuse_setattr_in*)inarg_;
|
||||
|
||||
memset(&stbuf,0,sizeof(stbuf));
|
||||
convert_attr(arg,&stbuf);
|
||||
|
||||
if(arg->valid & FATTR_FH)
|
||||
{
|
||||
arg->valid &= ~FATTR_FH;
|
||||
memset(&fi_store,0,sizeof(fi_store));
|
||||
fi = &fi_store;
|
||||
fi->fh = arg->fh;
|
||||
}
|
||||
|
||||
arg->valid &=
|
||||
(FATTR_MODE |
|
||||
FATTR_UID |
|
||||
FATTR_GID |
|
||||
FATTR_SIZE |
|
||||
FATTR_ATIME |
|
||||
FATTR_MTIME |
|
||||
FATTR_CTIME |
|
||||
FATTR_ATIME_NOW |
|
||||
FATTR_MTIME_NOW);
|
||||
|
||||
req_->f->op.setattr(req_,nodeid_,&stbuf,arg->valid,fi);
|
||||
}
|
||||
|
||||
static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
@@ -1230,7 +1251,6 @@ static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
if (req->f->conn.proto_minor >= 9) {
|
||||
fi.lock_owner = arg->lock_owner;
|
||||
fi.flags = arg->flags;
|
||||
@@ -1248,7 +1268,6 @@ static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
fi.writepage = arg->write_flags & 1;
|
||||
|
||||
if (req->f->conn.proto_minor < 9) {
|
||||
@@ -1279,7 +1298,6 @@ static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
fi.writepage = arg->write_flags & 1;
|
||||
|
||||
if (req->f->conn.proto_minor < 9) {
|
||||
@@ -1318,7 +1336,6 @@ static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
fi.flush = 1;
|
||||
if (req->f->conn.proto_minor >= 7)
|
||||
fi.lock_owner = arg->lock_owner;
|
||||
@@ -1337,7 +1354,6 @@ static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.flags = arg->flags;
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
if (req->f->conn.proto_minor >= 8) {
|
||||
fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
|
||||
fi.lock_owner = arg->lock_owner;
|
||||
@@ -1360,7 +1376,6 @@ static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
|
||||
if (req->f->op.fsync)
|
||||
req->f->op.fsync(req, nodeid, arg->fsync_flags & 1, &fi);
|
||||
@@ -1389,7 +1404,6 @@ static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
|
||||
if (req->f->op.readdir)
|
||||
req->f->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
|
||||
@@ -1405,7 +1419,6 @@ static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.flags = arg->flags;
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
|
||||
if (req->f->op.releasedir)
|
||||
req->f->op.releasedir(req, nodeid, &fi);
|
||||
@@ -1420,7 +1433,6 @@ static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
|
||||
if (req->f->op.fsyncdir)
|
||||
req->f->op.fsyncdir(req, nodeid, arg->fsync_flags & 1, &fi);
|
||||
@@ -1675,7 +1687,6 @@ static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
|
||||
if (sizeof(void *) == 4 && req->f->conn.proto_minor >= 16 &&
|
||||
!(flags & FUSE_IOCTL_32BIT)) {
|
||||
@@ -1702,7 +1713,6 @@ static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
|
||||
memset(&fi, 0, sizeof(fi));
|
||||
fi.fh = arg->fh;
|
||||
fi.fh_old = fi.fh;
|
||||
|
||||
if (req->f->op.poll) {
|
||||
struct fuse_pollhandle *ph = NULL;
|
||||
@@ -1760,8 +1770,10 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
f->conn.want = 0;
|
||||
|
||||
memset(&outarg, 0, sizeof(outarg));
|
||||
outarg.major = FUSE_KERNEL_VERSION;
|
||||
outarg.minor = FUSE_KERNEL_MINOR_VERSION;
|
||||
|
||||
outarg.major = FUSE_KERNEL_VERSION;
|
||||
outarg.minor = FUSE_KERNEL_MINOR_VERSION;
|
||||
outarg.max_pages = FUSE_DEFAULT_MAX_PAGES_PER_REQ;
|
||||
|
||||
if (arg->major < 7) {
|
||||
fprintf(stderr, "fuse: unsupported protocol version: %u.%u\n",
|
||||
@@ -1777,8 +1789,6 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
}
|
||||
|
||||
if (arg->minor >= 6) {
|
||||
if (f->conn.async_read)
|
||||
f->conn.async_read = arg->flags & FUSE_ASYNC_READ;
|
||||
if (arg->max_readahead < f->conn.max_readahead)
|
||||
f->conn.max_readahead = arg->max_readahead;
|
||||
if (arg->flags & FUSE_ASYNC_READ)
|
||||
@@ -1795,8 +1805,20 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
f->conn.capable |= FUSE_CAP_DONT_MASK;
|
||||
if (arg->flags & FUSE_FLOCK_LOCKS)
|
||||
f->conn.capable |= FUSE_CAP_FLOCK_LOCKS;
|
||||
if (arg->flags & FUSE_POSIX_ACL)
|
||||
f->conn.capable |= FUSE_CAP_POSIX_ACL;
|
||||
if (arg->flags & FUSE_CACHE_SYMLINKS)
|
||||
f->conn.capable |= FUSE_CAP_CACHE_SYMLINKS;
|
||||
if (arg->flags & FUSE_ASYNC_DIO)
|
||||
f->conn.capable |= FUSE_CAP_ASYNC_DIO;
|
||||
if (arg->flags & FUSE_PARALLEL_DIROPS)
|
||||
f->conn.capable |= FUSE_CAP_PARALLEL_DIROPS;
|
||||
if (arg->flags & FUSE_MAX_PAGES)
|
||||
f->conn.capable |= FUSE_CAP_MAX_PAGES;
|
||||
if (arg->flags & FUSE_WRITEBACK_CACHE)
|
||||
f->conn.capable |= FUSE_CAP_WRITEBACK_CACHE;
|
||||
} else {
|
||||
f->conn.async_read = 0;
|
||||
f->conn.want &= ~FUSE_CAP_ASYNC_READ;
|
||||
f->conn.max_readahead = 0;
|
||||
}
|
||||
|
||||
@@ -1817,14 +1839,10 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
if (req->f->conn.proto_minor >= 18)
|
||||
f->conn.capable |= FUSE_CAP_IOCTL_DIR;
|
||||
|
||||
if (f->atomic_o_trunc)
|
||||
f->conn.want |= FUSE_CAP_ATOMIC_O_TRUNC;
|
||||
if (f->op.getlk && f->op.setlk && !f->no_remote_posix_lock)
|
||||
f->conn.want |= FUSE_CAP_POSIX_LOCKS;
|
||||
if (f->op.flock && !f->no_remote_flock)
|
||||
f->conn.want |= FUSE_CAP_FLOCK_LOCKS;
|
||||
if (f->big_writes)
|
||||
f->conn.want |= FUSE_CAP_BIG_WRITES;
|
||||
|
||||
if (bufsize < FUSE_MIN_READ_BUFFER) {
|
||||
fprintf(stderr, "fuse: warning: buffer size too small: %zu\n",
|
||||
@@ -1847,7 +1865,13 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
if (f->no_splice_move)
|
||||
f->conn.want &= ~FUSE_CAP_SPLICE_MOVE;
|
||||
|
||||
if (f->conn.async_read || (f->conn.want & FUSE_CAP_ASYNC_READ))
|
||||
if ((arg->flags & FUSE_MAX_PAGES) && (f->conn.want & FUSE_CAP_MAX_PAGES))
|
||||
{
|
||||
outarg.flags |= FUSE_MAX_PAGES;
|
||||
outarg.max_pages = f->conn.max_pages;
|
||||
}
|
||||
|
||||
if (f->conn.want & FUSE_CAP_ASYNC_READ)
|
||||
outarg.flags |= FUSE_ASYNC_READ;
|
||||
if (f->conn.want & FUSE_CAP_POSIX_LOCKS)
|
||||
outarg.flags |= FUSE_POSIX_LOCKS;
|
||||
@@ -1861,6 +1885,16 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
outarg.flags |= FUSE_DONT_MASK;
|
||||
if (f->conn.want & FUSE_CAP_FLOCK_LOCKS)
|
||||
outarg.flags |= FUSE_FLOCK_LOCKS;
|
||||
if (f->conn.want & FUSE_CAP_POSIX_ACL)
|
||||
outarg.flags |= FUSE_POSIX_ACL;
|
||||
if (f->conn.want & FUSE_CAP_CACHE_SYMLINKS)
|
||||
outarg.flags |= FUSE_CACHE_SYMLINKS;
|
||||
if (f->conn.want & FUSE_CAP_ASYNC_DIO)
|
||||
outarg.flags |= FUSE_ASYNC_DIO;
|
||||
if (f->conn.want & FUSE_CAP_PARALLEL_DIROPS)
|
||||
outarg.flags |= FUSE_PARALLEL_DIROPS;
|
||||
if (f->conn.want & FUSE_CAP_WRITEBACK_CACHE)
|
||||
outarg.flags |= FUSE_WRITEBACK_CACHE;
|
||||
outarg.max_readahead = f->conn.max_readahead;
|
||||
outarg.max_write = f->conn.max_write;
|
||||
if (f->conn.proto_minor >= 13) {
|
||||
@@ -1887,6 +1921,7 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
|
||||
outarg.max_background);
|
||||
fprintf(stderr, " congestion_threshold=%i\n",
|
||||
outarg.congestion_threshold);
|
||||
fprintf(stderr, " max_pages=%d\n",outarg.max_pages);
|
||||
}
|
||||
|
||||
size_t outargsize;
|
||||
@@ -1959,6 +1994,34 @@ static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
|
||||
nreq->reply(nreq, req, nodeid, inarg, buf);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
do_copy_file_range(fuse_req_t req_,
|
||||
fuse_ino_t nodeid_in_,
|
||||
const void *arg_)
|
||||
|
||||
{
|
||||
struct fuse_file_info ffi_in = {0};
|
||||
struct fuse_file_info ffi_out = {0};
|
||||
struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in*)arg_;
|
||||
|
||||
ffi_in.fh = arg->fh_in;
|
||||
ffi_out.fh = arg->fh_out;
|
||||
|
||||
if(req_->f->op.copy_file_range == NULL)
|
||||
fuse_reply_err(req_,ENOSYS);
|
||||
else
|
||||
req_->f->op.copy_file_range(req_,
|
||||
nodeid_in_,
|
||||
arg->off_in,
|
||||
&ffi_in,
|
||||
arg->nodeid_out,
|
||||
arg->off_out,
|
||||
&ffi_out,
|
||||
arg->len,
|
||||
arg->flags);
|
||||
}
|
||||
|
||||
static int send_notify_iov(struct fuse_ll *f, struct fuse_chan *ch,
|
||||
int notify_code, struct iovec *iov, int count)
|
||||
{
|
||||
@@ -2259,50 +2322,52 @@ int fuse_req_interrupted(fuse_req_t req)
|
||||
static struct {
|
||||
void (*func)(fuse_req_t, fuse_ino_t, const void *);
|
||||
const char *name;
|
||||
} fuse_ll_ops[] = {
|
||||
[FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
|
||||
[FUSE_FORGET] = { do_forget, "FORGET" },
|
||||
[FUSE_GETATTR] = { do_getattr, "GETATTR" },
|
||||
[FUSE_SETATTR] = { do_setattr, "SETATTR" },
|
||||
[FUSE_READLINK] = { do_readlink, "READLINK" },
|
||||
[FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
|
||||
[FUSE_MKNOD] = { do_mknod, "MKNOD" },
|
||||
[FUSE_MKDIR] = { do_mkdir, "MKDIR" },
|
||||
[FUSE_UNLINK] = { do_unlink, "UNLINK" },
|
||||
[FUSE_RMDIR] = { do_rmdir, "RMDIR" },
|
||||
[FUSE_RENAME] = { do_rename, "RENAME" },
|
||||
[FUSE_LINK] = { do_link, "LINK" },
|
||||
[FUSE_OPEN] = { do_open, "OPEN" },
|
||||
[FUSE_READ] = { do_read, "READ" },
|
||||
[FUSE_WRITE] = { do_write, "WRITE" },
|
||||
[FUSE_STATFS] = { do_statfs, "STATFS" },
|
||||
[FUSE_RELEASE] = { do_release, "RELEASE" },
|
||||
[FUSE_FSYNC] = { do_fsync, "FSYNC" },
|
||||
[FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
|
||||
[FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
|
||||
[FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
|
||||
[FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
|
||||
[FUSE_FLUSH] = { do_flush, "FLUSH" },
|
||||
[FUSE_INIT] = { do_init, "INIT" },
|
||||
[FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
|
||||
[FUSE_READDIR] = { do_readdir, "READDIR" },
|
||||
[FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
|
||||
[FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
|
||||
[FUSE_GETLK] = { do_getlk, "GETLK" },
|
||||
[FUSE_SETLK] = { do_setlk, "SETLK" },
|
||||
[FUSE_SETLKW] = { do_setlkw, "SETLKW" },
|
||||
[FUSE_ACCESS] = { do_access, "ACCESS" },
|
||||
[FUSE_CREATE] = { do_create, "CREATE" },
|
||||
[FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
|
||||
[FUSE_BMAP] = { do_bmap, "BMAP" },
|
||||
[FUSE_IOCTL] = { do_ioctl, "IOCTL" },
|
||||
[FUSE_POLL] = { do_poll, "POLL" },
|
||||
[FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
|
||||
[FUSE_DESTROY] = { do_destroy, "DESTROY" },
|
||||
[FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
|
||||
[FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
|
||||
[CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
|
||||
};
|
||||
} fuse_ll_ops[] =
|
||||
{
|
||||
[FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
|
||||
[FUSE_FORGET] = { do_forget, "FORGET" },
|
||||
[FUSE_GETATTR] = { do_getattr, "GETATTR" },
|
||||
[FUSE_SETATTR] = { do_setattr, "SETATTR" },
|
||||
[FUSE_READLINK] = { do_readlink, "READLINK" },
|
||||
[FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
|
||||
[FUSE_MKNOD] = { do_mknod, "MKNOD" },
|
||||
[FUSE_MKDIR] = { do_mkdir, "MKDIR" },
|
||||
[FUSE_UNLINK] = { do_unlink, "UNLINK" },
|
||||
[FUSE_RMDIR] = { do_rmdir, "RMDIR" },
|
||||
[FUSE_RENAME] = { do_rename, "RENAME" },
|
||||
[FUSE_LINK] = { do_link, "LINK" },
|
||||
[FUSE_OPEN] = { do_open, "OPEN" },
|
||||
[FUSE_READ] = { do_read, "READ" },
|
||||
[FUSE_WRITE] = { do_write, "WRITE" },
|
||||
[FUSE_STATFS] = { do_statfs, "STATFS" },
|
||||
[FUSE_RELEASE] = { do_release, "RELEASE" },
|
||||
[FUSE_FSYNC] = { do_fsync, "FSYNC" },
|
||||
[FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
|
||||
[FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
|
||||
[FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
|
||||
[FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
|
||||
[FUSE_FLUSH] = { do_flush, "FLUSH" },
|
||||
[FUSE_INIT] = { do_init, "INIT" },
|
||||
[FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
|
||||
[FUSE_READDIR] = { do_readdir, "READDIR" },
|
||||
[FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
|
||||
[FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
|
||||
[FUSE_GETLK] = { do_getlk, "GETLK" },
|
||||
[FUSE_SETLK] = { do_setlk, "SETLK" },
|
||||
[FUSE_SETLKW] = { do_setlkw, "SETLKW" },
|
||||
[FUSE_ACCESS] = { do_access, "ACCESS" },
|
||||
[FUSE_CREATE] = { do_create, "CREATE" },
|
||||
[FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
|
||||
[FUSE_BMAP] = { do_bmap, "BMAP" },
|
||||
[FUSE_IOCTL] = { do_ioctl, "IOCTL" },
|
||||
[FUSE_POLL] = { do_poll, "POLL" },
|
||||
[FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
|
||||
[FUSE_DESTROY] = { do_destroy, "DESTROY" },
|
||||
[FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
|
||||
[FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
|
||||
[FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
|
||||
[CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
|
||||
};
|
||||
|
||||
#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
|
||||
|
||||
@@ -2487,19 +2552,14 @@ static const struct fuse_opt fuse_ll_opts[] = {
|
||||
{ "debug", offsetof(struct fuse_ll, debug), 1 },
|
||||
{ "-d", offsetof(struct fuse_ll, debug), 1 },
|
||||
{ "allow_root", offsetof(struct fuse_ll, allow_root), 1 },
|
||||
{ "max_write=%u", offsetof(struct fuse_ll, conn.max_write), 0 },
|
||||
{ "max_readahead=%u", offsetof(struct fuse_ll, conn.max_readahead), 0 },
|
||||
{ "max_background=%u", offsetof(struct fuse_ll, conn.max_background), 0 },
|
||||
{ "congestion_threshold=%u",
|
||||
offsetof(struct fuse_ll, conn.congestion_threshold), 0 },
|
||||
{ "async_read", offsetof(struct fuse_ll, conn.async_read), 1 },
|
||||
{ "sync_read", offsetof(struct fuse_ll, conn.async_read), 0 },
|
||||
{ "atomic_o_trunc", offsetof(struct fuse_ll, atomic_o_trunc), 1},
|
||||
{ "no_remote_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
|
||||
{ "no_remote_lock", offsetof(struct fuse_ll, no_remote_flock), 1},
|
||||
{ "no_remote_flock", offsetof(struct fuse_ll, no_remote_flock), 1},
|
||||
{ "no_remote_posix_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
|
||||
{ "big_writes", offsetof(struct fuse_ll, big_writes), 1},
|
||||
{ "splice_write", offsetof(struct fuse_ll, splice_write), 1},
|
||||
{ "no_splice_write", offsetof(struct fuse_ll, no_splice_write), 1},
|
||||
{ "splice_move", offsetof(struct fuse_ll, splice_move), 1},
|
||||
@@ -2523,14 +2583,9 @@ static void fuse_ll_version(void)
|
||||
static void fuse_ll_help(void)
|
||||
{
|
||||
fprintf(stderr,
|
||||
" -o max_write=N set maximum size of write requests\n"
|
||||
" -o max_readahead=N set maximum readahead\n"
|
||||
" -o max_background=N set number of maximum background requests\n"
|
||||
" -o congestion_threshold=N set kernel's congestion threshold\n"
|
||||
" -o async_read perform reads asynchronously (default)\n"
|
||||
" -o sync_read perform reads synchronously\n"
|
||||
" -o atomic_o_trunc enable atomic open+truncate support\n"
|
||||
" -o big_writes enable larger than 4kB writes\n"
|
||||
" -o no_remote_lock disable remote file locking\n"
|
||||
" -o no_remote_flock disable remote file locking (BSD)\n"
|
||||
" -o no_remote_posix_lock disable remove file locking (POSIX)\n"
|
||||
@@ -2733,10 +2788,8 @@ struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
|
||||
goto out;
|
||||
}
|
||||
|
||||
f->conn.async_read = 1;
|
||||
f->conn.max_write = UINT_MAX;
|
||||
f->conn.max_readahead = UINT_MAX;
|
||||
f->atomic_o_trunc = 0;
|
||||
list_init_req(&f->list);
|
||||
list_init_req(&f->interrupts);
|
||||
list_init_nreq(&f->notify_list);
|
||||
|
||||
+15
-12
@@ -36,22 +36,25 @@ static inline void fuse_mutex_init(pthread_mutex_t *mut)
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_ATIM
|
||||
/* Linux */
|
||||
#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
|
||||
#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
|
||||
#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
|
||||
#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
|
||||
#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
|
||||
#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
|
||||
#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
|
||||
#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
|
||||
#define ST_ATIM_NSEC_SET(stbuf,val) ((stbuf)->st_atim.tv_nsec = (val))
|
||||
#define ST_CTIM_NSEC_SET(stbuf,val) ((stbuf)->st_ctim.tv_nsec = (val))
|
||||
#define ST_MTIM_NSEC_SET(stbuf,val) ((stbuf)->st_mtim.tv_nsec = (val))
|
||||
#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
|
||||
/* FreeBSD */
|
||||
#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
|
||||
#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
|
||||
#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
|
||||
#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
|
||||
#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
|
||||
#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
|
||||
#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
|
||||
#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
|
||||
#define ST_ATIM_NSEC_SET(stbuf,val) ((stbuf)->st_atimespec.tv_nsec = (val))
|
||||
#define ST_CTIM_NSEC_SET(stbuf,val) ((stbuf)->st_ctimespec.tv_nsec = (val))
|
||||
#define ST_MTIM_NSEC_SET(stbuf,val) ((stbuf)->st_mtimespec.tv_nsec = (val))
|
||||
#else
|
||||
#define ST_ATIM_NSEC(stbuf) 0
|
||||
#define ST_CTIM_NSEC(stbuf) 0
|
||||
#define ST_MTIM_NSEC(stbuf) 0
|
||||
#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
|
||||
#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
|
||||
#define ST_ATIM_NSEC_SET(stbuf,val) do { } while (0)
|
||||
#define ST_CTIM_NSEC_SET(stbuf,val) do { } while (0)
|
||||
#define ST_MTIM_NSEC_SET(stbuf,val) do { } while (0)
|
||||
#endif
|
||||
|
||||
+72
-55
@@ -28,23 +28,26 @@ enum {
|
||||
KEY_VERSION,
|
||||
};
|
||||
|
||||
struct helper_opts {
|
||||
int singlethread;
|
||||
int foreground;
|
||||
int nodefault_subtype;
|
||||
char *mountpoint;
|
||||
struct helper_opts
|
||||
{
|
||||
int singlethread;
|
||||
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[] = {
|
||||
static
|
||||
const
|
||||
struct fuse_opt fuse_helper_opts[] =
|
||||
{
|
||||
FUSE_HELPER_OPT("-d", foreground),
|
||||
FUSE_HELPER_OPT("debug", foreground),
|
||||
FUSE_HELPER_OPT("-f", foreground),
|
||||
FUSE_HELPER_OPT("-s", singlethread),
|
||||
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),
|
||||
@@ -144,37 +147,46 @@ static int add_default_subtype(const char *progname, struct fuse_args *args)
|
||||
return res;
|
||||
}
|
||||
|
||||
int fuse_parse_cmdline(struct fuse_args *args, char **mountpoint,
|
||||
int *multithreaded, int *foreground)
|
||||
int
|
||||
fuse_parse_cmdline(struct fuse_args *args_,
|
||||
char **mountpoint_,
|
||||
int *multithreaded_,
|
||||
int *foreground_)
|
||||
{
|
||||
int res;
|
||||
struct helper_opts hopts;
|
||||
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;
|
||||
memset(&hopts, 0, sizeof(hopts));
|
||||
|
||||
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);
|
||||
res = fuse_opt_parse(args_,
|
||||
&hopts,
|
||||
fuse_helper_opts,
|
||||
fuse_helper_opt_proc);
|
||||
if(res == -1)
|
||||
return -1;
|
||||
|
||||
if (multithreaded)
|
||||
*multithreaded = !hopts.singlethread;
|
||||
if (foreground)
|
||||
*foreground = hopts.foreground;
|
||||
return 0;
|
||||
if(!hopts.nodefault_subtype)
|
||||
{
|
||||
res = add_default_subtype(args_->argv[0], args_);
|
||||
if(res == -1)
|
||||
goto err;
|
||||
}
|
||||
|
||||
err:
|
||||
free(hopts.mountpoint);
|
||||
return -1;
|
||||
if(mountpoint_)
|
||||
*mountpoint_ = hopts.mountpoint;
|
||||
else
|
||||
free(hopts.mountpoint);
|
||||
|
||||
if(multithreaded_)
|
||||
*multithreaded_ = !hopts.singlethread;
|
||||
if(foreground_)
|
||||
*foreground_ = hopts.foreground;
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
free(hopts.mountpoint);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fuse_daemonize(int foreground)
|
||||
@@ -229,36 +241,41 @@ int fuse_daemonize(int foreground)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct fuse_chan *fuse_mount_common(const char *mountpoint,
|
||||
struct fuse_args *args)
|
||||
static
|
||||
struct fuse_chan *
|
||||
fuse_mount_common(const char *mountpoint_,
|
||||
struct fuse_args *args_)
|
||||
{
|
||||
struct fuse_chan *ch;
|
||||
int fd;
|
||||
struct fuse_chan *ch;
|
||||
int fd;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
/*
|
||||
* 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_mount_compat25(mountpoint, args);
|
||||
if (fd == -1)
|
||||
return NULL;
|
||||
fd = fuse_mount_compat25(mountpoint_, args_);
|
||||
if(fd == -1)
|
||||
return NULL;
|
||||
|
||||
ch = fuse_kern_chan_new(fd);
|
||||
if (!ch)
|
||||
fuse_kern_unmount(mountpoint, fd);
|
||||
ch = fuse_kern_chan_new(fd);
|
||||
if(!ch)
|
||||
fuse_kern_unmount(mountpoint_, fd);
|
||||
|
||||
return ch;
|
||||
return ch;
|
||||
}
|
||||
|
||||
struct fuse_chan *fuse_mount(const char *mountpoint, struct fuse_args *args)
|
||||
struct fuse_chan *
|
||||
fuse_mount(const char *mountpoint_,
|
||||
struct fuse_args *args_)
|
||||
{
|
||||
return fuse_mount_common(mountpoint, args);
|
||||
return fuse_mount_common(mountpoint_,args_);
|
||||
}
|
||||
|
||||
static void fuse_unmount_common(const char *mountpoint, struct fuse_chan *ch)
|
||||
|
||||
@@ -143,7 +143,9 @@ static void mount_help(void)
|
||||
|
||||
static void exec_fusermount(const char *argv[])
|
||||
{
|
||||
execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (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);
|
||||
}
|
||||
|
||||
@@ -407,7 +409,7 @@ static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
|
||||
rv = receive_fd(fds[1]);
|
||||
|
||||
if (!mo->auto_unmount) {
|
||||
/* with auto_unmount option fusermount will not exit until
|
||||
/* with auto_unmount option fusermount will not exit until
|
||||
this socket is closed */
|
||||
close(fds[1]);
|
||||
waitpid(pid, NULL, 0); /* bury zombie */
|
||||
|
||||
@@ -1293,7 +1293,7 @@ int main(int argc, char *argv[])
|
||||
return 0;
|
||||
|
||||
/* Become a daemon and wait for the parent to exit or die.
|
||||
ie For the control socket to get closed.
|
||||
ie For the control socket to get closed.
|
||||
btw We don't want to use daemon() function here because
|
||||
it forks and messes with the file descriptors. */
|
||||
setsid();
|
||||
|
||||
@@ -93,6 +93,7 @@ int main(int argc, char *argv[])
|
||||
else
|
||||
basename = argv[0];
|
||||
|
||||
type = "mergerfs";
|
||||
if (strncmp(basename, "mount.fuse.", 11) == 0)
|
||||
type = basename + 11;
|
||||
if (strncmp(basename, "mount.fuseblk.", 14) == 0)
|
||||
+638
-247
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -1,7 +1,7 @@
|
||||
Name: mergerfs
|
||||
Version: __VERSION__
|
||||
Release: 1%{?dist}
|
||||
Summary: A FUSE union filesystem
|
||||
Summary: A featureful FUSE based union filesystem
|
||||
|
||||
Group: Applications/System
|
||||
License: ISC
|
||||
@@ -33,6 +33,7 @@ make install PREFIX=%{_prefix} DESTDIR=%{buildroot}
|
||||
|
||||
%files
|
||||
%{_bindir}/*
|
||||
%{_sbindir}/*
|
||||
%doc %{_mandir}/*
|
||||
|
||||
%changelog
|
||||
|
||||
+83
-5
@@ -14,17 +14,17 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.hpp"
|
||||
#include "errno.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "rwlock.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "errno.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "rwlock.hpp"
|
||||
|
||||
#define MINFREESPACE_DEFAULT (4294967295ULL)
|
||||
#define POLICYINIT(X) X(policies[FuseFunc::Enum::X])
|
||||
|
||||
@@ -48,6 +48,13 @@ Config::Config()
|
||||
xattr(0),
|
||||
statfs(StatFS::BASE),
|
||||
statfs_ignore(StatFSIgnore::NONE),
|
||||
posix_acl(false),
|
||||
cache_symlinks(false),
|
||||
cache_readdir(false),
|
||||
async_read(true),
|
||||
writeback_cache(false),
|
||||
cache_files(CacheFiles::LIBFUSE),
|
||||
fuse_msg_size(FUSE_MAX_MAX_PAGES),
|
||||
POLICYINIT(access),
|
||||
POLICYINIT(chmod),
|
||||
POLICYINIT(chown),
|
||||
@@ -120,3 +127,74 @@ Config::set_category_policy(const string &category_,
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Config::CacheFiles::operator int() const
|
||||
{
|
||||
return _data;
|
||||
}
|
||||
|
||||
Config::CacheFiles::operator std::string() const
|
||||
{
|
||||
switch(_data)
|
||||
{
|
||||
case OFF:
|
||||
return "off";
|
||||
case PARTIAL:
|
||||
return "partial";
|
||||
case FULL:
|
||||
return "full";
|
||||
case AUTO_FULL:
|
||||
return "auto-full";
|
||||
case LIBFUSE:
|
||||
return "libfuse";
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Config::CacheFiles::CacheFiles()
|
||||
: _data(INVALID)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Config::CacheFiles::CacheFiles(Config::CacheFiles::Enum data_)
|
||||
: _data(data_)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
Config::CacheFiles::valid() const
|
||||
{
|
||||
return (_data != INVALID);
|
||||
}
|
||||
|
||||
Config::CacheFiles&
|
||||
Config::CacheFiles::operator=(const Config::CacheFiles::Enum data_)
|
||||
{
|
||||
_data = data_;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Config::CacheFiles&
|
||||
Config::CacheFiles::operator=(const std::string &data_)
|
||||
{
|
||||
if(data_ == "off")
|
||||
_data = OFF;
|
||||
else if(data_ == "partial")
|
||||
_data = PARTIAL;
|
||||
else if(data_ == "full")
|
||||
_data = FULL;
|
||||
else if(data_ == "auto-full")
|
||||
_data = AUTO_FULL;
|
||||
else if(data_ == "libfuse")
|
||||
_data = LIBFUSE;
|
||||
else
|
||||
_data = INVALID;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,34 @@ public:
|
||||
};
|
||||
};
|
||||
|
||||
class CacheFiles
|
||||
{
|
||||
public:
|
||||
enum Enum
|
||||
{
|
||||
INVALID = -1,
|
||||
LIBFUSE,
|
||||
OFF,
|
||||
PARTIAL,
|
||||
FULL,
|
||||
AUTO_FULL
|
||||
};
|
||||
|
||||
CacheFiles();
|
||||
CacheFiles(Enum);
|
||||
|
||||
operator int() const;
|
||||
operator std::string() const;
|
||||
|
||||
CacheFiles& operator=(const Enum);
|
||||
CacheFiles& operator=(const std::string&);
|
||||
|
||||
bool valid() const;
|
||||
|
||||
private:
|
||||
Enum _data;
|
||||
};
|
||||
|
||||
public:
|
||||
Config();
|
||||
|
||||
@@ -68,6 +96,8 @@ public:
|
||||
uint64_t minfreespace;
|
||||
bool moveonenospc;
|
||||
bool direct_io;
|
||||
bool kernel_cache;
|
||||
bool auto_cache;
|
||||
bool dropcacheonclose;
|
||||
bool symlinkify;
|
||||
time_t symlinkify_timeout;
|
||||
@@ -78,6 +108,13 @@ public:
|
||||
int xattr;
|
||||
StatFS::Enum statfs;
|
||||
StatFSIgnore::Enum statfs_ignore;
|
||||
bool posix_acl;
|
||||
bool cache_symlinks;
|
||||
bool cache_readdir;
|
||||
bool async_read;
|
||||
bool writeback_cache;
|
||||
CacheFiles cache_files;
|
||||
uint16_t fuse_msg_size;
|
||||
|
||||
public:
|
||||
const Policy *policies[FuseFunc::Enum::END];
|
||||
|
||||
+39
-39
@@ -26,56 +26,56 @@
|
||||
#include "fs_base_stat.hpp"
|
||||
#include "fs_base_utime.hpp"
|
||||
#include "fs_copy_file_range.hpp"
|
||||
#include "fs_copyfile.hpp"
|
||||
#include "fs_copydata_copy_file_range.hpp"
|
||||
#include "fs_copydata_readwrite.hpp"
|
||||
#include "fs_ficlone.hpp"
|
||||
#include "fs_sendfile.hpp"
|
||||
#include "fs_xattr.hpp"
|
||||
|
||||
static
|
||||
int
|
||||
copydata(const int src_fd_,
|
||||
const int dst_fd_,
|
||||
const size_t count_)
|
||||
namespace l
|
||||
{
|
||||
int rv;
|
||||
static
|
||||
int
|
||||
copydata(const int src_fd_,
|
||||
const int dst_fd_,
|
||||
const size_t count_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = fs::ftruncate(dst_fd_,count_);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
rv = fs::ftruncate(dst_fd_,count_);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
rv = fs::ficlone(src_fd_,dst_fd_);
|
||||
if(rv != -1)
|
||||
return rv;
|
||||
rv = fs::ficlone(src_fd_,dst_fd_);
|
||||
if(rv != -1)
|
||||
return rv;
|
||||
|
||||
fs::fadvise_willneed(src_fd_,0,count_);
|
||||
fs::fadvise_sequential(src_fd_,0,count_);
|
||||
fs::fadvise_willneed(src_fd_,0,count_);
|
||||
fs::fadvise_sequential(src_fd_,0,count_);
|
||||
|
||||
rv = fs::copy_file_range(src_fd_,dst_fd_,count_);
|
||||
if(rv != -1)
|
||||
return rv;
|
||||
rv = fs::copydata_copy_file_range(src_fd_,dst_fd_);
|
||||
if(rv != -1)
|
||||
return rv;
|
||||
|
||||
rv = fs::sendfile(src_fd_,dst_fd_,count_);
|
||||
if(rv != -1)
|
||||
return rv;
|
||||
return fs::copydata_readwrite(src_fd_,dst_fd_);
|
||||
}
|
||||
|
||||
return fs::copyfile(src_fd_,dst_fd_);
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
ignorable_error(const int err_)
|
||||
{
|
||||
switch(err_)
|
||||
{
|
||||
case ENOTTY:
|
||||
case ENOTSUP:
|
||||
static
|
||||
bool
|
||||
ignorable_error(const int err_)
|
||||
{
|
||||
switch(err_)
|
||||
{
|
||||
case ENOTTY:
|
||||
case ENOTSUP:
|
||||
#if ENOTSUP != EOPNOTSUPP
|
||||
case EOPNOTSUPP:
|
||||
case EOPNOTSUPP:
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace fs
|
||||
@@ -91,16 +91,16 @@ namespace fs
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
rv = ::copydata(src_fd_,dst_fd_,src_st.st_size);
|
||||
rv = l::copydata(src_fd_,dst_fd_,src_st.st_size);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
rv = fs::attr::copy(src_fd_,dst_fd_);
|
||||
if((rv == -1) && !ignorable_error(errno))
|
||||
if((rv == -1) && !l::ignorable_error(errno))
|
||||
return -1;
|
||||
|
||||
rv = fs::xattr::copy(src_fd_,dst_fd_);
|
||||
if((rv == -1) && !ignorable_error(errno))
|
||||
if((rv == -1) && !l::ignorable_error(errno))
|
||||
return -1;
|
||||
|
||||
rv = fs::fchown_check_on_error(dst_fd_,src_st);
|
||||
|
||||
@@ -24,17 +24,11 @@
|
||||
|
||||
namespace fs
|
||||
{
|
||||
ssize_t
|
||||
copy_file_range(const int fd_in_,
|
||||
int64_t *off_in_,
|
||||
const int fd_out_,
|
||||
int64_t *off_out_,
|
||||
const size_t len_,
|
||||
const unsigned int flags_);
|
||||
|
||||
ssize_t
|
||||
copy_file_range(const int fd_in_,
|
||||
const int fd_out_,
|
||||
const size_t len_,
|
||||
const unsigned int flags_ = 0);
|
||||
int64_t
|
||||
copy_file_range(const int fd_in,
|
||||
int64_t *off_in,
|
||||
const int fd_out,
|
||||
int64_t *off_out,
|
||||
const uint64_t len,
|
||||
const unsigned int flags);
|
||||
}
|
||||
|
||||
@@ -32,33 +32,39 @@
|
||||
namespace l
|
||||
{
|
||||
static
|
||||
ssize_t
|
||||
copy_file_range_(int fd_in_,
|
||||
int64_t *off_in_,
|
||||
int fd_out_,
|
||||
int64_t *off_out_,
|
||||
size_t len_,
|
||||
unsigned int flags_)
|
||||
int64_t
|
||||
copy_file_range_(int src_fd_,
|
||||
int64_t *src_off_,
|
||||
int tgt_fd_,
|
||||
int64_t *tgt_off_,
|
||||
const uint64_t len_,
|
||||
const unsigned int flags_)
|
||||
{
|
||||
#ifdef SYS_copy_file_range
|
||||
ssize_t rv;
|
||||
loff_t off_in;
|
||||
loff_t off_out;
|
||||
int64_t rv;
|
||||
loff_t src_off;
|
||||
loff_t tgt_off;
|
||||
loff_t *src_off_ptr;
|
||||
loff_t *tgt_off_ptr;
|
||||
|
||||
off_in = *off_in_;
|
||||
off_out = *off_out_;
|
||||
src_off = ((src_off_ == NULL) ? 0 : *src_off_);
|
||||
tgt_off = ((tgt_off_ == NULL) ? 0 : *tgt_off_);
|
||||
src_off_ptr = ((src_off_ == NULL) ? NULL : &src_off);
|
||||
tgt_off_ptr = ((tgt_off_ == NULL) ? NULL : &tgt_off);
|
||||
rv = ::syscall(SYS_copy_file_range,
|
||||
fd_in_,
|
||||
&off_in,
|
||||
fd_out_,
|
||||
&off_out,
|
||||
src_fd_,
|
||||
src_off_ptr,
|
||||
tgt_fd_,
|
||||
tgt_off_ptr,
|
||||
len_,
|
||||
flags_);
|
||||
|
||||
if(rv != -1)
|
||||
{
|
||||
*off_in_ = off_in;
|
||||
*off_out_ = off_out;
|
||||
if(src_off_ != NULL)
|
||||
*src_off_ = src_off;
|
||||
if(tgt_off_ != NULL)
|
||||
*tgt_off_ = tgt_off;
|
||||
}
|
||||
|
||||
return rv;
|
||||
@@ -70,38 +76,18 @@ namespace l
|
||||
|
||||
namespace fs
|
||||
{
|
||||
ssize_t
|
||||
copy_file_range(const int fd_in_,
|
||||
int64_t *off_in_,
|
||||
const int fd_out_,
|
||||
int64_t *off_out_,
|
||||
const size_t len_,
|
||||
int64_t
|
||||
copy_file_range(const int src_fd_,
|
||||
int64_t *src_off_,
|
||||
const int tgt_fd_,
|
||||
int64_t *tgt_off_,
|
||||
const uint64_t len_,
|
||||
const unsigned int flags_)
|
||||
{
|
||||
return l::copy_file_range_(fd_in_,
|
||||
off_in_,
|
||||
fd_out_,
|
||||
off_out_,
|
||||
len_,
|
||||
flags_);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
copy_file_range(const int fd_in_,
|
||||
const int fd_out_,
|
||||
const size_t len_,
|
||||
const unsigned int flags_)
|
||||
{
|
||||
int64_t off_in;
|
||||
int64_t off_out;
|
||||
|
||||
off_in = 0;
|
||||
off_out = 0;
|
||||
|
||||
return fs::copy_file_range(fd_in_,
|
||||
&off_in,
|
||||
fd_out_,
|
||||
&off_out,
|
||||
return l::copy_file_range_(src_fd_,
|
||||
src_off_,
|
||||
tgt_fd_,
|
||||
tgt_off_,
|
||||
len_,
|
||||
flags_);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "errno.hpp"
|
||||
#include "fs_base_stat.hpp"
|
||||
#include "fs_copy_file_range.hpp"
|
||||
|
||||
namespace l
|
||||
{
|
||||
int64_t
|
||||
copydata_copy_file_range(const int src_fd_,
|
||||
const int dst_fd_,
|
||||
uint64_t size_)
|
||||
{
|
||||
int64_t rv;
|
||||
uint64_t len;
|
||||
int64_t src_off;
|
||||
int64_t dst_off;
|
||||
|
||||
src_off = 0;
|
||||
dst_off = 0;
|
||||
len = size_;
|
||||
do
|
||||
{
|
||||
rv = fs::copy_file_range(src_fd_,&src_off,dst_fd_,&dst_off,len,0);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
len -= rv;
|
||||
}
|
||||
while((len > 0) && (rv > 0));
|
||||
|
||||
return size_;
|
||||
}
|
||||
}
|
||||
|
||||
namespace fs
|
||||
{
|
||||
int64_t
|
||||
copydata_copy_file_range(const int src_fd_,
|
||||
const int dst_fd_)
|
||||
{
|
||||
int rv;
|
||||
struct stat st;
|
||||
|
||||
rv = fs::fstat(src_fd_,&st);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
return l::copydata_copy_file_range(src_fd_,
|
||||
dst_fd_,
|
||||
st.st_size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright (c) 2016, 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
|
||||
|
||||
namespace fs
|
||||
{
|
||||
int
|
||||
copydata_copy_file_range(const int src_fd,
|
||||
const int dst_fd);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
Copyright (c) 2018, 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 "errno.hpp"
|
||||
#include "fs_base_stat.hpp"
|
||||
#include "fs_base_lseek.hpp"
|
||||
#include "fs_base_read.hpp"
|
||||
#include "fs_base_write.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using std::vector;
|
||||
|
||||
namespace l
|
||||
{
|
||||
static
|
||||
int
|
||||
writen(const int fd_,
|
||||
const char *buf_,
|
||||
const size_t count_)
|
||||
{
|
||||
size_t nleft;
|
||||
ssize_t nwritten;
|
||||
|
||||
nleft = count_;
|
||||
do
|
||||
{
|
||||
nwritten = fs::write(fd_,buf_,nleft);
|
||||
if((nwritten == -1) && (errno == EINTR))
|
||||
continue;
|
||||
if(nwritten == -1)
|
||||
return -1;
|
||||
|
||||
nleft -= nwritten;
|
||||
buf_ += nwritten;
|
||||
}
|
||||
while(nleft > 0);
|
||||
|
||||
return count_;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
copydata_readwrite(const int src_fd_,
|
||||
const int dst_fd_,
|
||||
const size_t count_,
|
||||
const size_t blocksize_)
|
||||
{
|
||||
ssize_t nr;
|
||||
ssize_t nw;
|
||||
ssize_t bufsize;
|
||||
size_t totalwritten;
|
||||
vector<char> buf;
|
||||
|
||||
bufsize = (blocksize_ * 16);
|
||||
buf.resize(bufsize);
|
||||
|
||||
fs::lseek(src_fd_,0,SEEK_SET);
|
||||
|
||||
totalwritten = 0;
|
||||
while(totalwritten < count_)
|
||||
{
|
||||
nr = fs::read(src_fd_,&buf[0],bufsize);
|
||||
if(nr == 0)
|
||||
return totalwritten;
|
||||
if((nr == -1) && (errno == EINTR))
|
||||
continue;
|
||||
if(nr == -1)
|
||||
return -1;
|
||||
|
||||
nw = l::writen(dst_fd_,&buf[0],nr);
|
||||
if(nw == -1)
|
||||
return -1;
|
||||
|
||||
totalwritten += nw;
|
||||
}
|
||||
|
||||
return totalwritten;
|
||||
}
|
||||
}
|
||||
|
||||
namespace fs
|
||||
{
|
||||
int
|
||||
copydata_readwrite(const int src_fd_,
|
||||
const int dst_fd_)
|
||||
{
|
||||
int rv;
|
||||
struct stat st;
|
||||
|
||||
rv = fs::fstat(src_fd_,&st);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
return l::copydata_readwrite(src_fd_,
|
||||
dst_fd_,
|
||||
st.st_size,
|
||||
st.st_blksize);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,6 @@
|
||||
namespace fs
|
||||
{
|
||||
int
|
||||
copyfile(const int src_fd_,
|
||||
const int dst_fd_);
|
||||
copydata_readwrite(const int src_fd,
|
||||
const int dst_fd);
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2018, 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 "errno.hpp"
|
||||
#include "fs_base_stat.hpp"
|
||||
#include "fs_base_lseek.hpp"
|
||||
#include "fs_base_read.hpp"
|
||||
#include "fs_base_write.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
using std::vector;
|
||||
|
||||
static
|
||||
int
|
||||
writen(const int fd_,
|
||||
const char *buf_,
|
||||
const size_t count_)
|
||||
{
|
||||
size_t nleft;
|
||||
ssize_t nwritten;
|
||||
|
||||
nleft = count_;
|
||||
do
|
||||
{
|
||||
nwritten = fs::write(fd_,buf_,nleft);
|
||||
if((nwritten == -1) && (errno == EINTR))
|
||||
continue;
|
||||
if(nwritten == -1)
|
||||
return -1;
|
||||
|
||||
nleft -= nwritten;
|
||||
buf_ += nwritten;
|
||||
}
|
||||
while(nleft > 0);
|
||||
|
||||
return count_;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
copyfile(const int src_fd_,
|
||||
const int dst_fd_,
|
||||
const size_t count_,
|
||||
const size_t blocksize_)
|
||||
{
|
||||
ssize_t nr;
|
||||
ssize_t nw;
|
||||
ssize_t bufsize;
|
||||
size_t totalwritten;
|
||||
vector<char> buf;
|
||||
|
||||
bufsize = (blocksize_ * 16);
|
||||
buf.resize(bufsize);
|
||||
|
||||
fs::lseek(src_fd_,0,SEEK_SET);
|
||||
|
||||
totalwritten = 0;
|
||||
while(totalwritten < count_)
|
||||
{
|
||||
nr = fs::read(src_fd_,&buf[0],bufsize);
|
||||
if(nr == 0)
|
||||
return totalwritten;
|
||||
if((nr == -1) && (errno == EINTR))
|
||||
continue;
|
||||
if(nr == -1)
|
||||
return -1;
|
||||
|
||||
nw = writen(dst_fd_,&buf[0],nr);
|
||||
if(nw == -1)
|
||||
return -1;
|
||||
|
||||
totalwritten += nw;
|
||||
}
|
||||
|
||||
return totalwritten;
|
||||
}
|
||||
|
||||
namespace fs
|
||||
{
|
||||
int
|
||||
copyfile(const int src_fd_,
|
||||
const int dst_fd_)
|
||||
{
|
||||
int rv;
|
||||
struct stat st;
|
||||
|
||||
rv = fs::fstat(src_fd_,&st);
|
||||
if(rv == -1)
|
||||
return rv;
|
||||
|
||||
return ::copyfile(src_fd_,
|
||||
dst_fd_,
|
||||
st.st_size,
|
||||
st.st_blksize);
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -18,6 +18,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "fasthash.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -31,11 +33,15 @@ namespace fs
|
||||
void
|
||||
recompute(struct stat *st_)
|
||||
{
|
||||
/* not ideal to do this at runtime but likely gets optimized out */
|
||||
if(sizeof(st_->st_ino) == 4)
|
||||
st_->st_ino |= ((uint32_t)st_->st_dev << 16);
|
||||
else
|
||||
st_->st_ino |= ((uint64_t)st_->st_dev << 32);
|
||||
uint64_t buf[5];
|
||||
|
||||
buf[0] = st_->st_ino;
|
||||
buf[1] = st_->st_dev;
|
||||
buf[2] = buf[0] ^ buf[1];
|
||||
buf[3] = buf[0] & buf[1];
|
||||
buf[4] = buf[0] | buf[1];
|
||||
|
||||
st_->st_ino = fasthash64(&buf[0],sizeof(buf),MAGIC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "errno.hpp"
|
||||
#include "fileinfo.hpp"
|
||||
#include "fs_copy_file_range.hpp"
|
||||
|
||||
#include <fuse.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace l
|
||||
{
|
||||
static
|
||||
ssize_t
|
||||
copy_file_range(const int fd_in_,
|
||||
off_t offset_in_,
|
||||
const int fd_out_,
|
||||
off_t offset_out_,
|
||||
size_t size_,
|
||||
int flags_)
|
||||
{
|
||||
ssize_t rv;
|
||||
|
||||
rv = fs::copy_file_range(fd_in_,
|
||||
&offset_in_,
|
||||
fd_out_,
|
||||
&offset_out_,
|
||||
size_,
|
||||
flags_);
|
||||
|
||||
return ((rv == -1) ? -errno : rv);
|
||||
}
|
||||
}
|
||||
|
||||
namespace FUSE
|
||||
{
|
||||
ssize_t
|
||||
copy_file_range(const char *path_in_,
|
||||
struct fuse_file_info *ffi_in_,
|
||||
off_t offset_in_,
|
||||
const char *path_out_,
|
||||
struct fuse_file_info *ffi_out_,
|
||||
off_t offset_out_,
|
||||
size_t size_,
|
||||
int flags_)
|
||||
{
|
||||
FileInfo *fi_in = reinterpret_cast<FileInfo*>(ffi_in_->fh);
|
||||
FileInfo *fi_out = reinterpret_cast<FileInfo*>(ffi_out_->fh);
|
||||
|
||||
return l::copy_file_range(fi_in->fd,
|
||||
offset_in_,
|
||||
fi_out->fd,
|
||||
offset_out_,
|
||||
size_,
|
||||
flags_);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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
|
||||
|
||||
namespace FUSE
|
||||
{
|
||||
ssize_t
|
||||
copy_file_range(const char *path_in,
|
||||
struct fuse_file_info *ffi_in,
|
||||
off_t offset_in,
|
||||
const char *path_out,
|
||||
struct fuse_file_info *ffi_out,
|
||||
off_t offset_out,
|
||||
size_t size,
|
||||
int flags);
|
||||
}
|
||||
+60
-1
@@ -31,9 +31,64 @@
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
typedef Config::CacheFiles CacheFiles;
|
||||
|
||||
namespace l
|
||||
{
|
||||
/*
|
||||
The kernel expects being able to issue read requests when running
|
||||
with writeback caching enabled so we must change O_WRONLY to
|
||||
O_RDWR.
|
||||
|
||||
With writeback caching enabled the kernel handles O_APPEND. Could
|
||||
be an issue if the underlying file changes out of band but that is
|
||||
true of any caching.
|
||||
*/
|
||||
static
|
||||
void
|
||||
tweak_flags_writeback_cache(int *flags_)
|
||||
{
|
||||
if((*flags_ & O_ACCMODE) == O_WRONLY)
|
||||
*flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR);
|
||||
if(*flags_ & O_APPEND)
|
||||
*flags_ &= ~O_APPEND;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
config_to_ffi_flags(const Config &config_,
|
||||
fuse_file_info *ffi_)
|
||||
{
|
||||
switch(config_.cache_files)
|
||||
{
|
||||
case CacheFiles::LIBFUSE:
|
||||
ffi_->direct_io = config_.direct_io;
|
||||
ffi_->keep_cache = config_.kernel_cache;
|
||||
ffi_->auto_cache = config_.auto_cache;
|
||||
break;
|
||||
case CacheFiles::OFF:
|
||||
ffi_->direct_io = 1;
|
||||
ffi_->keep_cache = 0;
|
||||
ffi_->auto_cache = 0;
|
||||
break;
|
||||
case CacheFiles::PARTIAL:
|
||||
ffi_->direct_io = 0;
|
||||
ffi_->keep_cache = 0;
|
||||
ffi_->auto_cache = 0;
|
||||
break;
|
||||
case CacheFiles::FULL:
|
||||
ffi_->direct_io = 0;
|
||||
ffi_->keep_cache = 1;
|
||||
ffi_->auto_cache = 0;
|
||||
break;
|
||||
case CacheFiles::AUTO_FULL:
|
||||
ffi_->direct_io = 0;
|
||||
ffi_->keep_cache = 0;
|
||||
ffi_->auto_cache = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
create_core(const string &fullpath_,
|
||||
@@ -123,7 +178,11 @@ namespace FUSE
|
||||
const ugid::Set ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.branches_lock);
|
||||
|
||||
ffi_->direct_io = config.direct_io;
|
||||
l::config_to_ffi_flags(config,ffi_);
|
||||
|
||||
if(config.writeback_cache)
|
||||
l::tweak_flags_writeback_cache(&ffi_->flags);
|
||||
|
||||
return l::create(config.getattr,
|
||||
config.create,
|
||||
config.branches,
|
||||
|
||||
@@ -225,6 +225,26 @@ namespace l
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
getxattr_controlfile(const Config::CacheFiles &cache_files_,
|
||||
string &attrvalue_)
|
||||
{
|
||||
attrvalue_ = (string)cache_files_;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
getxattr_controlfile(const uint16_t &uint16_,
|
||||
string &attrvalue_)
|
||||
{
|
||||
std::ostringstream os;
|
||||
|
||||
os << uint16_;
|
||||
|
||||
attrvalue_ = os.str();
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
getxattr_controlfile_policies(const Config &config,
|
||||
@@ -346,6 +366,12 @@ namespace l
|
||||
l::getxattr_controlfile_pid(attrvalue);
|
||||
else if(attr[2] == "direct_io")
|
||||
l::getxattr_controlfile_bool(config.direct_io,attrvalue);
|
||||
else if(attr[2] == "posix_acl")
|
||||
l::getxattr_controlfile_bool(config.posix_acl,attrvalue);
|
||||
else if(attr[2] == "async_read")
|
||||
l::getxattr_controlfile_bool(config.async_read,attrvalue);
|
||||
else if(attr[2] == "fuse_msg_size")
|
||||
l::getxattr_controlfile(config.fuse_msg_size,attrvalue);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
@@ -363,6 +389,14 @@ namespace l
|
||||
l::getxattr_controlfile_cache_entry(attrvalue);
|
||||
else if((attr[2] == "cache") && (attr[3] == "negative_entry"))
|
||||
l::getxattr_controlfile_cache_negative_entry(attrvalue);
|
||||
else if((attr[2] == "cache") && (attr[3] == "symlinks"))
|
||||
l::getxattr_controlfile_bool(config.cache_symlinks,attrvalue);
|
||||
else if((attr[2] == "cache") && (attr[3] == "readdir"))
|
||||
l::getxattr_controlfile_bool(config.cache_readdir,attrvalue);
|
||||
else if((attr[2] == "cache") && (attr[3] == "files"))
|
||||
l::getxattr_controlfile(config.cache_files,attrvalue);
|
||||
else if((attr[2] == "cache") && (attr[3] == "writeback"))
|
||||
l::getxattr_controlfile_bool(config.writeback_cache,attrvalue);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+73
-6
@@ -19,19 +19,86 @@
|
||||
|
||||
#include <fuse.h>
|
||||
|
||||
namespace l
|
||||
{
|
||||
static
|
||||
void
|
||||
want(fuse_conn_info *conn_,
|
||||
const int flag_)
|
||||
{
|
||||
conn_->want |= flag_;
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
capable(fuse_conn_info *conn_,
|
||||
const int flag_)
|
||||
{
|
||||
return !!(conn_->capable & flag_);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
want_if_capable(fuse_conn_info *conn_,
|
||||
const int flag_)
|
||||
{
|
||||
if(capable(conn_,flag_))
|
||||
want(conn_,flag_);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
want_if_capable(fuse_conn_info *conn_,
|
||||
const int flag_,
|
||||
bool *want_)
|
||||
{
|
||||
if(*want_ && l::capable(conn_,flag_))
|
||||
{
|
||||
l::want(conn_,flag_);
|
||||
return;
|
||||
}
|
||||
|
||||
*want_ = false;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
want_if_capable_max_pages(fuse_conn_info *conn_,
|
||||
Config &c_)
|
||||
{
|
||||
if(l::capable(conn_,FUSE_CAP_MAX_PAGES))
|
||||
{
|
||||
l::want(conn_,FUSE_CAP_MAX_PAGES);
|
||||
conn_->max_pages = c_.fuse_msg_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
c_.fuse_msg_size = FUSE_DEFAULT_MAX_PAGES_PER_REQ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FUSE
|
||||
{
|
||||
void *
|
||||
init(fuse_conn_info *conn_)
|
||||
{
|
||||
Config &c = Config::get_writable();
|
||||
|
||||
ugid::init();
|
||||
|
||||
conn_->want |= FUSE_CAP_ASYNC_READ;
|
||||
conn_->want |= FUSE_CAP_ATOMIC_O_TRUNC;
|
||||
conn_->want |= FUSE_CAP_BIG_WRITES;
|
||||
conn_->want |= FUSE_CAP_DONT_MASK;
|
||||
conn_->want |= FUSE_CAP_IOCTL_DIR;
|
||||
l::want_if_capable(conn_,FUSE_CAP_ASYNC_DIO);
|
||||
l::want_if_capable(conn_,FUSE_CAP_ASYNC_READ,&c.async_read);
|
||||
l::want_if_capable(conn_,FUSE_CAP_ATOMIC_O_TRUNC);
|
||||
l::want_if_capable(conn_,FUSE_CAP_BIG_WRITES);
|
||||
l::want_if_capable(conn_,FUSE_CAP_CACHE_SYMLINKS,&c.cache_symlinks);
|
||||
l::want_if_capable(conn_,FUSE_CAP_DONT_MASK);
|
||||
l::want_if_capable(conn_,FUSE_CAP_IOCTL_DIR);
|
||||
l::want_if_capable(conn_,FUSE_CAP_PARALLEL_DIROPS);
|
||||
l::want_if_capable(conn_,FUSE_CAP_POSIX_ACL,&c.posix_acl);
|
||||
l::want_if_capable(conn_,FUSE_CAP_WRITEBACK_CACHE,&c.writeback_cache);
|
||||
l::want_if_capable_max_pages(conn_,c);
|
||||
|
||||
return &Config::get_writable();
|
||||
return &c;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -107,12 +107,13 @@ namespace l
|
||||
void *data_,
|
||||
uint32_t *out_bufsz_)
|
||||
{
|
||||
FileInfo *fi = reinterpret_cast<FileInfo*>(ffi_->fh);
|
||||
FileInfo *fi = reinterpret_cast<FileInfo*>(ffi_->fh);
|
||||
const fuse_context *fc = fuse_get_context();
|
||||
const ugid::Set ugid(fc->uid,fc->gid);
|
||||
|
||||
return l::ioctl(fi->fd,cmd_,data_,out_bufsz_);
|
||||
}
|
||||
|
||||
|
||||
#ifndef O_NOATIME
|
||||
#define O_NOATIME 0
|
||||
#endif
|
||||
|
||||
@@ -44,14 +44,20 @@ namespace l
|
||||
string xattrs;
|
||||
const vector<string> strs =
|
||||
buildvector<string>
|
||||
("user.mergerfs.async_read")
|
||||
("user.mergerfs.branches")
|
||||
("user.mergerfs.cache.attr")
|
||||
("user.mergerfs.cache.entry")
|
||||
("user.mergerfs.cache.files")
|
||||
("user.mergerfs.cache.negative_entry")
|
||||
("user.mergerfs.cache.open")
|
||||
("user.mergerfs.cache.readdir")
|
||||
("user.mergerfs.cache.statfs")
|
||||
("user.mergerfs.cache.symlinks")
|
||||
("user.mergerfs.cache.writeback")
|
||||
("user.mergerfs.direct_io")
|
||||
("user.mergerfs.dropcacheonclose")
|
||||
("user.mergerfs.fuse_msg_size")
|
||||
("user.mergerfs.ignorepponrename")
|
||||
("user.mergerfs.link_cow")
|
||||
("user.mergerfs.minfreespace")
|
||||
@@ -59,6 +65,7 @@ namespace l
|
||||
("user.mergerfs.nullrw")
|
||||
("user.mergerfs.pid")
|
||||
("user.mergerfs.policies")
|
||||
("user.mergerfs.posix_acl")
|
||||
("user.mergerfs.security_capability")
|
||||
("user.mergerfs.srcmounts")
|
||||
("user.mergerfs.statfs")
|
||||
|
||||
+60
-1
@@ -31,9 +31,64 @@
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
typedef Config::CacheFiles CacheFiles;
|
||||
|
||||
namespace l
|
||||
{
|
||||
/*
|
||||
The kernel expects being able to issue read requests when running
|
||||
with writeback caching enabled so we must change O_WRONLY to
|
||||
O_RDWR.
|
||||
|
||||
With writeback caching enabled the kernel handles O_APPEND. Could
|
||||
be an issue if the underlying file changes out of band but that is
|
||||
true of any caching.
|
||||
*/
|
||||
static
|
||||
void
|
||||
tweak_flags_writeback_cache(int *flags_)
|
||||
{
|
||||
if((*flags_ & O_ACCMODE) == O_WRONLY)
|
||||
*flags_ = ((*flags_ & ~O_ACCMODE) | O_RDWR);
|
||||
if(*flags_ & O_APPEND)
|
||||
*flags_ &= ~O_APPEND;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
config_to_ffi_flags(const Config &config_,
|
||||
fuse_file_info *ffi_)
|
||||
{
|
||||
switch(config_.cache_files)
|
||||
{
|
||||
case CacheFiles::LIBFUSE:
|
||||
ffi_->direct_io = config_.direct_io;
|
||||
ffi_->keep_cache = config_.kernel_cache;
|
||||
ffi_->auto_cache = config_.auto_cache;
|
||||
break;
|
||||
case CacheFiles::OFF:
|
||||
ffi_->direct_io = 1;
|
||||
ffi_->keep_cache = 0;
|
||||
ffi_->auto_cache = 0;
|
||||
break;
|
||||
case CacheFiles::PARTIAL:
|
||||
ffi_->direct_io = 0;
|
||||
ffi_->keep_cache = 0;
|
||||
ffi_->auto_cache = 0;
|
||||
break;
|
||||
case CacheFiles::FULL:
|
||||
ffi_->direct_io = 0;
|
||||
ffi_->keep_cache = 1;
|
||||
ffi_->auto_cache = 0;
|
||||
break;
|
||||
case CacheFiles::AUTO_FULL:
|
||||
ffi_->direct_io = 0;
|
||||
ffi_->keep_cache = 0;
|
||||
ffi_->auto_cache = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
open_core(const string &basepath_,
|
||||
@@ -92,7 +147,11 @@ namespace FUSE
|
||||
const ugid::Set ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.branches_lock);
|
||||
|
||||
ffi_->direct_io = config.direct_io;
|
||||
l::config_to_ffi_flags(config,ffi_);
|
||||
|
||||
if(config.writeback_cache)
|
||||
l::tweak_flags_writeback_cache(&ffi_->flags);
|
||||
|
||||
return l::open(config.open,
|
||||
config.open_cache,
|
||||
config.branches,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.hpp"
|
||||
#include "dirinfo.hpp"
|
||||
|
||||
#include <fuse.h>
|
||||
@@ -24,8 +25,16 @@ namespace FUSE
|
||||
opendir(const char *fusepath_,
|
||||
fuse_file_info *ffi_)
|
||||
{
|
||||
const Config &config = Config::get();
|
||||
|
||||
ffi_->fh = reinterpret_cast<uint64_t>(new DirInfo(fusepath_));
|
||||
|
||||
if(config.cache_readdir)
|
||||
{
|
||||
ffi_->keep_cache = 1;
|
||||
ffi_->cache_readdir = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,7 @@ namespace FUSE
|
||||
{
|
||||
int
|
||||
prepare_hide(const char *fusepath_,
|
||||
uint64_t *fh_,
|
||||
int type_)
|
||||
uint64_t *fh_)
|
||||
{
|
||||
int rv;
|
||||
struct fuse_file_info ffi = {0};
|
||||
|
||||
@@ -24,6 +24,5 @@ namespace FUSE
|
||||
{
|
||||
int
|
||||
prepare_hide(const char *name_,
|
||||
uint64_t *fh_,
|
||||
int type_);
|
||||
uint64_t *fh_);
|
||||
}
|
||||
|
||||
+25
-1
@@ -178,7 +178,7 @@ namespace l
|
||||
int
|
||||
setxattr_xattr(const string &attrval_,
|
||||
const int flags_,
|
||||
int xattr_)
|
||||
int &xattr_)
|
||||
{
|
||||
if((flags_ & XATTR_CREATE) == XATTR_CREATE)
|
||||
return -EEXIST;
|
||||
@@ -235,6 +235,26 @@ namespace l
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
setxattr(const string &attrval_,
|
||||
const int flags_,
|
||||
Config::CacheFiles &cache_files_)
|
||||
{
|
||||
Config::CacheFiles tmp;
|
||||
|
||||
if((flags_ & XATTR_CREATE) == XATTR_CREATE)
|
||||
return -EEXIST;
|
||||
|
||||
tmp = attrval_;
|
||||
if(!tmp.valid())
|
||||
return -EINVAL;
|
||||
|
||||
cache_files_ = tmp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
setxattr_controlfile_func_policy(Config &config,
|
||||
@@ -436,6 +456,10 @@ namespace l
|
||||
return l::setxattr_controlfile_cache_entry(attrval,flags);
|
||||
else if((attr[2] == "cache") && (attr[3] == "negative_entry"))
|
||||
return l::setxattr_controlfile_cache_negative_entry(attrval,flags);
|
||||
else if((attr[2] == "cache") && (attr[3] == "readdir"))
|
||||
return l::setxattr_bool(attrval,flags,config.cache_readdir);
|
||||
else if((attr[2] == "cache") && (attr[3] == "files"))
|
||||
return l::setxattr(attrval,flags,config.cache_files);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
+51
-58
@@ -22,6 +22,7 @@
|
||||
#include "fuse_access.hpp"
|
||||
#include "fuse_chmod.hpp"
|
||||
#include "fuse_chown.hpp"
|
||||
#include "fuse_copy_file_range.hpp"
|
||||
#include "fuse_create.hpp"
|
||||
#include "fuse_destroy.hpp"
|
||||
#include "fuse_fallocate.hpp"
|
||||
@@ -82,64 +83,56 @@ namespace l
|
||||
ops.flag_nopath = true;
|
||||
ops.flag_utime_omit_ok = true;
|
||||
|
||||
ops.prepare_hide = FUSE::prepare_hide;
|
||||
ops.free_hide = FUSE::free_hide;
|
||||
ops.fchown = FUSE::fchown;
|
||||
ops.fchmod = FUSE::fchmod;
|
||||
ops.futimens = FUSE::futimens;
|
||||
|
||||
ops.access = FUSE::access;
|
||||
ops.bmap = NULL;
|
||||
ops.chmod = FUSE::chmod;
|
||||
ops.chown = FUSE::chown;
|
||||
ops.create = FUSE::create;
|
||||
ops.destroy = FUSE::destroy;
|
||||
ops.fallocate = FUSE::fallocate;
|
||||
ops.fgetattr = FUSE::fgetattr;
|
||||
ops.flock = FUSE::flock;
|
||||
ops.flush = FUSE::flush;
|
||||
ops.fsync = FUSE::fsync;
|
||||
ops.fsyncdir = FUSE::fsyncdir;
|
||||
ops.ftruncate = FUSE::ftruncate;
|
||||
ops.getattr = FUSE::getattr;
|
||||
ops.getdir = NULL; /* deprecated; use readdir */
|
||||
ops.getxattr = FUSE::getxattr;
|
||||
ops.init = FUSE::init;
|
||||
ops.ioctl = FUSE::ioctl;
|
||||
ops.link = FUSE::link;
|
||||
ops.listxattr = FUSE::listxattr;
|
||||
ops.lock = NULL;
|
||||
ops.mkdir = FUSE::mkdir;
|
||||
ops.mknod = FUSE::mknod;
|
||||
ops.open = FUSE::open;
|
||||
ops.opendir = FUSE::opendir;
|
||||
ops.poll = NULL;
|
||||
ops.read = (nullrw ?
|
||||
FUSE::read_null :
|
||||
FUSE::read);
|
||||
ops.read_buf = (nullrw ?
|
||||
NULL :
|
||||
FUSE::read_buf);
|
||||
ops.readdir = FUSE::readdir;
|
||||
ops.readlink = FUSE::readlink;
|
||||
ops.release = FUSE::release;
|
||||
ops.releasedir = FUSE::releasedir;
|
||||
ops.removexattr = FUSE::removexattr;
|
||||
ops.rename = FUSE::rename;
|
||||
ops.rmdir = FUSE::rmdir;
|
||||
ops.setxattr = FUSE::setxattr;
|
||||
ops.statfs = FUSE::statfs;
|
||||
ops.symlink = FUSE::symlink;
|
||||
ops.truncate = FUSE::truncate;
|
||||
ops.unlink = FUSE::unlink;
|
||||
ops.utime = NULL; /* deprecated; use utimens() */
|
||||
ops.utimens = FUSE::utimens;
|
||||
ops.write = (nullrw ?
|
||||
FUSE::write_null :
|
||||
FUSE::write);
|
||||
ops.write_buf = (nullrw ?
|
||||
FUSE::write_buf_null :
|
||||
FUSE::write_buf);
|
||||
ops.access = FUSE::access;
|
||||
ops.bmap = NULL;
|
||||
ops.chmod = FUSE::chmod;
|
||||
ops.chown = FUSE::chown;
|
||||
ops.copy_file_range = FUSE::copy_file_range;
|
||||
ops.create = FUSE::create;
|
||||
ops.destroy = FUSE::destroy;
|
||||
ops.fallocate = FUSE::fallocate;
|
||||
ops.fchmod = FUSE::fchmod;
|
||||
ops.fchown = FUSE::fchown;
|
||||
ops.fgetattr = FUSE::fgetattr;
|
||||
ops.flock = NULL; // FUSE::flock;
|
||||
ops.flush = FUSE::flush;
|
||||
ops.free_hide = FUSE::free_hide;
|
||||
ops.fsync = FUSE::fsync;
|
||||
ops.fsyncdir = FUSE::fsyncdir;
|
||||
ops.ftruncate = FUSE::ftruncate;
|
||||
ops.futimens = FUSE::futimens;
|
||||
ops.getattr = FUSE::getattr;
|
||||
ops.getdir = NULL; /* deprecated; use readdir */
|
||||
ops.getxattr = FUSE::getxattr;
|
||||
ops.init = FUSE::init;
|
||||
ops.ioctl = FUSE::ioctl;
|
||||
ops.link = FUSE::link;
|
||||
ops.listxattr = FUSE::listxattr;
|
||||
ops.lock = NULL;
|
||||
ops.mkdir = FUSE::mkdir;
|
||||
ops.mknod = FUSE::mknod;
|
||||
ops.open = FUSE::open;
|
||||
ops.opendir = FUSE::opendir;
|
||||
ops.poll = NULL;
|
||||
ops.prepare_hide = FUSE::prepare_hide;
|
||||
ops.read = (nullrw ? FUSE::read_null : FUSE::read);
|
||||
ops.read_buf = (nullrw ? NULL : FUSE::read_buf);
|
||||
ops.readdir = FUSE::readdir;
|
||||
ops.readlink = FUSE::readlink;
|
||||
ops.release = FUSE::release;
|
||||
ops.releasedir = FUSE::releasedir;
|
||||
ops.removexattr = FUSE::removexattr;
|
||||
ops.rename = FUSE::rename;
|
||||
ops.rmdir = FUSE::rmdir;
|
||||
ops.setxattr = FUSE::setxattr;
|
||||
ops.statfs = FUSE::statfs;
|
||||
ops.symlink = FUSE::symlink;
|
||||
ops.truncate = FUSE::truncate;
|
||||
ops.unlink = FUSE::unlink;
|
||||
ops.utime = NULL; /* deprecated; use utimens() */
|
||||
ops.utimens = FUSE::utimens;
|
||||
ops.write = (nullrw ? FUSE::write_null : FUSE::write);
|
||||
ops.write_buf = (nullrw ? FUSE::write_buf_null : FUSE::write_buf);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
+115
-21
@@ -99,11 +99,31 @@ static
|
||||
void
|
||||
set_default_options(fuse_args *args)
|
||||
{
|
||||
set_option(args,"atomic_o_trunc");
|
||||
set_option(args,"big_writes");
|
||||
set_option(args,"default_permissions");
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
parse_and_process(const std::string &value_,
|
||||
uint16_t &uint16_,
|
||||
uint16_t min_,
|
||||
uint16_t max_)
|
||||
{
|
||||
int rv;
|
||||
uint64_t uint64;
|
||||
|
||||
rv = num::to_uint64_t(value_,uint64);
|
||||
if(rv == -1)
|
||||
return 1;
|
||||
|
||||
if((uint64 > max_) || (uint64 < min_))
|
||||
return 1;
|
||||
|
||||
uint16_ = uint64;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
parse_and_process(const std::string &value_,
|
||||
@@ -134,13 +154,13 @@ parse_and_process(const std::string &value,
|
||||
|
||||
static
|
||||
int
|
||||
parse_and_process(const std::string &value,
|
||||
bool &boolean)
|
||||
parse_and_process(const std::string &value_,
|
||||
bool &boolean_)
|
||||
{
|
||||
if(value == "false")
|
||||
boolean = false;
|
||||
else if(value == "true")
|
||||
boolean = true;
|
||||
if((value_ == "false") || (value_ == "0") || (value_ == "off"))
|
||||
boolean_ = false;
|
||||
else if((value_ == "true") || (value_ == "1") || (value_ == "on"))
|
||||
boolean_ = true;
|
||||
else
|
||||
return 1;
|
||||
|
||||
@@ -157,6 +177,22 @@ parse_and_process(const std::string &value_,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
parse_and_process(const std::string &value_,
|
||||
Config::CacheFiles &cache_files_)
|
||||
{
|
||||
Config::CacheFiles tmp;
|
||||
|
||||
tmp = value_;
|
||||
if(!tmp.valid())
|
||||
return 1;
|
||||
|
||||
cache_files_ = tmp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
parse_and_process_errno(const std::string &value_,
|
||||
@@ -239,6 +275,14 @@ parse_and_process_cache(Config &config_,
|
||||
return (set_kv_option(outargs,"negative_timeout",value_),0);
|
||||
else if(func_ == "attr")
|
||||
return (set_kv_option(outargs,"attr_timeout",value_),0);
|
||||
else if(func_ == "symlinks")
|
||||
return parse_and_process(value_,config_.cache_symlinks);
|
||||
else if(func_ == "readdir")
|
||||
return parse_and_process(value_,config_.cache_readdir);
|
||||
else if(func_ == "files")
|
||||
return parse_and_process(value_,config_.cache_files);
|
||||
else if(func_ == "writeback")
|
||||
return parse_and_process(value_,config_.writeback_cache);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -250,8 +294,22 @@ parse_and_process_arg(Config &config,
|
||||
{
|
||||
if(arg == "defaults")
|
||||
return 0;
|
||||
else if(arg == "hard_remove")
|
||||
return 0;
|
||||
else if(arg == "direct_io")
|
||||
return (config.direct_io=true,0);
|
||||
else if(arg == "kernel_cache")
|
||||
return (config.kernel_cache=true,0);
|
||||
else if(arg == "auto_cache")
|
||||
return (config.auto_cache=true,0);
|
||||
else if(arg == "async_read")
|
||||
return (config.async_read=true,0);
|
||||
else if(arg == "sync_read")
|
||||
return (config.async_read=false,0);
|
||||
else if(arg == "atomic_o_trunc")
|
||||
return 0;
|
||||
else if(arg == "big_writes")
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -305,6 +363,22 @@ parse_and_process_kv_arg(Config &config,
|
||||
rv = parse_and_process_statfsignore(value,config.statfs_ignore);
|
||||
else if(key == "fsname")
|
||||
rv = parse_and_process(value,config.fsname);
|
||||
else if(key == "posix_acl")
|
||||
rv = parse_and_process(value,config.posix_acl);
|
||||
else if(key == "direct_io")
|
||||
rv = parse_and_process(value,config.direct_io);
|
||||
else if(key == "kernel_cache")
|
||||
rv = parse_and_process(value,config.kernel_cache);
|
||||
else if(key == "auto_cache")
|
||||
rv = parse_and_process(value,config.auto_cache);
|
||||
else if(key == "async_read")
|
||||
rv = parse_and_process(value,config.async_read);
|
||||
else if(key == "max_write")
|
||||
rv = 0;
|
||||
else if(key == "fuse_msg_size")
|
||||
rv = parse_and_process(value,config.fuse_msg_size,
|
||||
1,
|
||||
FUSE_MAX_MAX_PAGES);
|
||||
}
|
||||
|
||||
if(rv == -1)
|
||||
@@ -366,14 +440,14 @@ void
|
||||
usage(void)
|
||||
{
|
||||
std::cout <<
|
||||
"Usage: mergerfs [options] <srcpaths> <destpath>\n"
|
||||
"Usage: mergerfs [options] <branches> <destpath>\n"
|
||||
"\n"
|
||||
" -o [opt,...] mount options\n"
|
||||
" -h --help print help\n"
|
||||
" -v --version print version\n"
|
||||
"\n"
|
||||
"mergerfs options:\n"
|
||||
" <srcpaths> ':' delimited list of directories. Supports\n"
|
||||
" <branches> ':' delimited list of directories. Supports\n"
|
||||
" shell globbing (must be escaped in shell)\n"
|
||||
" -o func.<f>=<p> Set function <f> to policy <p>\n"
|
||||
" -o category.<c>=<p> Set functions in category <c> to <p>\n"
|
||||
@@ -381,38 +455,53 @@ usage(void)
|
||||
" default = 0 (disabled)\n"
|
||||
" -o cache.statfs=<int> 'statfs' cache timeout in seconds. Used by\n"
|
||||
" policies. default = 0 (disabled)\n"
|
||||
" -o cache.attr=<int> file attribute cache timeout in seconds.\n"
|
||||
" -o cache.files=libfuse|off|partial|full|auto-full\n"
|
||||
" * libfuse: Use direct_io, kernel_cache, auto_cache\n"
|
||||
" values directly\n"
|
||||
" * off: Disable page caching\n"
|
||||
" * partial: Clear page cache on file open\n"
|
||||
" * full: Keep cache on file open\n"
|
||||
" * auto-full: Keep cache if mtime & size not changed\n"
|
||||
" default = libfuse\n"
|
||||
" -o cache.writeback=<bool>\n"
|
||||
" Enable kernel writeback caching (if supported)\n"
|
||||
" cache.files must must be enabled as well.\n"
|
||||
" default = false\n"
|
||||
" -o cache.symlinks=<bool>\n"
|
||||
" Enable kernel caching of symlinks (if supported)\n"
|
||||
" default = false\n"
|
||||
" -o cache.readdir=<bool>\n"
|
||||
" Enable kernel caching readdir (if supported)\n"
|
||||
" default = false\n"
|
||||
" -o cache.attr=<int> File attribute cache timeout in seconds.\n"
|
||||
" default = 1\n"
|
||||
" -o cache.entry=<int> file name lookup cache timeout in seconds.\n"
|
||||
" -o cache.entry=<int> File name lookup cache timeout in seconds.\n"
|
||||
" default = 1\n"
|
||||
" -o cache.negative_entry=<int>\n"
|
||||
" negative file name lookup cache timeout in\n"
|
||||
" Negative file name lookup cache timeout in\n"
|
||||
" seconds. default = 0\n"
|
||||
" -o direct_io Bypass page caching, may increase write\n"
|
||||
" speeds at the cost of reads. Please read docs\n"
|
||||
" for more details as there are tradeoffs.\n"
|
||||
" -o use_ino Have mergerfs generate inode values rather than\n"
|
||||
" autogenerated by libfuse. Suggested.\n"
|
||||
" -o minfreespace=<int> minimum free space needed for certain policies.\n"
|
||||
" -o minfreespace=<int> Minimum free space needed for certain policies.\n"
|
||||
" default = 4G\n"
|
||||
" -o moveonenospc=<bool> Try to move file to another drive when ENOSPC\n"
|
||||
" on write. default = false\n"
|
||||
" -o dropcacheonclose=<bool>\n"
|
||||
" When a file is closed suggest to OS it drop\n"
|
||||
" the file's cache. This is useful when direct_io\n"
|
||||
" is disabled. default = false\n"
|
||||
" the file's cache. This is useful when using\n"
|
||||
" 'cache.files'. default = false\n"
|
||||
" -o symlinkify=<bool> Read-only files, after a timeout, will be turned\n"
|
||||
" into symlinks. Read docs for limitations and\n"
|
||||
" possible issues. default = false\n"
|
||||
" -o symlinkify_timeout=<int>\n"
|
||||
" timeout in seconds before will turn to symlinks.\n"
|
||||
" Timeout in seconds before files turn to symlinks.\n"
|
||||
" default = 3600\n"
|
||||
" -o nullrw=<bool> Disables reads and writes. For benchmarking.\n"
|
||||
" default = false\n"
|
||||
" -o ignorepponrename=<bool>\n"
|
||||
" Ignore path preserving when performing renames\n"
|
||||
" and links. default = false\n"
|
||||
" -o link_cow=<bool> delink/clone file on open to simulate CoW.\n"
|
||||
" -o link_cow=<bool> Delink/clone file on open to simulate CoW.\n"
|
||||
" default = false\n"
|
||||
" -o security_capability=<bool>\n"
|
||||
" When disabled return ENOATTR when the xattr\n"
|
||||
@@ -433,6 +522,11 @@ usage(void)
|
||||
" as 'read only' or 'no create'. 'nc' will ignore\n"
|
||||
" available space for branches tagged as\n"
|
||||
" 'no create'. default = none\n"
|
||||
" -o posix_acl=<bool> Enable POSIX ACL support. default = false\n"
|
||||
" -o async_read=<bool> If disabled or unavailable the kernel will\n"
|
||||
" ensure there is at most one pending read \n"
|
||||
" request per file and will attempt to order\n"
|
||||
" requests by offset. default = true\n"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,15 @@ PolicyCache::Value::Value()
|
||||
PolicyCache::PolicyCache(void)
|
||||
: timeout(DEFAULT_TIMEOUT)
|
||||
{
|
||||
pthread_mutex_init(&_lock,NULL);
|
||||
}
|
||||
|
||||
void
|
||||
PolicyCache::erase(const char *fusepath_)
|
||||
{
|
||||
if(timeout == 0)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&_lock);
|
||||
|
||||
_cache.erase(fusepath_);
|
||||
@@ -56,6 +60,9 @@ PolicyCache::cleanup(const int prob_)
|
||||
uint64_t now;
|
||||
map<string,Value>::iterator i;
|
||||
|
||||
if(timeout == 0)
|
||||
return;
|
||||
|
||||
if(rand() % prob_)
|
||||
return;
|
||||
|
||||
|
||||
@@ -25,17 +25,20 @@ using std::string;
|
||||
using std::vector;
|
||||
|
||||
int
|
||||
Policy::Func::eprand(const Category::Enum::Type type,
|
||||
Policy::Func::eprand(const Category::Enum::Type type_,
|
||||
const Branches &branches_,
|
||||
const char *fusepath,
|
||||
const uint64_t minfreespace,
|
||||
vector<const string*> &paths)
|
||||
const char *fusepath_,
|
||||
const uint64_t minfreespace_,
|
||||
vector<const string*> &paths_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = Policy::Func::epall(type,branches_,fusepath,minfreespace,paths);
|
||||
rv = Policy::Func::epall(type_,branches_,fusepath_,minfreespace_,paths_);
|
||||
if(rv == 0)
|
||||
std::random_shuffle(paths.begin(),paths.end());
|
||||
{
|
||||
std::random_shuffle(paths_.begin(),paths_.end());
|
||||
paths_.resize(1);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
+9
-6
@@ -25,17 +25,20 @@ using std::string;
|
||||
using std::vector;
|
||||
|
||||
int
|
||||
Policy::Func::rand(const Category::Enum::Type type,
|
||||
Policy::Func::rand(const Category::Enum::Type type_,
|
||||
const Branches &branches_,
|
||||
const char *fusepath,
|
||||
const uint64_t minfreespace,
|
||||
vector<const string*> &paths)
|
||||
const char *fusepath_,
|
||||
const uint64_t minfreespace_,
|
||||
vector<const string*> &paths_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
rv = Policy::Func::all(type,branches_,fusepath,minfreespace,paths);
|
||||
rv = Policy::Func::all(type_,branches_,fusepath_,minfreespace_,paths_);
|
||||
if(rv == 0)
|
||||
std::random_shuffle(paths.begin(),paths.end());
|
||||
{
|
||||
std::random_shuffle(paths_.begin(),paths_.end());
|
||||
paths_.resize(1);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
+16
-39
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
@@ -18,9 +18,12 @@ import sys
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
def call(args):
|
||||
return subprocess.Popen(args,stdout=subprocess.PIPE).communicate()[0].decode()
|
||||
|
||||
def git_tags():
|
||||
args = ["git", "tag", '-l']
|
||||
tags = subprocess.check_output(args)
|
||||
tags = call(args)
|
||||
tags = [[int(X) for X in tag.split(".")] for tag in tags.split()]
|
||||
tags.sort()
|
||||
tags.reverse()
|
||||
@@ -29,20 +32,20 @@ def git_tags():
|
||||
|
||||
def git_log(fromtag,totag):
|
||||
args = ['git','log','--no-merges','--oneline',fromtag+'...'+totag]
|
||||
return subprocess.check_output(args).strip().split('\n')
|
||||
return call(args).strip().split('\n')
|
||||
|
||||
def git_author_and_time(tag):
|
||||
args = ['git','log','-1','--format=-- %an <%ae> %cD',tag]
|
||||
return subprocess.check_output(args).strip()
|
||||
return call(args).strip()
|
||||
|
||||
def git_version():
|
||||
args = ['git','describe','--always','--tags','--dirty']
|
||||
return subprocess.check_output(args).strip()
|
||||
return call(args).strip()
|
||||
|
||||
def guess_distro():
|
||||
try:
|
||||
args = ['lsb_release','-i','-s']
|
||||
return subprocess.check_output(args).strip().lower()
|
||||
return call(args).strip().lower()
|
||||
except:
|
||||
return 'unknown'
|
||||
|
||||
@@ -50,38 +53,12 @@ def guess_distro():
|
||||
def guess_codename():
|
||||
try:
|
||||
args = ['lsb_release','-c','-s']
|
||||
return subprocess.check_output(args).strip().lower()
|
||||
return call(args).strip().lower()
|
||||
except:
|
||||
return 'unknown'
|
||||
|
||||
|
||||
def patch_subprocess():
|
||||
if "check_output" not in dir( subprocess ): # duck punch it in!
|
||||
def check_output(*popenargs, **kwargs):
|
||||
r"""Run command with arguments and return its output as a byte string.
|
||||
|
||||
Backported from Python 2.7 as it's implemented as pure python on stdlib.
|
||||
|
||||
>>> check_output(['/usr/bin/python', '--version'])
|
||||
Python 2.6.2
|
||||
"""
|
||||
process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
|
||||
output, unused_err = process.communicate()
|
||||
retcode = process.poll()
|
||||
if retcode:
|
||||
cmd = kwargs.get("args")
|
||||
if cmd is None:
|
||||
cmd = popenargs[0]
|
||||
error = subprocess.CalledProcessError(retcode, cmd)
|
||||
error.output = output
|
||||
raise error
|
||||
return output
|
||||
|
||||
subprocess.check_output = check_output
|
||||
|
||||
def main():
|
||||
patch_subprocess()
|
||||
|
||||
parser = argparse.ArgumentParser(description='Generated debian/changelog from git log')
|
||||
parser.add_argument('--name',type=str,help='Name of package',required=True)
|
||||
parser.add_argument('--version',type=str,help='Place in git history to include upto',default='::guess::')
|
||||
@@ -107,12 +84,12 @@ def main():
|
||||
if args.version in tags:
|
||||
idx = tags.index(args.version)
|
||||
tags = tags[idx:]
|
||||
tags = zip(tags,tags)
|
||||
tags = list(zip(tags,tags))
|
||||
else:
|
||||
tags = zip(tags,tags)
|
||||
tags = list(zip(tags,tags))
|
||||
tags.insert(0,(args.version,'HEAD'))
|
||||
|
||||
for i in xrange(0,len(tags)):
|
||||
for i in range(0,len(tags)):
|
||||
tags[i] = (tags[i][0] + versuffix,tags[i][1])
|
||||
|
||||
tag = tags[0]
|
||||
@@ -122,13 +99,13 @@ def main():
|
||||
tag = prev
|
||||
continue
|
||||
|
||||
print('%s (%s) %s; urgency=%s\n' % (args.name,tag[0],args.codename,args.urgency))
|
||||
print('{0} ({1}) {2}; urgency={3}\n'.format(args.name,tag[0],args.codename,args.urgency))
|
||||
|
||||
for line in lines:
|
||||
print " * " + line
|
||||
print(" * " + line)
|
||||
|
||||
authorandtime = git_author_and_time(tag[1])
|
||||
print(' %s\n' % authorandtime)
|
||||
print(' {0}\n'.format(authorandtime))
|
||||
|
||||
tag = prev
|
||||
|
||||
|
||||
Reference in New Issue
Block a user