Compare commits

...
70 Commits
Author SHA1 Message Date
Antonio SJ Musumeci 9123d05834 fuse_lowlevel.c 2024-02-12 20:03:05 -06:00
trapexitandGitHub d68ad9ac01 Expand the preload docs (#1299) 2024-01-30 00:03:39 -06:00
trapexitandGitHub ae6c4f7c25 Rework mergerfs vs X section of readme (#1298) 2024-01-28 18:12:29 -06:00
trapexitandGitHub 87475098a1 Add credit to nohajc for preload idea (#1295) 2024-01-22 12:52:51 -06:00
trapexitandGitHub d4dc0701d6 Create ld_preload to reopen files on underlying filesystem (#1294) 2024-01-22 12:42:55 -06:00
0x6d61726bandGitHub c1c2f075ee Update README.md (#1283)
* Update README.md

Information added that mergerfs v2.35.0 and newer sets `allow_other` (but not 2.33.5, that is used by Debian 12.2)
2023-12-01 15:45:13 -06:00
trapexitandGitHub 96d34f1eab Merge pull request #1280 from trapexit/getpid
Fix user.mergerfs.pid
2023-11-11 19:48:28 -06:00
Antonio SJ Musumeci f66fb7e2d4 Fix user.mergerfs.pid 2023-11-11 19:03:32 -06:00
trapexitandGitHub edb3368078 Merge pull request #1277 from trapexit/flushdefault
Set flush-on-close default to opened-for-write
2023-10-31 23:26:08 -05:00
Antonio SJ Musumeci bd70928268 Set flush-on-close default to opened-for-write 2023-10-31 09:23:52 -05:00
trapexitandGitHub 337aade02d Merge pull request #1276 from trapexit/allowother
Only set allow_other when running as root
2023-10-29 02:54:51 -05:00
Antonio SJ Musumeci d6a2f06797 Only set allow_other when running as root 2023-10-29 01:44:18 -05:00
trapexitandGitHub 7890a4996c Merge pull request #1275 from trapexit/noflush
Add flushonclose feature
2023-10-28 19:49:17 -05:00
Antonio SJ Musumeci 6aa6452d3e Add flushonclose feature 2023-10-28 19:47:23 -05:00
trapexitandGitHub 7d6c8e9333 Merge pull request #1271 from trapexit/warn-root
Warn if not running as root
2023-10-22 00:45:41 -05:00
Antonio SJ Musumeci a7069153bd Warn if not running as root 2023-10-22 00:43:19 -05:00
trapexitandGitHub 5c71a75fdd Merge pull request #1269 from trapexit/wyhash
Update wyhash to 4.2
2023-10-21 18:04:14 -05:00
Antonio SJ Musumeci fd33df04a3 Update wyhash to 4.2 2023-10-21 18:03:05 -05:00
trapexitandGitHub a9ae36a3e4 Merge pull request #1263 from trapexit/pthread_getname_np
Remove use of pthread_getname_np to work with older musl versions
2023-10-14 01:20:00 -05:00
Antonio SJ Musumeci 569537df9e Remove use of pthread_getname_np to work with older musl versions 2023-10-14 01:15:29 -05:00
trapexitandGitHub 8534ee7ceb Merge pull request #1262 from trapexit/readdir-errs
Rework thread pool queue depth impl
2023-10-14 01:13:48 -05:00
Antonio SJ Musumeci 620cab2948 Rework thread pool queue depth impl 2023-10-14 00:58:10 -05:00
trapexitandGitHub 8635456818 Merge pull request #1254 from trapexit/typo
Fix readdir doc typos
2023-09-23 14:54:25 -05:00
Antonio SJ Musumeci 0888f372aa Fix readdir doc typos 2023-09-23 14:53:41 -05:00
trapexitandGitHub e09b10c55b Merge pull request #1249 from trapexit/tpfix
Fix thread pool destruction where threads don't explicitly exit themselves
2023-09-09 17:07:51 -05:00
Antonio SJ Musumeci 766b923116 Fix thread pool destruction where threads don't explicitly exit themselves 2023-09-09 16:48:47 -05:00
trapexitandGitHub dd7e9e2aa7 Merge pull request #1244 from trapexit/docs
Add some new questions to FAQ
2023-09-05 22:56:50 -05:00
Antonio SJ Musumeci 3fc0aa3809 Add some new questions to FAQ 2023-09-05 22:56:08 -05:00
trapexitandGitHub b1dcc94e98 Merge pull request #1242 from trapexit/debug-removal
Remove unneeded debug logging from thread pool
2023-09-04 18:40:38 -05:00
Antonio SJ Musumeci 6d2c99e487 Remove unneeded debug logging from thread pool 2023-09-04 18:39:43 -05:00
trapexitandGitHub 9d9eacfe7b Merge pull request #1241 from trapexit/version
Add website and license to version option
2023-09-04 16:02:24 -05:00
Antonio SJ Musumeci b91cc1c191 Add website and license to version option 2023-09-04 15:57:47 -05:00
trapexitandGitHub 6d2f26c60e Merge pull request #1240 from trapexit/dynamic-threadpool
Rework thread pool, add ability to add/remove threads at runtime
2023-09-03 23:37:29 -05:00
Antonio SJ Musumeci 0a94bd7cfc Rework thread pool, add ability to add/remove threads at runtime 2023-09-03 23:36:03 -05:00
trapexitandGitHub 211e6c7115 Merge pull request #1239 from trapexit/validation
Add proper input validation for readdir policy
2023-09-02 15:48:43 -05:00
Antonio SJ Musumeci 335ca55a6e Add proper input validation for readdir policy 2023-09-02 15:47:49 -05:00
trapexitandGitHub 69de4de53f Merge pull request #1238 from trapexit/boundedqueue
Change threadpool to use concurrentqueue
2023-09-02 00:47:26 -05:00
Antonio SJ Musumeci 76c8d48dbd Change threadpool to use concurrentqueue 2023-09-02 00:45:47 -05:00
trapexitandGitHub a927a15e9c Merge pull request #1237 from trapexit/threadfix
Initialize readdir threadpool after daemonizing
2023-08-28 22:48:59 -05:00
Antonio SJ Musumeci 0c555e71a0 Initialize readdir threadpool after daemonizing 2023-08-28 22:47:04 -05:00
trapexitandGitHub 82781b6ff8 Merge pull request #1235 from DavidFair/readme_direct_parallel
Docs: clarify parallel-direct-writes and cache.files
2023-08-26 18:52:57 -05:00
David Fairbrother b545352712 Nit: cache.files=per-process grammar
Change which 'comm' name -> whose 'comm' name.
Whose implies possession, and the process possess the name we're
matching
2023-08-26 21:27:16 +01:00
David Fairbrother 3d05999db0 State cache.files per-process behaviour w/out match
This makes it clear, along with the previous commit, that per-process will allow
us to use parallel direct writes as they will mount with the equivalent
of cache.files=off (i.e. direct_io)
2023-08-26 21:24:34 +01:00
David Fairbrother f4a8105a42 Clarify parallel direct writes conditions
Clarify that paralell direct can only be enabled where the kernel
version supports it, which is 6.2+.

State the two cache.files modes which can support parallel direct writes
in the description.
2023-08-26 21:22:37 +01:00
trapexitandGitHub 004b616115 Merge pull request #1234 from trapexit/fixes
Misc fixes for older platforms
2023-08-22 08:13:56 -05:00
Antonio SJ Musumeci 8d7017273f Misc fixes for older platforms 2023-08-22 08:10:55 -05:00
trapexitandGitHub 51b65cd126 Merge pull request #1233 from trapexit/atomic
Use relaxed memory order for atomic counters
2023-08-21 17:19:09 -05:00
Antonio SJ Musumeci 0ed03a1535 Use relaxed memory order for atomic counters 2023-08-21 15:43:03 -05:00
trapexitandGitHub b6d3c34d3f Merge pull request #1232 from trapexit/wyhash
Update wyhash to final4
2023-08-21 00:20:04 -05:00
Antonio SJ Musumeci fdecabfaab Update wyhash to final4 2023-08-21 00:18:41 -05:00
trapexitandGitHub 3a1d8e03a1 Merge pull request #1231 from trapexit/readdir
Fix setting of ugids for concurrent readdir
2023-08-20 23:12:56 -05:00
Antonio SJ Musumeci e586d2fd1e Fix setting of ugids for concurrent readdir 2023-08-20 23:06:05 -05:00
trapexitandGitHub e0087cd8a7 Merge pull request #1228 from trapexit/threading
Add thread names for easier debugging
2023-08-14 17:52:31 -05:00
Antonio SJ Musumeci 2377cb05b6 Add thread names for easier debugging 2023-08-14 17:51:41 -05:00
trapexitandGitHub a8ffbc84f6 Merge pull request #1227 from trapexit/gidcache
Add ability to invalidate gid cache on demand
2023-08-13 23:32:33 -05:00
Antonio SJ Musumeci 0aafdefc18 Add ability to invalidate gid cache on demand
SIGUSR2 or ioctl
2023-08-13 23:30:52 -05:00
trapexitandGitHub 7221fded71 Merge pull request #1226 from feltcat/feltcat-patch-1
Fix a typo in README.md
2023-08-13 23:13:30 -05:00
feltcatandGitHub 7a09f560f7 Fix a typo in README.md 2023-08-14 03:45:01 +00:00
trapexitandGitHub 8b769dff7f Merge pull request #1225 from trapexit/new-funcs
Add callbacks for newer functions
2023-08-13 17:46:09 -05:00
Antonio SJ Musumeci 6dcf6111af Add callbacks for newer functions
* setupmapping
* removemapping
* syncfs
* tmpfile
2023-08-13 17:38:13 -05:00
trapexitandGitHub 9849bcd3cf Merge pull request #1224 from trapexit/readdir
Add readdir policies
2023-08-11 00:05:28 -05:00
Antonio SJ Musumeci c92a10021e Add readdir policies 2023-08-11 00:05:14 -05:00
trapexitandGitHub d817fa4e97 Merge pull request #1220 from trapexit/fix-logging
Fix logging pin-threads details
2023-07-30 23:14:16 -05:00
Antonio SJ Musumeci eb141c4c4e Fix logging pin-threads details 2023-07-30 23:13:14 -05:00
trapexitandGitHub 282ce08ddb Merge pull request #1219 from trapexit/fuse-config
Move fuse thread args out of fuse session object
2023-07-30 22:56:27 -05:00
Antonio SJ Musumeci 51d97bb444 Move fuse thread args out of fuse session object 2023-07-30 22:52:43 -05:00
trapexitandGitHub 539f2221a3 Merge pull request #1213 from trapexit/lib-updates
Lib updates
2023-07-15 18:51:43 -04:00
Antonio SJ Musumeci 2b1804d364 Add a DEPENDENCIES file to list dependencies
Mostly to remind myself which libs to update on occasion
2023-07-15 17:49:57 -05:00
Antonio SJ Musumeci 556c6522c0 Update fmt to v10.0.0 2023-07-15 17:47:36 -05:00
Antonio SJ Musumeci 543603bdfc Update ghc::filesystem to v1.5.14 2023-07-15 14:47:44 -05:00
101 changed files with 11636 additions and 4151 deletions
+56 -41
View File
@@ -5,7 +5,7 @@ freebsd_task:
env:
ASSUME_ALWAYS_YES: yes
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- gmake -j4
freebsd_task:
@@ -15,7 +15,7 @@ freebsd_task:
env:
ASSUME_ALWAYS_YES: yes
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- gmake -j4
freebsd_task:
@@ -25,7 +25,7 @@ freebsd_task:
env:
ASSUME_ALWAYS_YES: yes
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- gmake -j4
freebsd_task:
@@ -35,7 +35,7 @@ freebsd_task:
env:
ASSUME_ALWAYS_YES: yes
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- gmake -j4
@@ -43,7 +43,7 @@ freebsd_task:
# osx_instance:
# image: catalina-base
# script:
# - tools/install-build-pkgs
# - buildtools/install-build-pkgs
# - gmake -j4
linux_task:
@@ -54,7 +54,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -65,7 +65,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -76,7 +76,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -87,7 +87,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -98,7 +98,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -109,7 +109,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -120,7 +120,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -131,7 +131,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -142,7 +142,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make STATIC=1 LTO=1
linux_task:
@@ -153,7 +153,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -165,7 +165,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -177,7 +177,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -189,7 +189,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -201,7 +201,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -213,7 +213,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -225,7 +225,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -237,7 +237,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -249,7 +249,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -261,7 +261,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -273,7 +273,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -285,7 +285,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -297,7 +297,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- make
- make rpm
@@ -309,7 +309,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -324,7 +324,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -339,7 +339,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -354,7 +354,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -369,7 +369,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -384,7 +384,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -399,7 +399,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -414,7 +414,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -429,7 +429,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -444,7 +444,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -459,7 +459,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -474,7 +474,22 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
- dpkg -i ../*.deb
- mergerfs -v || true
linux_task:
name: "debian:12"
container:
image: debian:12
cpu: 4
memory: 4G
timeout_in: 15m
script:
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -489,7 +504,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
@@ -504,7 +519,7 @@ linux_task:
memory: 4G
timeout_in: 15m
script:
- tools/install-build-pkgs
- buildtools/install-build-pkgs
- git fetch
- make deb
- apt-get -y install fuse
+11
View File
@@ -0,0 +1,11 @@
## Libraries
### included in repo
* libfuse: https://github.com/libfuse/libfuse (heavily modified fork of v2.x)
* wyhash: https://github.com/wangyi-fudan/wyhash
* ghc::filesystem: https://github.com/gulrak/filesystem
* nonstd::optional: https://github.com/martinmoene/optional-lite
* fmt: https://github.com/fmtlib/fmt
* concurrentqueue: https://github.com/cameron314/concurrentqueue
* scope_guard: https://github.com/Neargye/scope_guard
+1 -1
View File
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2024, 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
+23 -8
View File
@@ -1,4 +1,4 @@
# Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
# Copyright (c) 2024, 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
@@ -26,7 +26,7 @@ STRIP = strip
PANDOC = pandoc
SED = sed
RPMBUILD = rpmbuild
GIT2DEBCL = ./tools/git2debcl
GIT2DEBCL = ./buildtools/git2debcl
PKGCONFIG = pkg-config
GIT_REPO = 0
@@ -42,7 +42,7 @@ UGID_USE_RWLOCK = 0
ifeq ($(DEBUG),1)
OPT_FLAGS := -O0 -g -fsanitize=undefined
else
OPT_FLAGS := -O2
OPT_FLAGS := -O2 -DNDEBUG
endif
ifeq ($(STATIC),1)
@@ -68,6 +68,10 @@ TESTS_DEPS = $(TESTS:tests/%.cpp=build/.tests/%.d)
TESTS_DEPS += $(DEPS)
MANPAGE = mergerfs.1
CFLAGS ?= ${OPT_FLAGS}
CFLAGS := ${CFLAGS} \
-Wall \
-Wno-unused-result
CXXFLAGS ?= ${OPT_FLAGS}
CXXFLAGS := \
${CXXFLAGS} \
@@ -92,6 +96,7 @@ LDFLAGS := \
-pthread \
-lrt
# https://www.gnu.org/prep/standards/html_node/Directory-Variables.html
DESTDIR =
PREFIX = /usr/local
EXEC_PREFIX = $(PREFIX)
@@ -99,11 +104,13 @@ DATAROOTDIR = $(PREFIX)/share
DATADIR = $(DATAROOTDIR)
BINDIR = $(EXEC_PREFIX)/bin
SBINDIR = $(EXEC_PREFIX)/sbin
LIBDIR = $(EXEC_PREFIX)/lib
MANDIR = $(DATAROOTDIR)/man
MAN1DIR = $(MANDIR)/man1
INSTALLBINDIR = $(DESTDIR)$(BINDIR)
INSTALLSBINDIR = $(DESTDIR)$(SBINDIR)
INSTALLLIBDIR = $(DESTDIR)$(LIBDIR)/mergerfs
INSTALLMAN1DIR = $(DESTDIR)$(MAN1DIR)
.PHONY: all
@@ -141,7 +148,7 @@ endif
.PHONY: version
version:
tools/update-version
./buildtools/update-version
build/stamp:
$(MKDIR) -p build/.src build/.tests
@@ -153,6 +160,10 @@ build/.src/%.o: src/%.cpp
build/.tests/%.o: tests/%.cpp
$(CXX) $(CXXFLAGS) $(TESTS_FLAGS) $(FUSE_FLAGS) $(MFS_FLAGS) $(CPPFLAGS) -c $< -o $@
build/preload.so: build/stamp tools/preload.c
$(CC) -shared -fPIC $(CFLAGS) $(CPPFLAGS) -o $@ tools/preload.c
preload: build/preload.so
.PHONY: clean
clean: rpm-clean
@@ -166,7 +177,7 @@ ifeq ($(GIT_REPO),1)
endif
.PHONY: install
install: install-base install-mount-tools install-man
install: install-base install-mount-tools install-preload install-man
install-base: build/mergerfs
$(MKDIR) -p "$(INSTALLBINDIR)"
@@ -180,6 +191,10 @@ install-man: $(MANPAGE)
$(MKDIR) -p "$(INSTALLMAN1DIR)"
$(INSTALL) -v -m 0644 "man/$(MANPAGE)" "$(INSTALLMAN1DIR)/$(MANPAGE)"
install-preload: preload
$(MKDIR) -p "$(INSTALLLIBDIR)"
$(INSTALL) -v -m 444 "build/preload.so" "$(INSTALLLIBDIR)/preload.so"
install-strip: install-base
$(STRIP) "$(INSTALLBINDIR)/mergerfs"
@@ -225,13 +240,13 @@ endif
signed-deb:
$(MAKE) distclean
$(MAKE) debian-changelog
dpkg-source -b .
# dpkg-source -b .
dpkg-buildpackage -nc
deb:
$(MAKE) distclean
$(MAKE) debian-changelog
dpkg-source -b .
# dpkg-source -b .
dpkg-buildpackage -nc -uc -us
.PHONY: rpm-clean
@@ -250,7 +265,7 @@ rpm: tarball
.PHONY: install-build-pkgs
install-build-pkgs:
tools/install-build-pkgs
./buildtools/install-build-pkgs
.PHONY: libfuse
libfuse:
+330 -146
View File
@@ -248,6 +248,9 @@ These options are the same regardless of whether you use them with the
to the same as the process thread count. (default: 0)
* **pin-threads=STR**: Selects a strategy to pin threads to CPUs
(default: unset)
* **flush-on-close=never|always|opened-for-write**: Flush data cache
on file close. Mostly for when writeback is enabled or merging
network filesystems. (default: opened-for-write)
* **scheduling-priority=INT**: Set mergerfs' scheduling
priority. Valid values range from -20 to 19. See `setpriority` man
page for more details. (default: -10)
@@ -256,6 +259,9 @@ These options are the same regardless of whether you use them with the
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**
* **func.readdir=seq|cosr|cor|cosr:INT|cor:INT**: Sets `readdir`
policy. INT value sets the number of threads to use for
concurrency. (default: seq)
* **category.action=POLICY**: Sets policy of all FUSE functions in the
action category. (default: epall)
* **category.create=POLICY**: Sets policy of all FUSE functions in the
@@ -286,7 +292,8 @@ These options are the same regardless of whether you use them with the
(default: false)
* **parallel-direct-writes=BOOL**: Allow the kernel to dispatch
multiple, parallel (non-extending) write requests for files opened
with `direct_io=true` (if supported by the kernel)
with `cache.files=per-process` (if the process is not in `process-names`)
or `cache.files=off`. (This requires kernel support, and was added in v6.2)
* **direct_io**: deprecated - Bypass page cache. Use `cache.files=off`
instead. (default: false)
* **kernel_cache**: deprecated - Do not invalidate data cache on file
@@ -300,8 +307,8 @@ These options are the same regardless of whether you use them with the
* **splice_read**: deprecated - Does nothing.
* **splice_write**: deprecated - Does nothing.
* **splice_move**: deprecated - Does nothing.
* **allow_other**: deprecated - mergerfs always sets this FUSE option
as normal permissions can be used to limit access.
* **allow_other**: deprecated - mergerfs v2.35.0 and newer sets this FUSE option
automatically if running as root.
* **use_ino**: deprecated - mergerfs should always control inode
calculation so this is enabled all the time.
@@ -663,7 +670,7 @@ writable.
Even though it's a more niche situation this hack breaks normal
security and behavior and as such is `off` by default. If set to `git`
it will only perform the hack when the path in question includes
`/.git/`. `all` will result it applying anytime a readonly file which
`/.git/`. `all` will result it applying anytime a read-only file which
is empty is opened for writing.
@@ -682,9 +689,8 @@ rather than file paths, which were created by `open` or `create`. That
said many times the current FUSE kernel driver will not always provide
the file handle when a client calls `fgetattr`, `fchown`, `fchmod`,
`futimens`, `ftruncate`, etc. This means it will call the regular,
path based, versions. `readdir` has no real need for a policy given
the purpose is merely to return a list of entries in a
directory. `statfs`'s behavior can be modified via other options.
path based, versions. `statfs`'s behavior can be modified via other
options.
When using policies which are based on a branch's available space the
base path provided is used. Not the full path to the file in
@@ -692,8 +698,8 @@ question. Meaning that mounts in the branch won't be considered in the
space calculations. The reason is that it doesn't really work for
non-path preserving policies and can lead to non-obvious behaviors.
NOTE: While any policy can be assigned to a function or category
though some may not be very useful in practice. For instance: **rand**
NOTE: While any policy can be assigned to a function or category,
some may not be very useful in practice. For instance: **rand**
(random) may be useful for file creation (create) but could lead to
very odd behavior if used for `chmod` if there were more than one copy
of the file.
@@ -715,8 +721,7 @@ In cases where something may be searched for (such as a path to clone)
### Policies
A policy is the algorithm used to choose a branch or branches for a
function to work on. Think of them as ways to filter and sort
branches.
function to work on or generally how the function behaves.
Any function in the `create` category will clone the relative path if
needed. Some other functions (`rename`,`link`,`ioctl`) have special
@@ -725,12 +730,11 @@ requirements or behaviors which you can read more about below.
#### Filtering
Policies basically search branches and create a list of files / paths
Most policies basically search branches and create a list of files / paths
for functions to work on. The policy is responsible for filtering and
sorting the branches. Filters include **minfreespace**, whether or not
a branch is mounted read-only, and the branch tagging
(RO,NC,RW). These filters are applied across all policies unless
otherwise noted.
(RO,NC,RW). These filters are applied across most policies.
* No **search** function policies filter.
* All **action** function policies filter out branches which are
@@ -823,6 +827,26 @@ policies is not appropriate.
| search | ff |
#### func.readdir
examples: `func.readdir=seq`, `func.readdir=cor:4`
`readdir` has policies to control how it manages reading directory
content.
| Policy | Description |
|--------|-------------|
| seq | "sequential" : Iterate over branches in the order defined. This is the default and traditional behavior found prior to the readdir policy introduction. |
| cosr | "concurrent open, sequential read" : Concurrently open branch directories using a thread pool and process them in order of definition. This keeps memory and CPU usage low while also reducing the time spent waiting on branches to respond. Number of threads defaults to the number of logical cores. Can be overwritten via the syntax `func.readdir=cosr:N` where `N` is the number of threads. |
| cor | "concurrent open and read" : Concurrently open branch directories and immediately start reading their contents using a thread pool. This will result in slightly higher memory and CPU usage but reduced latency. Particularly when using higher latency / slower speed network filesystem branches. Unlike `seq` and `cosr` the order of files could change due the async nature of the thread pool. Number of threads defaults to the number of logical cores. Can be overwritten via the syntax `func.readdir=cor:N` where `N` is the number of threads.
Keep in mind that `readdir` mostly just provides a list of file names
in a directory and possibly some basic metadata about said files. To
know details about the files, as one would see from commands like
`find` or `ls`, it is required to call `stat` on the file which is
controlled by `fuse.getattr`.
#### ioctl
When `ioctl` is used with an open file then it will use the file
@@ -891,19 +915,6 @@ returned but it will still be possible.
**link** uses the same strategy but without the removals.
#### readdir ####
[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
@@ -918,6 +929,27 @@ The options `statfs` and `statfs_ignore` can be used to modify
`statfs` behavior.
#### flush-on-close
https://lkml.kernel.org/linux-fsdevel/20211024132607.1636952-1-amir73il@gmail.com/T/
By default FUSE would issue a flush before the release of a file
descriptor. This was considered a bit aggressive and a feature added
to give the FUSE server the ability to choose when that happens.
Options:
* always
* never
* opened-for-write
For now it defaults to "opened-for-write" which is less aggressive
than the behavior before this feature was added. It should not be a
problem because the flush is really only relevant when a file is
written to. Given flush is irrelevant for many filesystems in the
future a branch specific flag may be added so only files opened on a
specific branch would be flushed on close.
# ERROR HANDLING
POSIX filesystem functions offer a single return code meaning that
@@ -1256,6 +1288,102 @@ typedef char IOCTL_BUF[4096];
# TOOLING
## preload.so
EXPERIMENTAL
For some time there has been work to enable passthrough IO in
FUSE. Passthrough IO would allow for near native performance with
regards to reads and writes (at the expense of certain mergerfs
features.) However, there have been several complications which have
kept the feature from making it into the mainline Linux kernel. Until
that feature is available there are two methods to provide similar
functionality. One method is using the LD_PRELOAD feature of the
dynamic linker. The other leveraging ptrace to intercept
syscalls. Each has their disadvantages. At the moment only a preload
based tool is available. A ptrace based tool may be developed later if
there is a need.
`/usr/lib/mergerfs/preload.so`
This [preloadable
library](https://man7.org/linux/man-pages/man8/ld.so.8.html#ENVIRONMENT)
overrides the creation and opening of files in order to simulate
passthrough file IO. It catches the open/creat/fopen calls, has
mergerfs do the call, queries mergerfs for the branch the file exists
on, reopens the file on the underlying filesystem and returns that
instead. Meaning that you will get native read/write performance
because mergerfs is no longer part of the workflow. Keep in mind that
this also means certain mergerfs features that work by interrupting
the read/write workflow, such as `moveonenospc`, will no longer work.
Also understand that this will only work on dynamically linked
software. Anything statically compiled will not work. Many GoLang and
Rust apps are statically compiled.
The library will not interfere with non-mergerfs filesystems. The
library is written to always fallback to returning the mergerfs opened
file on error.
While the library was written to account for a number of edgecases
there could be some yet accounted for so please report any oddities.
Thank you to
[nohajc](https://github.com/nohajc/mergerfs-io-passthrough) for
prototyping the idea.
### general usage
```sh
LD_PRELOAD=/usr/lib/mergerfs/preload.so touch /mnt/mergerfs/filename
```
### Docker usage
Assume `/mnt/fs0` and `/mnt/fs1` are pooled with mergerfs at `/media`.
All mergerfs branch paths *must* be bind mounted into the container at
the same path as found on the host so the preload library can see them.
```sh
docker run \
-e LD_PRELOAD=/usr/lib/mergerfs/preload.so \
-v /usr/lib/mergerfs/preload.so:/usr/lib/mergerfs/preload.so:ro \
-v /media:/data \
-v /mnt:/mnt \
ubuntu:latest \
bash
```
or more explicitly
```sh
docker run \
-e LD_PRELOAD=/usr/lib/mergerfs/preload.so \
-v /usr/lib/mergerfs/preload.so:/usr/lib/mergerfs/preload.so:ro \
-v /media:/data \
-v /mnt/fs0:/mnt/fs0 \
-v /mnt/fs1:/mnt/fs1 \
ubuntu:latest \
bash
```
### systemd unit
Use the `Environment` option to set the LD_PRELOAD variable.
* https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Command%20lines
* https://serverfault.com/questions/413397/how-to-set-environment-variable-in-systemd-service
```
[Service]
Environment=LD_PRELOAD=/usr/lib/mergerfs/preload.so
```
## Misc
* https://github.com/trapexit/mergerfs-tools
* mergerfs.ctl: A tool to make it easier to query and configure mergerfs at runtime
* mergerfs.fsck: Provides permissions and ownership auditing and the ability to fix them
@@ -1286,12 +1414,13 @@ https://en.wikipedia.org/wiki/Page_cache
unchanged since previous open.
* cache.files=libfuse: follow traditional libfuse `direct_io`,
`kernel_cache`, and `auto_cache` arguments.
* cache.files=per-process: Enable page caching only for processes
which 'comm' name matches one of the values defined in
`cache.files.process-names`.
* cache.files=per-process: Enable page caching (equivalent to
`cache.files=partial`) only for processes whose 'comm' name matches
one of the values defined in `cache.files.process-names`. If the
name does not match the file open is equivalent to
`cache.files=off`.
FUSE, which mergerfs uses, offers a number of page caching
modes. mergerfs tries to simplify their use via the `cache.files`
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`.
@@ -1420,7 +1549,7 @@ placing of usually smaller, faster storage as a transparent cache to
larger, slower storage. NVMe, SSD, Optane in front of traditional HDDs
for instance.
MergerFS does not natively support any sort of tiered caching. Most
mergerfs does not natively support any sort of tiered caching. Most
users have no use for such a feature and its inclusion would
complicate the code. However, there are a few situations where a cache
filesystem could help with a typical mergerfs setup.
@@ -1893,18 +2022,51 @@ of which are fixed in stable releases.
#### Can mergerfs be used with filesystems which already have data / are in use?
Yes. MergerFS is a proxy and does **NOT** interfere with the normal
form or function of the filesystems / mounts / paths it manages.
Yes. mergerfs is really just a proxy and does **NOT** interfere with
the normal form or function of the filesystems / mounts / paths it
manages. It is just another userland application that is acting as a
man-in-the-middle. It can't do anything that any other random piece of
software can't do.
MergerFS is **not** a traditional filesystem. MergerFS is **not**
RAID. It does **not** manipulate the data that passes through it. It
does **not** shard data across filesystems. It merely shards some
**behavior** and aggregates others.
mergerfs is **not** a traditional filesystem that takes control over
the underlying block device. mergerfs is **not** RAID. It does **not**
manipulate the data that passes through it. It does **not** shard data
across filesystems. It merely shards some **behavior** and aggregates
others.
#### Can drives/filesystems be removed from the pool at will?
Yes. See previous question's answer.
#### Can mergerfs be removed without affecting the data?
See the previous question's answer.
Yes. See the previous question's answer.
#### Can drives/filesystems be moved to another pool?
Yes. See the previous question's answer.
#### How do I migrate data into or out of the pool when adding/removing drives/filesystems?
You don't need to. See the previous question's answer.
#### How do I remove a drive/filesystem but keep the data in the pool?
Nothing special needs to be done. Remove the branch from mergerfs'
config and copy (rsync) the data from the removed filesystem into the
pool. Effectively the same as if it were you transfering data from one
filesystem to another.
If you wish to continue using the pool while performing the transfer
simply create another, temporary pool without the filesystem in
question and then copy the data. It would probably be a good idea to
set the branch to `RO` prior to doing this to ensure no new content is
written to the filesystem while performing the copy.
#### What policies should I use?
@@ -1973,15 +2135,16 @@ 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 filesystems change to `mfs` or similar policy as described
above. If you do want path preservation you'll need to perform the
manual act of creating paths on the filesystems you want the data to
land on before transferring your data. Setting `func.mkdir=epall` can
simplify managing path preservation for `create`. Or use
`func.mkdir=rand` if you're interested in just grouping together
directory content by filesystem.
would break the setup for many existing users and this policy is the
safest policy as it will not change the general layout of the existing
filesystems. If you do not care about path preservation and wish your
files to be spread across all your filesystems change to `mfs` or
similar policy as described above. If you do want path preservation
you'll need to perform the manual act of creating paths on the
filesystems you want the data to land on before transferring your
data. Setting `func.mkdir=epall` can simplify managing path
preservation for `create`. Or use `func.mkdir=rand` if you're
interested in just grouping together directory content by filesystem.
#### Do hardlinks work?
@@ -2003,6 +2166,16 @@ mergerfs pool that includes all the paths you need if you want links
to work.
#### Does FICLONE or FICLONERANGE work?
Unfortunately not. FUSE, the technology mergerfs is based on, does not
support the `clone_file_range` feature needed for it to work. mergerfs
won't even know such a request is made. The kernel will simply return
an error back to the application making the request.
Should FUSE gain the ability mergerfs will be updated to support it.
#### Can I use mergerfs without SnapRAID? SnapRAID without mergerfs?
Yes. They are completely unrelated pieces of software.
@@ -2121,103 +2294,6 @@ traditional read/write fallback to be provided. The splice code was
removed to simplify the codebase.
#### Why use mergerfs over mhddfs?
mhddfs is no longer maintained and has some known stability and
security issues (see below). MergerFS provides a superset of mhddfs'
features and should offer the same or maybe better performance.
Below is an example of mhddfs and mergerfs setup to work similarly.
`mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool`
`mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool`
#### Why use mergerfs over aufs?
aufs is mostly abandoned and no longer available in many distros.
While aufs can offer better peak performance mergerfs provides more
configurability and is generally easier to use. mergerfs however does
not offer the overlay / copy-on-write (CoW) features which aufs and
overlayfs have.
#### Why use mergerfs over unionfs?
UnionFS is more like aufs than mergerfs in that it offers overlay /
CoW features. If you're just looking to create a union of filesystems
and want flexibility in file/directory placement then mergerfs offers
that whereas unionfs is more for overlaying RW filesystems over RO
ones.
#### Why use mergerfs over overlayfs?
Same reasons as with unionfs.
#### 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 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 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 filesystems (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).
#### Why use mergerfs over UnRAID?
UnRAID is a full OS and its storage layer, as I understand, is
proprietary and closed source. Users who have experience with both
have said they prefer the flexibility offered by mergerfs and for some
the fact it is free and open source is important.
There are a number of UnRAID users who use mergerfs as well though I'm
not entirely familiar with the use case.
#### Why use mergerfs over StableBit's DrivePool?
DrivePool works only on Windows so not as common an alternative as
other Linux solutions. If you want to use Windows then DrivePool is a
good option. Functionally the two projects work a bit
differently. DrivePool always writes to the filesystem with the most
free space and later rebalances. mergerfs does not offer rebalance but
chooses a branch at file/directory create time. DrivePool's
rebalancing can be done differently in any directory and has file
pattern matching to further customize the behavior. mergerfs, not
having rebalancing does not have these features, but similar features
are planned for mergerfs v3. DrivePool has builtin file duplication
which mergerfs does not natively support (but can be done via an
external script.)
There are a lot of misc differences between the two projects but most
features in DrivePool can be replicated with external tools in
combination with mergerfs.
Additionally DrivePool is a closed source commercial product vs
mergerfs a ISC licensed OSS project.
#### What should mergerfs NOT be used for?
* databases: Even if the database stored data in separate files
@@ -2369,6 +2445,114 @@ 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.
# mergerfs versus X
#### mhddfs
mhddfs had not been maintained for some time and has some known
stability and security issues. mergerfs provides a superset of mhddfs'
features and should offer the same or better performance.
Below is an example of mhddfs and mergerfs setup to work similarly.
`mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool`
`mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool`
#### aufs
aufs is mostly abandoned and no longer available in most Linux distros.
While aufs can offer better peak performance mergerfs provides more
configurability and is generally easier to use. mergerfs however does
not offer the overlay / copy-on-write (CoW) features which aufs has.
#### unionfs-fuse
unionfs-fuse is more like aufs than mergerfs in that it offers overlay /
copy-on-write (CoW) features. If you're just looking to create a union
of filesystems and want flexibility in file/directory placement then
mergerfs offers that whereas unionfs is more for overlaying read/write
filesystems over read-only ones.
#### overlayfs
overlayfs is similar to aufs and unionfs-fuse in that it also is
primarily used to layer a read/write filesystem over one or more
read-only filesystems. It does not have the ability to spread
files/directories across numerous filesystems.
#### RAID0, JBOD, drive concatenation, striping
With simple JBOD / drive concatenation / stripping / RAID0 a single
drive failure will result in full pool failure. mergerfs performs a
similar function without the possibility of catastrophic failure and
the difficulties in recovery. Drives may fail but all other
filesystems and their data will continue to be accessible.
The main practical difference with mergerfs is the fact you don't
actually have contiguous space as large as if you used those other
technologies. Meaning you can't create a 2TB file on a pool of 2 1TB
filesystems.
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.
#### UnRAID
UnRAID is a full OS and its storage layer, as I understand, is
proprietary and closed source. Users who have experience with both
have often said they prefer the flexibility offered by mergerfs and
for some the fact it is open source is important.
There are a number of UnRAID users who use mergerfs as well though I'm
not entirely familiar with the use case.
For semi-static data mergerfs + [SnapRaid](http://www.snapraid.it)
provides a similar solution.
#### ZFS
mergerfs is very different from ZFS. mergerfs is intended to provide
flexible pooling of arbitrary filesystems (local or remote), of
arbitrary sizes, and arbitrary filesystems. For `write once, read
many` usecases such as bulk media storage. Where data integrity and
backup is managed in other ways. In those usecases ZFS can introduce a
number of costs and limitations as described
[here](http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html),
[here](https://markmcb.com/2020/01/07/five-years-of-btrfs/), and
[here](https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSWhyNoRealReshaping).
#### StableBit's DrivePool
DrivePool works only on Windows so not as common an alternative as
other Linux solutions. If you want to use Windows then DrivePool is a
good option. Functionally the two projects work a bit
differently. DrivePool always writes to the filesystem with the most
free space and later rebalances. mergerfs does not offer rebalance but
chooses a branch at file/directory create time. DrivePool's
rebalancing can be done differently in any directory and has file
pattern matching to further customize the behavior. mergerfs, not
having rebalancing does not have these features, but similar features
are planned for mergerfs v3. DrivePool has builtin file duplication
which mergerfs does not natively support (but can be done via an
external script.)
There are a lot of misc differences between the two projects but most
features in DrivePool can be replicated with external tools in
combination with mergerfs.
Additionally DrivePool is a closed source commercial product vs
mergerfs a ISC licensed OSS project.
# SUPPORT
+3 -4
View File
@@ -44,20 +44,19 @@ SRC_C = \
lib/fuse.c \
lib/fuse_dirents.c \
lib/fuse_lowlevel.c \
lib/fuse_mt.c \
lib/node.c \
lib/fuse_node.c \
lib/fuse_opt.c \
lib/fuse_session.c \
lib/fuse_signals.c \
lib/helper.c \
lib/mount.c \
lib/syslog.c
lib/mount.c
SRC_CPP = \
lib/format.cpp \
lib/os.cpp \
lib/cpu.cpp \
lib/fuse_loop_mt.cpp \
lib/fuse_config.cpp \
lib/fuse_loop.cpp \
lib/fuse_msgbuf.cpp
OBJS_C = $(SRC_C:lib/%.c=build/%.o)
OBJS_CPP = $(SRC_CPP:lib/%.cpp=build/%.o)
+10
View File
@@ -539,6 +539,16 @@ struct fuse_operations
off_t offset_out,
size_t size,
int flags);
ssize_t (*setupmapping)(uint64_t *fh_,
uint64_t foffset_,
uint64_t len_,
uint64_t flags_,
uint64_t moffset_);
int (*removemapping)();
int (*syncfs)();
int (*tmpfile)(const char *, mode_t, fuse_file_info_t *);
};
/** Extra context that may be needed by some filesystems
+2
View File
@@ -86,6 +86,8 @@ struct fuse_file_info_t
uint32_t parallel_direct_writes:1;
uint32_t noflush:1;
/** File handle. May be filled in by filesystem in open().
Available in all other file operations */
uint64_t fh;
+31
View File
@@ -0,0 +1,31 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <string>
int fuse_config_get_read_thread_count();
int fuse_config_get_process_thread_count();
int fuse_config_get_process_thread_queue_depth();
std::string fuse_config_get_pin_threads();
void fuse_config_set_read_thread_count(int const);
void fuse_config_set_process_thread_count(int const);
void fuse_config_set_process_thread_queue_depth(int const);
void fuse_config_set_pin_threads(std::string const);
+8 -8
View File
@@ -63,14 +63,14 @@ int fuse_dirents_add_plus(fuse_dirents_t *d,
const uint64_t namelen,
const fuse_entry_t *entry,
const struct stat *st);
int fuse_dirents_add_linux(fuse_dirents_t *d,
const struct linux_dirent64 *de,
const uint64_t namelen);
int fuse_dirents_add_linux_plus(fuse_dirents_t *d,
const struct linux_dirent64 *de,
const uint64_t namelen,
const fuse_entry_t *entry,
const struct stat *st);
int fuse_dirents_add_linux(fuse_dirents_t *d,
const linux_dirent64_t *de,
const uint64_t namelen);
int fuse_dirents_add_linux_plus(fuse_dirents_t *d,
const linux_dirent64_t *de,
const uint64_t namelen,
const fuse_entry_t *entry,
const struct stat *st);
void *fuse_dirents_find(fuse_dirents_t *d,
const uint64_t ino);
+9
View File
@@ -1007,6 +1007,15 @@ struct fuse_lowlevel_ops
*/
void (*copy_file_range)(fuse_req_t req,
const struct fuse_in_header *hdr);
void (*setupmapping)(fuse_req_t req,
const struct fuse_in_header *hdr);
void (*removemapping)(fuse_req_t req,
const struct fuse_in_header *hdr);
void (*syncfs)(fuse_req_t req,
const struct fuse_in_header *hdr);
void (*tmpfile)(fuse_req_t req,
const struct fuse_in_header *hdr);
};
/**
+5 -1
View File
@@ -1,8 +1,12 @@
#pragma once
#include <stdint.h>
#include <string.h>
struct linux_dirent64
#define DIRENT_NAMELEN(X) (strlen((X)->name))
typedef struct linux_dirent64_t linux_dirent64_t;
struct linux_dirent64_t
{
uint64_t ino;
int64_t off;
@@ -0,0 +1,582 @@
// Provides an efficient blocking version of moodycamel::ConcurrentQueue.
// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified
// BSD license, available at the top of concurrentqueue.h.
// Also dual-licensed under the Boost Software License (see LICENSE.md)
// Uses Jeff Preshing's semaphore implementation (under the terms of its
// separate zlib license, see lightweightsemaphore.h).
#pragma once
#include "concurrentqueue.h"
#include "lightweightsemaphore.h"
#include <type_traits>
#include <cerrno>
#include <memory>
#include <chrono>
#include <ctime>
namespace moodycamel
{
// This is a blocking version of the queue. It has an almost identical interface to
// the normal non-blocking version, with the addition of various wait_dequeue() methods
// and the removal of producer-specific dequeue methods.
template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
class BlockingConcurrentQueue
{
private:
typedef ::moodycamel::ConcurrentQueue<T, Traits> ConcurrentQueue;
typedef ::moodycamel::LightweightSemaphore LightweightSemaphore;
public:
typedef typename ConcurrentQueue::producer_token_t producer_token_t;
typedef typename ConcurrentQueue::consumer_token_t consumer_token_t;
typedef typename ConcurrentQueue::index_t index_t;
typedef typename ConcurrentQueue::size_t size_t;
typedef typename std::make_signed<size_t>::type ssize_t;
static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE;
static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD;
static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE;
static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE;
static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE;
static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE;
public:
// Creates a queue with at least `capacity` element slots; note that the
// actual number of elements that can be inserted without additional memory
// allocation depends on the number of producers and the block size (e.g. if
// the block size is equal to `capacity`, only a single block will be allocated
// up-front, which means only a single producer will be able to enqueue elements
// without an extra allocation -- blocks aren't shared between producers).
// This method is not thread safe -- it is up to the user to ensure that the
// queue is fully constructed before it starts being used by other threads (this
// includes making the memory effects of construction visible, possibly with a
// memory barrier).
explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
: inner(capacity), sema(create<LightweightSemaphore, ssize_t, int>(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
{
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
if (!sema) {
MOODYCAMEL_THROW(std::bad_alloc());
}
}
BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers)
: inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create<LightweightSemaphore, ssize_t, int>(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
{
assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
if (!sema) {
MOODYCAMEL_THROW(std::bad_alloc());
}
}
// Disable copying and copy assignment
BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
// Moving is supported, but note that it is *not* a thread-safe operation.
// Nobody can use the queue while it's being moved, and the memory effects
// of that move must be propagated to other threads before they can use it.
// Note: When a queue is moved, its tokens are still valid but can only be
// used with the destination queue (i.e. semantically they are moved along
// with the queue itself).
BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
: inner(std::move(other.inner)), sema(std::move(other.sema))
{ }
inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
{
return swap_internal(other);
}
// Swaps this queue's state with the other's. Not thread-safe.
// Swapping two queues does not invalidate their tokens, however
// the tokens that were created for one queue must be used with
// only the swapped queue (i.e. the tokens are tied to the
// queue's movable state, not the object itself).
inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT
{
swap_internal(other);
}
private:
BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other)
{
if (this == &other) {
return *this;
}
inner.swap(other.inner);
sema.swap(other.sema);
return *this;
}
public:
// Enqueues a single item (by copying it).
// Allocates memory if required. Only fails if memory allocation fails (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(T const& item)
{
if ((details::likely)(inner.enqueue(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible).
// Allocates memory if required. Only fails if memory allocation fails (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
// or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(T&& item)
{
if ((details::likely)(inner.enqueue(std::move(item)))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by copying it) using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails (or
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(producer_token_t const& token, T const& item)
{
if ((details::likely)(inner.enqueue(token, item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails (or
// Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Thread-safe.
inline bool enqueue(producer_token_t const& token, T&& item)
{
if ((details::likely)(inner.enqueue(token, std::move(item)))) {
sema->signal();
return true;
}
return false;
}
// Enqueues several items.
// Allocates memory if required. Only fails if memory allocation fails (or
// implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
// is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Note: Use std::make_move_iterator if the elements should be moved instead of copied.
// Thread-safe.
template<typename It>
inline bool enqueue_bulk(It itemFirst, size_t count)
{
if ((details::likely)(inner.enqueue_bulk(std::forward<It>(itemFirst), count))) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues several items using an explicit producer token.
// Allocates memory if required. Only fails if memory allocation fails
// (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
if ((details::likely)(inner.enqueue_bulk(token, std::forward<It>(itemFirst), count))) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues a single item (by copying it).
// Does not allocate memory. Fails if not enough room to enqueue (or implicit
// production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
// is 0).
// Thread-safe.
inline bool try_enqueue(T const& item)
{
if (inner.try_enqueue(item)) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible).
// Does not allocate memory (except for one-time implicit producer).
// Fails if not enough room to enqueue (or implicit production is
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
// Thread-safe.
inline bool try_enqueue(T&& item)
{
if (inner.try_enqueue(std::move(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by copying it) using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Thread-safe.
inline bool try_enqueue(producer_token_t const& token, T const& item)
{
if (inner.try_enqueue(token, item)) {
sema->signal();
return true;
}
return false;
}
// Enqueues a single item (by moving it, if possible) using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Thread-safe.
inline bool try_enqueue(producer_token_t const& token, T&& item)
{
if (inner.try_enqueue(token, std::move(item))) {
sema->signal();
return true;
}
return false;
}
// Enqueues several items.
// Does not allocate memory (except for one-time implicit producer).
// Fails if not enough room to enqueue (or implicit production is
// disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool try_enqueue_bulk(It itemFirst, size_t count)
{
if (inner.try_enqueue_bulk(std::forward<It>(itemFirst), count)) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Enqueues several items using an explicit producer token.
// Does not allocate memory. Fails if not enough room to enqueue.
// Note: Use std::make_move_iterator if the elements should be moved
// instead of copied.
// Thread-safe.
template<typename It>
inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
{
if (inner.try_enqueue_bulk(token, std::forward<It>(itemFirst), count)) {
sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
return true;
}
return false;
}
// Attempts to dequeue from the queue.
// Returns false if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename U>
inline bool try_dequeue(U& item)
{
if (sema->tryWait()) {
while (!inner.try_dequeue(item)) {
continue;
}
return true;
}
return false;
}
// Attempts to dequeue from the queue using an explicit consumer token.
// Returns false if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename U>
inline bool try_dequeue(consumer_token_t& token, U& item)
{
if (sema->tryWait()) {
while (!inner.try_dequeue(token, item)) {
continue;
}
return true;
}
return false;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued.
// Returns 0 if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename It>
inline size_t try_dequeue_bulk(It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued.
// Returns 0 if all producer streams appeared empty at the time they
// were checked (so, the queue is likely but not guaranteed to be empty).
// Never allocates. Thread-safe.
template<typename It>
inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Blocks the current thread until there's something to dequeue, then
// dequeues it.
// Never allocates. Thread-safe.
template<typename U>
inline void wait_dequeue(U& item)
{
while (!sema->wait()) {
continue;
}
while (!inner.try_dequeue(item)) {
continue;
}
}
// Blocks the current thread until either there's something to dequeue
// or the timeout (specified in microseconds) expires. Returns false
// without setting `item` if the timeout expires, otherwise assigns
// to `item` and returns true.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue.
// Never allocates. Thread-safe.
template<typename U>
inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
{
if (!sema->wait(timeout_usecs)) {
return false;
}
while (!inner.try_dequeue(item)) {
continue;
}
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Never allocates. Thread-safe.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Blocks the current thread until there's something to dequeue, then
// dequeues it using an explicit consumer token.
// Never allocates. Thread-safe.
template<typename U>
inline void wait_dequeue(consumer_token_t& token, U& item)
{
while (!sema->wait()) {
continue;
}
while (!inner.try_dequeue(token, item)) {
continue;
}
}
// Blocks the current thread until either there's something to dequeue
// or the timeout (specified in microseconds) expires. Returns false
// without setting `item` if the timeout expires, otherwise assigns
// to `item` and returns true.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue.
// Never allocates. Thread-safe.
template<typename U>
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs)
{
if (!sema->wait(timeout_usecs)) {
return false;
}
while (!inner.try_dequeue(token, item)) {
continue;
}
return true;
}
// Blocks the current thread until either there's something to dequeue
// or the timeout expires. Returns false without setting `item` if the
// timeout expires, otherwise assigns to `item` and returns true.
// Never allocates. Thread-safe.
template<typename U, typename Rep, typename Period>
inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_timed(token, item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which will
// always be at least one (this method blocks until the queue
// is non-empty) and at most max.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk(It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue_bulk.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Never allocates. Thread-safe.
template<typename It, typename Rep, typename Period>
inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_bulk_timed<It&>(itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which will
// always be at least one (this method blocks until the queue
// is non-empty) and at most max.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Using a negative timeout indicates an indefinite timeout,
// and is thus functionally equivalent to calling wait_dequeue_bulk.
// Never allocates. Thread-safe.
template<typename It>
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs)
{
size_t count = 0;
max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
while (count != max) {
count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
}
return count;
}
// Attempts to dequeue several elements from the queue using an explicit consumer token.
// Returns the number of items actually dequeued, which can
// be 0 if the timeout expires while waiting for elements,
// and at most max.
// Never allocates. Thread-safe.
template<typename It, typename Rep, typename Period>
inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
{
return wait_dequeue_bulk_timed<It&>(token, itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
}
// Returns an estimate of the total number of elements currently in the queue. This
// estimate is only accurate if the queue has completely stabilized before it is called
// (i.e. all enqueue and dequeue operations have completed and their memory effects are
// visible on the calling thread, and no further operations start while this method is
// being called).
// Thread-safe.
inline size_t size_approx() const
{
return (size_t)sema->availableApprox();
}
// Returns true if the underlying atomic variables used by
// the queue are lock-free (they should be on most platforms).
// Thread-safe.
static constexpr bool is_lock_free()
{
return ConcurrentQueue::is_lock_free();
}
private:
template<typename U, typename A1, typename A2>
static inline U* create(A1&& a1, A2&& a2)
{
void* p = (Traits::malloc)(sizeof(U));
return p != nullptr ? new (p) U(std::forward<A1>(a1), std::forward<A2>(a2)) : nullptr;
}
template<typename U>
static inline void destroy(U* p)
{
if (p != nullptr) {
p->~U();
}
(Traits::free)(p);
}
private:
ConcurrentQueue inner;
std::unique_ptr<LightweightSemaphore, void (*)(LightweightSemaphore*)> sema;
};
template<typename T, typename Traits>
inline void swap(BlockingConcurrentQueue<T, Traits>& a, BlockingConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT
{
a.swap(b);
}
} // end namespace moodycamel
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,427 @@
// Provides an efficient implementation of a semaphore (LightweightSemaphore).
// This is an extension of Jeff Preshing's sempahore implementation (licensed
// under the terms of its separate zlib license) that has been adapted and
// extended by Cameron Desrochers.
#pragma once
#include <cstddef> // For std::size_t
#include <atomic>
#include <type_traits> // For std::make_signed<T>
#if defined(_WIN32)
// Avoid including windows.h in a header; we only need a handful of
// items, so we'll redeclare them here (this is relatively safe since
// the API generally has to remain stable between Windows versions).
// I know this is an ugly hack but it still beats polluting the global
// namespace with thousands of generic names or adding a .cpp for nothing.
extern "C" {
struct _SECURITY_ATTRIBUTES;
__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
}
#elif defined(__MACH__)
#include <mach/mach.h>
#elif defined(__MVS__)
#include <zos-semaphore.h>
#elif defined(__unix__)
#include <semaphore.h>
#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE)
#if __GLIBC_PREREQ(2,30)
#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
#endif
#endif
#endif
namespace moodycamel
{
namespace details
{
// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
// portable + lightweight semaphore implementations, originally from
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
// LICENSE:
// Copyright (c) 2015 Jeff Preshing
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgement in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
#if defined(_WIN32)
class Semaphore
{
private:
void* m_hSema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
const long maxLong = 0x7fffffff;
m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
assert(m_hSema);
}
~Semaphore()
{
CloseHandle(m_hSema);
}
bool wait()
{
const unsigned long infinite = 0xffffffff;
return WaitForSingleObject(m_hSema, infinite) == 0;
}
bool try_wait()
{
return WaitForSingleObject(m_hSema, 0) == 0;
}
bool timed_wait(std::uint64_t usecs)
{
return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;
}
void signal(int count = 1)
{
while (!ReleaseSemaphore(m_hSema, count, nullptr));
}
};
#elif defined(__MACH__)
//---------------------------------------------------------
// Semaphore (Apple iOS and OSX)
// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
//---------------------------------------------------------
class Semaphore
{
private:
semaphore_t m_sema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
assert(rc == KERN_SUCCESS);
(void)rc;
}
~Semaphore()
{
semaphore_destroy(mach_task_self(), m_sema);
}
bool wait()
{
return semaphore_wait(m_sema) == KERN_SUCCESS;
}
bool try_wait()
{
return timed_wait(0);
}
bool timed_wait(std::uint64_t timeout_usecs)
{
mach_timespec_t ts;
ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
// added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
kern_return_t rc = semaphore_timedwait(m_sema, ts);
return rc == KERN_SUCCESS;
}
void signal()
{
while (semaphore_signal(m_sema) != KERN_SUCCESS);
}
void signal(int count)
{
while (count-- > 0)
{
while (semaphore_signal(m_sema) != KERN_SUCCESS);
}
}
};
#elif defined(__unix__) || defined(__MVS__)
//---------------------------------------------------------
// Semaphore (POSIX, Linux, zOS)
//---------------------------------------------------------
class Semaphore
{
private:
sem_t m_sema;
Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
public:
Semaphore(int initialCount = 0)
{
assert(initialCount >= 0);
int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
assert(rc == 0);
(void)rc;
}
~Semaphore()
{
sem_destroy(&m_sema);
}
bool wait()
{
// http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
int rc;
do {
rc = sem_wait(&m_sema);
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
bool try_wait()
{
int rc;
do {
rc = sem_trywait(&m_sema);
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
bool timed_wait(std::uint64_t usecs)
{
struct timespec ts;
const int usecs_in_1_sec = 1000000;
const int nsecs_in_1_sec = 1000000000;
#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &ts);
#else
clock_gettime(CLOCK_REALTIME, &ts);
#endif
ts.tv_sec += (time_t)(usecs / usecs_in_1_sec);
ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000;
// sem_timedwait bombs if you have more than 1e9 in tv_nsec
// so we have to clean things up before passing it in
if (ts.tv_nsec >= nsecs_in_1_sec) {
ts.tv_nsec -= nsecs_in_1_sec;
++ts.tv_sec;
}
int rc;
do {
#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC
rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts);
#else
rc = sem_timedwait(&m_sema, &ts);
#endif
} while (rc == -1 && errno == EINTR);
return rc == 0;
}
void signal()
{
while (sem_post(&m_sema) == -1);
}
void signal(int count)
{
while (count-- > 0)
{
while (sem_post(&m_sema) == -1);
}
}
};
#else
#error Unsupported platform! (No semaphore wrapper available)
#endif
} // end namespace details
//---------------------------------------------------------
// LightweightSemaphore
//---------------------------------------------------------
class LightweightSemaphore
{
public:
typedef std::make_signed<std::size_t>::type ssize_t;
private:
std::atomic<ssize_t> m_count;
details::Semaphore m_sema;
int m_maxSpins;
bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1)
{
ssize_t oldCount;
int spin = m_maxSpins;
while (--spin >= 0)
{
oldCount = m_count.load(std::memory_order_relaxed);
if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
return true;
std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop.
}
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
if (oldCount > 0)
return true;
if (timeout_usecs < 0)
{
if (m_sema.wait())
return true;
}
if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs))
return true;
// At this point, we've timed out waiting for the semaphore, but the
// count is still decremented indicating we may still be waiting on
// it. So we have to re-adjust the count, but only if the semaphore
// wasn't signaled enough times for us too since then. If it was, we
// need to release the semaphore too.
while (true)
{
oldCount = m_count.load(std::memory_order_acquire);
if (oldCount >= 0 && m_sema.try_wait())
return true;
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
return false;
}
}
ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1)
{
assert(max > 0);
ssize_t oldCount;
int spin = m_maxSpins;
while (--spin >= 0)
{
oldCount = m_count.load(std::memory_order_relaxed);
if (oldCount > 0)
{
ssize_t newCount = oldCount > max ? oldCount - max : 0;
if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
return oldCount - newCount;
}
std::atomic_signal_fence(std::memory_order_acquire);
}
oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
if (oldCount <= 0)
{
if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs)))
{
while (true)
{
oldCount = m_count.load(std::memory_order_acquire);
if (oldCount >= 0 && m_sema.try_wait())
break;
if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed))
return 0;
}
}
}
if (max > 1)
return 1 + tryWaitMany(max - 1);
return 1;
}
public:
LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins)
{
assert(initialCount >= 0);
assert(maxSpins >= 0);
}
bool tryWait()
{
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
while (oldCount > 0)
{
if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
return true;
}
return false;
}
bool wait()
{
return tryWait() || waitWithPartialSpinning();
}
bool wait(std::int64_t timeout_usecs)
{
return tryWait() || waitWithPartialSpinning(timeout_usecs);
}
// Acquires between 0 and (greedily) max, inclusive
ssize_t tryWaitMany(ssize_t max)
{
assert(max >= 0);
ssize_t oldCount = m_count.load(std::memory_order_relaxed);
while (oldCount > 0)
{
ssize_t newCount = oldCount > max ? oldCount - max : 0;
if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
return oldCount - newCount;
}
return 0;
}
// Acquires at least one, and (greedily) at most max
ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs)
{
assert(max >= 0);
ssize_t result = tryWaitMany(max);
if (result == 0 && max > 0)
result = waitManyWithPartialSpinning(max, timeout_usecs);
return result;
}
ssize_t waitMany(ssize_t max)
{
ssize_t result = waitMany(max, -1);
assert(result > 0);
return result;
}
void signal(ssize_t count = 1)
{
assert(count >= 0);
ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release);
ssize_t toRelease = -oldCount < count ? -oldCount : count;
if (toRelease > 0)
{
m_sema.signal((int)toRelease);
}
}
std::size_t availableApprox() const
{
ssize_t count = m_count.load(std::memory_order_relaxed);
return count > 0 ? static_cast<std::size_t>(count) : 0;
}
};
} // end namespace moodycamel
+318
View File
@@ -0,0 +1,318 @@
#pragma once
#include "moodycamel/blockingconcurrentqueue.h"
#include <algorithm>
#include <atomic>
#include <csignal>
#include <cstring>
#include <future>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#include <syslog.h>
struct ThreadPoolTraits : public moodycamel::ConcurrentQueueDefaultTraits
{
static const int MAX_SEMA_SPINS = 1;
};
class ThreadPool
{
private:
using Func = std::function<void(void)>;
using Queue = moodycamel::BlockingConcurrentQueue<Func,ThreadPoolTraits>;
public:
explicit
ThreadPool(unsigned const thread_count_ = std::thread::hardware_concurrency(),
unsigned const max_queue_depth_ = std::thread::hardware_concurrency(),
std::string const name_ = {})
: _queue(),
_queue_depth(0),
_max_queue_depth(std::max(thread_count_,max_queue_depth_)),
_name(name_)
{
syslog(LOG_DEBUG,
"threadpool (%s): spawning %u threads w/ max queue depth %u%s",
_name.c_str(),
thread_count_,
_max_queue_depth,
((_max_queue_depth != max_queue_depth_) ? " (adjusted)" : ""));
sigset_t oldset;
sigset_t newset;
sigfillset(&newset);
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
_threads.reserve(thread_count_);
for(std::size_t i = 0; i < thread_count_; ++i)
{
int rv;
pthread_t t;
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
if(rv != 0)
{
syslog(LOG_WARNING,
"threadpool (%s): error spawning thread - %d (%s)",
_name.c_str(),
rv,
strerror(rv));
continue;
}
if(!_name.empty())
pthread_setname_np(t,_name.c_str());
_threads.push_back(t);
}
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
if(_threads.empty())
throw std::runtime_error("threadpool: failed to spawn any threads");
}
~ThreadPool()
{
syslog(LOG_DEBUG,
"threadpool (%s): destroying %lu threads",
_name.c_str(),
_threads.size());
auto func = []() { pthread_exit(NULL); };
for(std::size_t i = 0; i < _threads.size(); i++)
_queue.enqueue(func);
for(auto t : _threads)
pthread_cancel(t);
for(auto t : _threads)
pthread_join(t,NULL);
}
private:
static
void*
start_routine(void *arg_)
{
ThreadPool *btp = static_cast<ThreadPool*>(arg_);
ThreadPool::Func func;
ThreadPool::Queue &q = btp->_queue;
std::atomic<unsigned> &queue_depth = btp->_queue_depth;
moodycamel::ConsumerToken ctok(btp->_queue);
while(true)
{
q.wait_dequeue(ctok,func);
func();
queue_depth.fetch_sub(1,std::memory_order_release);
}
return NULL;
}
public:
int
add_thread(std::string const name_ = {})
{
int rv;
pthread_t t;
sigset_t oldset;
sigset_t newset;
std::string name;
name = (name_.empty() ? _name : name_);
sigfillset(&newset);
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
if(rv != 0)
{
syslog(LOG_WARNING,
"threadpool (%s): error spawning thread - %d (%s)",
_name.c_str(),
rv,
strerror(rv));
return -rv;
}
if(!name.empty())
pthread_setname_np(t,name.c_str());
{
std::lock_guard<std::mutex> lg(_threads_mutex);
_threads.push_back(t);
}
syslog(LOG_DEBUG,
"threadpool (%s): 1 thread added named '%s'",
_name.c_str(),
name.c_str());
return 0;
}
int
remove_thread(void)
{
{
std::lock_guard<std::mutex> lg(_threads_mutex);
if(_threads.size() <= 1)
return -EINVAL;
}
std::promise<pthread_t> promise;
auto func = [&]()
{
pthread_t t;
t = pthread_self();
promise.set_value(t);
{
std::lock_guard<std::mutex> lg(_threads_mutex);
for(auto i = _threads.begin(); i != _threads.end(); ++i)
{
if(*i != t)
continue;
_threads.erase(i);
break;
}
}
syslog(LOG_DEBUG,
"threadpool (%s): 1 thread removed",
_name.c_str());
pthread_exit(NULL);
};
enqueue_work(func);
pthread_join(promise.get_future().get(),NULL);
return 0;
}
int
set_threads(std::size_t const count_)
{
int diff;
{
std::lock_guard<std::mutex> lg(_threads_mutex);
diff = ((int)count_ - (int)_threads.size());
}
for(auto i = diff; i > 0; --i)
add_thread();
for(auto i = diff; i < 0; ++i)
remove_thread();
return diff;
}
public:
template<typename FuncType>
void
enqueue_work(moodycamel::ProducerToken &ptok_,
FuncType &&f_)
{
timespec ts = {0,1000};
for(unsigned i = 0; i < 1000000; i++)
{
if(_queue_depth.load(std::memory_order_acquire) < _max_queue_depth)
break;
::nanosleep(&ts,NULL);
}
_queue.enqueue(ptok_,f_);
_queue_depth.fetch_add(1,std::memory_order_release);
}
template<typename FuncType>
void
enqueue_work(FuncType &&f_)
{
timespec ts = {0,1000};
for(unsigned i = 0; i < 1000000; i++)
{
if(_queue_depth.load(std::memory_order_acquire) < _max_queue_depth)
break;
::nanosleep(&ts,NULL);
}
_queue.enqueue(f_);
_queue_depth.fetch_add(1,std::memory_order_release);
}
template<typename FuncType>
[[nodiscard]]
std::future<typename std::result_of<FuncType()>::type>
enqueue_task(FuncType&& f_)
{
using TaskReturnType = typename std::result_of<FuncType()>::type;
using Promise = std::promise<TaskReturnType>;
auto promise = std::make_shared<Promise>();
auto future = promise->get_future();
auto work = [=]()
{
auto rv = f_();
promise->set_value(rv);
};
timespec ts = {0,1000};
for(unsigned i = 0; i < 1000000; i++)
{
if(_queue_depth.load(std::memory_order_acquire) < _max_queue_depth)
break;
::nanosleep(&ts,NULL);
}
_queue.enqueue(work);
_queue_depth.fetch_add(1,std::memory_order_release);
return future;
}
public:
std::vector<pthread_t>
threads() const
{
std::lock_guard<std::mutex> lg(_threads_mutex);
return _threads;
}
moodycamel::ProducerToken
ptoken()
{
return moodycamel::ProducerToken(_queue);
}
private:
Queue _queue;
std::atomic<unsigned> _queue_depth;
unsigned const _max_queue_depth;
private:
std::string const _name;
std::vector<pthread_t> _threads;
mutable std::mutex _threads_mutex;
};
-166
View File
@@ -1,166 +0,0 @@
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include <utility>
template<typename T>
class BoundedQueue
{
public:
explicit
BoundedQueue(std::size_t max_size_,
bool block_ = true)
: _block(block_),
_max_size(max_size_ ? max_size_ : 1)
{
}
BoundedQueue(const BoundedQueue&) = delete;
BoundedQueue(BoundedQueue&&) = default;
bool
push(const T& item_)
{
{
std::unique_lock<std::mutex> guard(_queue_lock);
_condition_push.wait(guard, [&]() { return _queue.size() < _max_size || !_block; });
if(_queue.size() == _max_size)
return false;
_queue.push(item_);
}
_condition_pop.notify_one();
return true;
}
bool
push(T&& item_)
{
{
std::unique_lock<std::mutex> guard(_queue_lock);
_condition_push.wait(guard, [&]() { return _queue.size() < _max_size || !_block; });
if(_queue.size() == _max_size)
return false;
_queue.push(std::move(item_));
}
_condition_pop.notify_one();
return true;
}
template<typename... Args>
bool
emplace(Args&&... args_)
{
{
std::lock_guard<std::mutex> guard(_queue_lock);
_condition_push.wait(guard, [&]() { return _queue.size() < _max_size || !_block; });
if(_queue.size() == _max_size)
return false;
_queue.emplace(std::forward<Args>(args_)...);
}
_condition_pop.notify_one();
return true;
}
bool
pop(T& item_)
{
{
std::unique_lock<std::mutex> guard(_queue_lock);
_condition_pop.wait(guard, [&]() { return !_queue.empty() || !_block; });
if(_queue.empty())
return false;
item_ = std::move(_queue.front());
_queue.pop();
}
_condition_push.notify_one();
return true;
}
std::size_t
size() const
{
std::lock_guard<std::mutex> guard(_queue_lock);
return _queue.size();
}
std::size_t
capacity() const
{
return _max_size;
}
bool
empty() const
{
std::lock_guard<std::mutex> guard(_queue_lock);
return _queue.empty();
}
bool
full() const
{
std::lock_guard<std::mutex> lock(_queue_lock);
return (_queue.size() == capacity());
}
void
block()
{
std::lock_guard<std::mutex> guard(_queue_lock);
_block = true;
}
void
unblock()
{
{
std::lock_guard<std::mutex> guard(_queue_lock);
_block = false;
}
_condition_push.notify_all();
_condition_pop.notify_all();
}
bool
blocking() const
{
std::lock_guard<std::mutex> guard(_queue_lock);
return _block;
}
private:
mutable std::mutex _queue_lock;
private:
bool _block;
std::queue<T> _queue;
const std::size_t _max_size;
std::condition_variable _condition_push;
std::condition_variable _condition_pop;
};
-143
View File
@@ -1,143 +0,0 @@
#pragma once
#include "bounded_queue.hpp"
#include "make_unique.hpp"
#include <signal.h>
#include <tuple>
#include <atomic>
#include <vector>
#include <thread>
#include <memory>
#include <future>
#include <utility>
#include <functional>
#include <type_traits>
class BoundedThreadPool
{
private:
using Proc = std::function<void(void)>;
using Queue = BoundedQueue<Proc>;
using Queues = std::vector<std::unique_ptr<Queue>>;
public:
explicit
BoundedThreadPool(const std::size_t thread_count_ = std::thread::hardware_concurrency(),
const std::size_t queue_depth_ = 1)
: _queues(),
_count(thread_count_)
{
for(std::size_t i = 0; i < thread_count_; i++)
_queues.emplace_back(std::make_unique<Queue>(queue_depth_));
auto worker = [this](std::size_t i)
{
while(true)
{
Proc f;
for(std::size_t n = 0; n < (_count * K); ++n)
{
if(_queues[(i + n) % _count]->pop(f))
break;
}
if(!f && !_queues[i]->pop(f))
break;
f();
}
};
sigset_t oldset;
sigset_t newset;
sigfillset(&newset);
pthread_sigmask(SIG_BLOCK,&newset,&oldset);
_threads.reserve(thread_count_);
for(std::size_t i = 0; i < thread_count_; ++i)
_threads.emplace_back(worker, i);
pthread_sigmask(SIG_SETMASK,&oldset,NULL);
}
~BoundedThreadPool()
{
for(auto &queue : _queues)
queue->unblock();
for(auto &thread : _threads)
pthread_cancel(thread.native_handle());
for(auto &thread : _threads)
thread.join();
}
template<typename F>
void
enqueue_work(F&& f_)
{
auto i = _index++;
for(std::size_t n = 0; n < (_count * K); ++n)
{
if(_queues[(i + n) % _count]->push(f_))
return;
}
_queues[i % _count]->push(std::move(f_));
}
template<typename F>
[[nodiscard]]
std::future<typename std::result_of<F()>::type>
enqueue_task(F&& f_)
{
using TaskReturnType = typename std::result_of<F()>::type;
using Promise = std::promise<TaskReturnType>;
auto i = _index++;
auto promise = std::make_shared<Promise>();
auto future = promise->get_future();
auto work = [=]() {
auto rv = f_();
promise->set_value(rv);
};
for(std::size_t n = 0; n < (_count * K); ++n)
{
if(_queues[(i + n) % _count]->push(work))
return future;
}
_queues[i % _count]->push(std::move(work));
return future;
}
public:
std::vector<pthread_t>
threads()
{
std::vector<pthread_t> rv;
for(auto &thread : _threads)
rv.push_back(thread.native_handle());
return rv;
}
private:
Queues _queues;
private:
std::vector<std::thread> _threads;
private:
const std::size_t _count;
std::atomic_uint _index;
static const unsigned int K = 2;
};
+137 -39
View File
@@ -44,6 +44,7 @@
#include <sys/param.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
@@ -72,10 +73,6 @@ struct fuse_config
int set_uid;
int set_gid;
int help;
int read_thread_count;
int process_thread_count;
int process_thread_queue_depth;
char *pin_threads;
};
struct fuse_fs
@@ -180,6 +177,19 @@ static pthread_key_t fuse_context_key;
static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
static int fuse_context_ref;
static
int
fuse_valid_type(uint32_t const m_)
{
return (S_ISREG(m_) ||
S_ISDIR(m_) ||
S_ISLNK(m_) ||
S_ISCHR(m_) ||
S_ISBLK(m_) ||
S_ISFIFO(m_) ||
S_ISSOCK(m_));
}
/*
Why was the nodeid:generation logic simplified?
@@ -640,7 +650,7 @@ rehash_name(struct fuse *f)
static
int
hash_name(struct fuse *f,
node_t *node,
node_t *node,
uint64_t parentid,
const char *name)
{
@@ -1572,6 +1582,7 @@ fuse_lib_lookup(fuse_req_t req,
{
pthread_mutex_unlock(&f->lock);
reply_entry(req,&e,-ESTALE);
syslog(LOG_ERR,". for nodeid %zu is stale",nodeid);
return;
}
dot->refctr++;
@@ -1579,9 +1590,10 @@ fuse_lib_lookup(fuse_req_t req,
}
else if((name[1] == '.') && (name[2] == '\0'))
{
if(nodeid == 1)
if(nodeid == FUSE_ROOT_ID)
{
reply_entry(req,&e,-ENOENT);
syslog(LOG_ERR,".. for root node????");
return;
}
@@ -1700,6 +1712,13 @@ fuse_lib_getattr(fuse_req_t req,
free_path(f,hdr_->nodeid,path);
}
if(buf.st_size > LLONG_MAX)
syslog(LOG_ERR,"%s: %zu size > LLONG_MAX %zu",__FUNCTION__,hdr_->nodeid,buf.st_size);
if(!fuse_valid_type(buf.st_mode))
syslog(LOG_ERR,"%s: %zu invalid type %x",__FUNCTION__,hdr_->nodeid,buf.st_mode);
if(hdr_->nodeid == FUSE_ROOT_ID && !S_ISDIR(buf.st_mode))
syslog(LOG_ERR,"%s: rootid not type DIR %x",__FUNCTION__,buf.st_mode);
if(!err)
{
pthread_mutex_lock(&f->lock);
@@ -1821,6 +1840,14 @@ fuse_lib_setattr(fuse_req_t req,
f->fs->op.getattr(path,&stbuf,&timeout) :
f->fs->op.fgetattr(fi,&stbuf,&timeout));
if(stbuf.st_size > LLONG_MAX)
syslog(LOG_ERR,"%s: %zu size > LLONG_MAX %zu",__FUNCTION__,hdr_->nodeid,stbuf.st_size);
if(!fuse_valid_type(stbuf.st_mode))
syslog(LOG_ERR,"%s: %zu invalid type %x",__FUNCTION__,hdr_->nodeid,stbuf.st_mode);
if(hdr_->nodeid == FUSE_ROOT_ID && !S_ISDIR(stbuf.st_mode))
syslog(LOG_ERR,"%s: rootid not type DIR %x",__FUNCTION__,stbuf.st_mode);
free_path(f,hdr_->nodeid,path);
}
@@ -2881,6 +2908,101 @@ fuse_lib_copy_file_range(fuse_req_t req_,
fuse_reply_err(req_,rv);
}
static
void
fuse_lib_setupmapping(fuse_req_t req_,
const struct fuse_in_header *hdr_)
{
fuse_reply_err(req_,ENOSYS);
}
static
void
fuse_lib_removemapping(fuse_req_t req_,
const struct fuse_in_header *hdr_)
{
fuse_reply_err(req_,ENOSYS);
}
static
void
fuse_lib_syncfs(fuse_req_t req_,
const struct fuse_in_header *hdr_)
{
fuse_reply_err(req_,ENOSYS);
}
// TODO: This is just a copy of fuse_lib_create. Needs to be rewritten
// so a nameless node can be setup.
// name is always '/'
// nodeid is the base directory
static
void
fuse_lib_tmpfile(fuse_req_t req_,
const struct fuse_in_header *hdr_)
{
int err;
char *path;
struct fuse *f;
const char *name;
fuse_file_info_t ffi = {0};
struct fuse_entry_param e;
struct fuse_create_in *arg;
arg = fuse_hdr_arg(hdr_);
name = PARAM(arg);
ffi.flags = arg->flags;
if(req_->f->conn.proto_minor >= 12)
req_->ctx.umask = arg->umask;
else
name = (char*)arg + sizeof(struct fuse_open_in);
f = req_fuse_prepare(req_);
err = get_path_name(f,hdr_->nodeid,name,&path);
if(!err)
{
err = f->fs->op.tmpfile(path,arg->mode,&ffi);
if(!err)
{
err = lookup_path(f,hdr_->nodeid,name,path,&e,&ffi);
if(err)
{
f->fs->op.release(&ffi);
}
else if(!S_ISREG(e.attr.st_mode))
{
err = -EIO;
f->fs->op.release(&ffi);
forget_node(f,e.ino,1);
}
}
}
if(!err)
{
pthread_mutex_lock(&f->lock);
get_node(f,e.ino)->open_count++;
pthread_mutex_unlock(&f->lock);
if(fuse_reply_create(req_,&e,&ffi) == -ENOENT)
{
/* The open syscall was interrupted,so it
must be cancelled */
fuse_do_release(f,e.ino,&ffi);
forget_node(f,e.ino,1);
}
}
else
{
fuse_reply_err(req_,err);
}
free_path(f,hdr_->nodeid,path);
}
static
lock_t*
locks_conflict(node_t *node,
@@ -3535,15 +3657,19 @@ static struct fuse_lowlevel_ops fuse_path_ops =
.readlink = fuse_lib_readlink,
.release = fuse_lib_release,
.releasedir = fuse_lib_releasedir,
.removemapping = fuse_lib_removemapping,
.removexattr = fuse_lib_removexattr,
.rename = fuse_lib_rename,
.retrieve_reply = NULL,
.rmdir = fuse_lib_rmdir,
.setattr = fuse_lib_setattr,
.setlk = fuse_lib_setlk,
.setupmapping = fuse_lib_setupmapping,
.setxattr = fuse_lib_setxattr,
.statfs = fuse_lib_statfs,
.symlink = fuse_lib_symlink,
.syncfs = fuse_lib_syncfs,
.tmpfile = fuse_lib_tmpfile,
.unlink = fuse_lib_unlink,
.write = fuse_lib_write,
};
@@ -3601,11 +3727,6 @@ static const struct fuse_opt fuse_lib_opts[] =
FUSE_LIB_OPT("gid=%d", gid,0),
FUSE_LIB_OPT("noforget", remember,-1),
FUSE_LIB_OPT("remember=%u", remember,0),
FUSE_LIB_OPT("threads=%d", read_thread_count,0),
FUSE_LIB_OPT("read-thread-count=%d", read_thread_count,0),
FUSE_LIB_OPT("process-thread-count=%d", process_thread_count,-1),
FUSE_LIB_OPT("process-thread-queue-depth=%d", process_thread_queue_depth,-1),
FUSE_LIB_OPT("pin-threads=%s", pin_threads, 0),
FUSE_OPT_END
};
@@ -3809,7 +3930,7 @@ fuse_invalidate_all_nodes()
{
struct fuse *f = fuse_get_fuse_obj();
syslog_info("invalidating file entries");
syslog(LOG_INFO,"invalidating file entries");
pthread_mutex_lock(&f->lock);
for(int i = 0; i < f->id_table.size; i++)
@@ -3835,7 +3956,7 @@ fuse_invalidate_all_nodes()
void
fuse_gc()
{
syslog_info("running thorough garbage collection");
syslog(LOG_INFO,"running thorough garbage collection");
node_gc();
msgbuf_gc();
fuse_malloc_trim();
@@ -3844,7 +3965,7 @@ fuse_gc()
void
fuse_gc1()
{
syslog_info("running basic garbage collection");
syslog(LOG_INFO,"running basic garbage collection");
node_gc1();
msgbuf_gc_10percent();
fuse_malloc_trim();
@@ -3858,6 +3979,8 @@ fuse_maintenance_loop(void *fuse_)
int sleep_time;
struct fuse *f = (struct fuse*)fuse_;
pthread_setname_np(pthread_self(),"fuse.maint");
loops = 0;
sleep_time = 60;
while(1)
@@ -4044,31 +4167,6 @@ fuse_destroy(struct fuse *f)
fuse_delete_context_key();
}
int
fuse_config_read_thread_count(const struct fuse *f_)
{
return f_->conf.read_thread_count;
}
int
fuse_config_process_thread_count(const struct fuse *f_)
{
return f_->conf.process_thread_count;
}
int
fuse_config_process_thread_queue_depth(const struct fuse *f_)
{
return f_->conf.process_thread_queue_depth;
}
const
char*
fuse_config_pin_threads(const struct fuse *f_)
{
return f_->conf.pin_threads;
}
void
fuse_log_metrics_set(int log_)
{
+73
View File
@@ -0,0 +1,73 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <string>
static int g_READ_THREAD_COUNT = -1;
static int g_PROCESS_THREAD_COUNT = -1;
static int g_PROCESS_THREAD_QUEUE_DEPTH = -1;
static std::string g_PIN_THREADS = {};
int
fuse_config_get_read_thread_count()
{
return g_READ_THREAD_COUNT;
}
void
fuse_config_set_read_thread_count(int const v_)
{
g_READ_THREAD_COUNT = v_;
}
int
fuse_config_get_process_thread_count()
{
return g_PROCESS_THREAD_COUNT;
}
void
fuse_config_set_process_thread_count(int const v_)
{
g_PROCESS_THREAD_COUNT = v_;
}
int
fuse_config_get_process_thread_queue_depth()
{
return g_PROCESS_THREAD_QUEUE_DEPTH;
}
void
fuse_config_set_process_thread_queue_depth(int const v_)
{
g_PROCESS_THREAD_QUEUE_DEPTH = v_;
}
std::string
fuse_config_get_pin_threads()
{
return g_PIN_THREADS;
}
void
fuse_config_set_pin_threads(std::string const v_)
{
g_PIN_THREADS = v_;
}
+8 -8
View File
@@ -304,9 +304,9 @@ fuse_dirents_add_plus(fuse_dirents_t *d_,
}
int
fuse_dirents_add_linux(fuse_dirents_t *d_,
const struct linux_dirent64 *dirent_,
const uint64_t namelen_)
fuse_dirents_add_linux(fuse_dirents_t *d_,
const linux_dirent64_t *dirent_,
const uint64_t namelen_)
{
fuse_dirent_t *d;
@@ -336,11 +336,11 @@ fuse_dirents_add_linux(fuse_dirents_t *d_,
}
int
fuse_dirents_add_linux_plus(fuse_dirents_t *d_,
const struct linux_dirent64 *dirent_,
const uint64_t namelen_,
const fuse_entry_t *entry_,
const struct stat *st_)
fuse_dirents_add_linux_plus(fuse_dirents_t *d_,
const linux_dirent64_t *dirent_,
const uint64_t namelen_,
const fuse_entry_t *entry_,
const struct stat *st_)
{
fuse_direntplus_t *d;
@@ -2,17 +2,18 @@
#define _GNU_SOURCE
#endif
#include "bounded_thread_pool.hpp"
#include "cpu.hpp"
#include "fmt/core.h"
#include "make_unique.hpp"
#include "scope_guard.hpp"
#include "syslog.h"
#include "thread_pool.hpp"
#include "fuse_i.h"
#include "fuse_kernel.h"
#include "fuse_lowlevel.h"
#include "fuse_misc.h"
#include "fuse_config.hpp"
#include "fuse_msgbuf.hpp"
#include "fuse_ll.hpp"
@@ -24,6 +25,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <syslog.h>
#include <unistd.h>
#include <cassert>
@@ -68,11 +70,11 @@ struct AsyncWorker
{
fuse_session *_se;
sem_t *_finished;
std::shared_ptr<BoundedThreadPool> _process_tp;
std::shared_ptr<ThreadPool> _process_tp;
AsyncWorker(fuse_session *se_,
sem_t *finished_,
std::shared_ptr<BoundedThreadPool> process_tp_)
std::shared_ptr<ThreadPool> process_tp_)
: _se(se_),
_finished(finished_),
_process_tp(process_tp_)
@@ -86,6 +88,7 @@ struct AsyncWorker
DEFER{ fuse_session_exit(_se); };
DEFER{ sem_post(_finished); };
moodycamel::ProducerToken ptok(_process_tp->ptoken());
while(!fuse_session_exited(_se))
{
int rv;
@@ -106,10 +109,13 @@ struct AsyncWorker
return handle_receive_error(rv,msgbuf);
} while(false);
_process_tp->enqueue_work([=] {
auto const func = [=]
{
_se->process_buf(_se,msgbuf);
msgbuf_free(msgbuf);
});
};
_process_tp->enqueue_work(ptok,func);
}
}
};
@@ -418,10 +424,12 @@ pin_threads_R1PPSP(const std::vector<pthread_t> read_threads_,
static
void
pin_threads(const std::vector<pthread_t> read_threads_,
const std::vector<pthread_t> process_threads_,
const std::string type_)
pin_threads(const std::vector<pthread_t> read_threads_,
const std::vector<pthread_t> process_threads_,
const std::string type_)
{
if(type_.empty() || (type_ == "false"))
return;
if(type_ == "R1L")
return ::pin_threads_R1L(read_threads_);
if(type_ == "R1P")
@@ -440,6 +448,10 @@ pin_threads(const std::vector<pthread_t> read_threads_,
return ::pin_threads_RPSP(read_threads_,process_threads_);
if(type_ == "R1PPSP")
return ::pin_threads_R1PPSP(read_threads_,process_threads_);
syslog(LOG_WARNING,
"Invalid pin-threads value, ignoring: %s",
type_.c_str());
}
static
@@ -456,7 +468,7 @@ fuse_session_loop_mt(struct fuse_session *se_,
const int raw_read_thread_count_,
const int raw_process_thread_count_,
const int raw_process_thread_queue_depth_,
const char *pin_threads_type_)
const std::string pin_threads_type_)
{
sem_t finished;
int read_thread_count;
@@ -464,8 +476,8 @@ fuse_session_loop_mt(struct fuse_session *se_,
int process_thread_queue_depth;
std::vector<pthread_t> read_threads;
std::vector<pthread_t> process_threads;
std::unique_ptr<BoundedThreadPool> read_tp;
std::shared_ptr<BoundedThreadPool> process_tp;
std::unique_ptr<ThreadPool> read_tp;
std::shared_ptr<ThreadPool> process_tp;
sem_init(&finished,0,0);
@@ -477,9 +489,14 @@ fuse_session_loop_mt(struct fuse_session *se_,
&process_thread_queue_depth);
if(process_thread_count > 0)
process_tp = std::make_shared<BoundedThreadPool>(process_thread_count,process_thread_queue_depth);
process_tp = std::make_shared<ThreadPool>(process_thread_count,
(process_thread_count *
process_thread_queue_depth),
"fuse.process");
read_tp = std::make_unique<BoundedThreadPool>(read_thread_count);
read_tp = std::make_unique<ThreadPool>(read_thread_count,
read_thread_count,
"fuse.read");
if(process_tp)
{
for(auto i = 0; i < read_thread_count; i++)
@@ -496,13 +513,18 @@ fuse_session_loop_mt(struct fuse_session *se_,
if(process_tp)
process_threads = process_tp->threads();
if(pin_threads_type_ != nullptr)
::pin_threads(read_threads,process_threads,pin_threads_type_);
::pin_threads(read_threads,process_threads,pin_threads_type_);
syslog_info("read-thread-count=%d; process-thread-count=%d; process-thread-queue-depth=%d",
read_thread_count,
process_thread_count,
process_thread_queue_depth);
syslog(LOG_INFO,
"read-thread-count=%d; "
"process-thread-count=%d; "
"process-thread-queue-depth=%d; "
"pin-threads=%s;"
,
read_thread_count,
process_thread_count,
process_thread_queue_depth,
pin_threads_type_.c_str());
::wait(se_,&finished);
@@ -510,3 +532,24 @@ fuse_session_loop_mt(struct fuse_session *se_,
return 0;
}
int
fuse_loop_mt(struct fuse *f)
{
if(f == NULL)
return -1;
int res = fuse_start_maintenance_thread(f);
if(res)
return -1;
res = fuse_session_loop_mt(fuse_get_session(f),
fuse_config_get_read_thread_count(),
fuse_config_get_process_thread_count(),
fuse_config_get_process_thread_queue_depth(),
fuse_config_get_pin_threads());
fuse_stop_maintenance_thread(f);
return res;
}
+68 -5
View File
@@ -19,15 +19,16 @@
#include "fuse_pollhandle.h"
#include "fuse_msgbuf.hpp"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>
#include <sys/file.h>
#include <syslog.h>
#include <unistd.h>
#ifndef F_LINUX_SPECIFIC_BASE
#define F_LINUX_SPECIFIC_BASE 1024
@@ -275,6 +276,8 @@ fill_open(struct fuse_open_out *arg_,
arg_->open_flags |= FOPEN_CACHE_DIR;
if(ffi_->parallel_direct_writes)
arg_->open_flags |= FOPEN_PARALLEL_DIRECT_WRITES;
if(ffi_->noflush)
arg_->open_flags |= FOPEN_NOFLUSH;
}
int
@@ -318,6 +321,19 @@ fuse_reply_create(fuse_req_t req,
return send_reply_ok(req, &buf, entrysize + sizeof(struct fuse_open_out));
}
static
int
fuse_valid_type(uint32_t const m_)
{
return (S_ISREG(m_) ||
S_ISDIR(m_) ||
S_ISLNK(m_) ||
S_ISCHR(m_) ||
S_ISBLK(m_) ||
S_ISFIFO(m_) ||
S_ISSOCK(m_));
}
int
fuse_reply_attr(fuse_req_t req,
const struct stat *attr,
@@ -331,6 +347,11 @@ fuse_reply_attr(fuse_req_t req,
arg.attr_valid_nsec = 0;
convert_stat(attr,&arg.attr);
if(arg.attr.size > LLONG_MAX)
syslog(LOG_ERR,"fuse_reply_attr: attr.size > LLONG_MAX");
if(!fuse_valid_type(arg.attr.mode))
syslog(LOG_ERR,"fuse_reply_attr: invalid type %x",arg.attr.mode);
return send_reply_ok(req,&arg,size);
}
@@ -1330,6 +1351,42 @@ do_copy_file_range(fuse_req_t req_,
req_->f->op.copy_file_range(req_,hdr_);
}
static
void
do_setupmapping(fuse_req_t req_,
struct fuse_in_header *hdr_)
{
printf("setupmapping\n");
req_->f->op.setupmapping(req_,hdr_);
}
static
void
do_removemapping(fuse_req_t req_,
struct fuse_in_header *hdr_)
{
printf("removemapping\n");
req_->f->op.removemapping(req_,hdr_);
}
static
void
do_syncfs(fuse_req_t req_,
struct fuse_in_header *hdr_)
{
printf("syncfs\n");
req_->f->op.syncfs(req_,hdr_);
}
static
void
do_tmpfile(fuse_req_t req_,
struct fuse_in_header *hdr_)
{
printf("tmpfile\n");
req_->f->op.tmpfile(req_,hdr_);
}
static
int
send_notify_iov(struct fuse_ll *f,
@@ -1643,6 +1700,10 @@ static struct {
[FUSE_NOTIFY_REPLY] = { do_notify_reply, "NOTIFY_REPLY" },
[FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
[FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
[FUSE_SETUPMAPPING] = { do_setupmapping, "SETUPMAPPING" },
[FUSE_REMOVEMAPPING] = { do_removemapping, "REMOVEMAPPING" },
[FUSE_SYNCFS] = { do_syncfs, "SYNCFS" },
[FUSE_TMPFILE] = { do_tmpfile, "TMPFILE" }
};
#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
@@ -1815,6 +1876,8 @@ fuse_ll_buf_process_read(struct fuse_session *se_,
in = (struct fuse_in_header*)msgbuf_->mem;
// printf("%d\n",in->opcode);
req = fuse_ll_alloc_req(se_->f);
if(req == NULL)
return fuse_send_enomem(se_->f,se_->ch,in->unique);
+4 -4
View File
@@ -115,7 +115,7 @@ _msgbuf_alloc(msgbuf_setup_func_t setup_func_)
if(msgbuf == NULL)
return NULL;
g_MSGBUF_ALLOC_COUNT++;
g_MSGBUF_ALLOC_COUNT.fetch_add(1,std::memory_order_relaxed);
}
else
{
@@ -160,7 +160,7 @@ msgbuf_free(fuse_msgbuf_t *msgbuf_)
if(msgbuf_->size != (g_BUFSIZE - g_PAGESIZE))
{
msgbuf_destroy(msgbuf_);
g_MSGBUF_ALLOC_COUNT--;
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
return;
}
@@ -206,7 +206,7 @@ msgbuf_gc_10percent()
for(auto msgbuf : togc)
{
msgbuf_destroy(msgbuf);
g_MSGBUF_ALLOC_COUNT--;
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
}
}
@@ -223,6 +223,6 @@ msgbuf_gc()
for(auto msgbuf: oldstack)
{
msgbuf_destroy(msgbuf);
g_MSGBUF_ALLOC_COUNT--;
g_MSGBUF_ALLOC_COUNT.fetch_sub(1,std::memory_order_relaxed);
}
}
-38
View File
@@ -1,38 +0,0 @@
/*
FUSE: Filesystem in Userspace
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
This program can be distributed under the terms of the GNU LGPLv2.
See the file COPYING.LIB.
*/
#include "fuse_i.h"
#include "fuse_misc.h"
#include "fuse_lowlevel.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <assert.h>
int
fuse_loop_mt(struct fuse *f)
{
if (f == NULL)
return -1;
int res = fuse_start_maintenance_thread(f);
if (res)
return -1;
res = fuse_session_loop_mt(fuse_get_session(f),
fuse_config_read_thread_count(f),
fuse_config_process_thread_count(f),
fuse_config_process_thread_queue_depth(f),
fuse_config_pin_threads(f));
fuse_stop_maintenance_thread(f);
return res;
}
+11 -10
View File
@@ -61,6 +61,7 @@ enum {
};
struct mount_opts {
int allow_other;
int ishelp;
int flags;
int auto_unmount;
@@ -76,13 +77,15 @@ struct mount_opts {
#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
static const struct fuse_opt fuse_mount_opts[] = {
FUSE_MOUNT_OPT("allow_other", allow_other),
FUSE_MOUNT_OPT("blkdev", blkdev),
FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
FUSE_MOUNT_OPT("fsname=%s", fsname),
FUSE_MOUNT_OPT("subtype=%s", subtype),
FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
FUSE_OPT_KEY("large_read", KEY_KERN_OPT),
FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
@@ -98,18 +101,18 @@ static const struct fuse_opt fuse_mount_opts[] = {
FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
FUSE_OPT_KEY("async", KEY_KERN_FLAG),
FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
FUSE_OPT_KEY("atime", KEY_KERN_FLAG),
FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_KEY("-V", KEY_VERSION),
FUSE_OPT_KEY("--version", KEY_VERSION),
FUSE_OPT_END
@@ -549,8 +552,6 @@ int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
fuse_opt_parse(args, &mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
return -1;
fuse_opt_add_opt(&mo.kernel_opts,"allow_other");
res = 0;
if (mo.ishelp)
goto out;
-103
View File
@@ -1,103 +0,0 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdarg.h>
#include <syslog.h>
#include <stdbool.h>
void
syslog_open()
{
const char *ident = "mergerfs";
const int option = (LOG_CONS|LOG_PID);
const int facility = LOG_USER;
openlog(ident,option,facility);
}
void
syslog_close()
{
closelog();
}
static
void
syslog_vlog(const int priority_,
const char *format_,
va_list valist_)
{
vsyslog(priority_,format_,valist_);
}
void
syslog_log(const int priority_,
const char *format_,
...)
{
va_list valist;
va_start(valist,format_);
syslog_vlog(priority_,format_,valist);
va_end(valist);
}
void
syslog_info(const char *format_,
...)
{
va_list valist;
va_start(valist,format_);
syslog_vlog(LOG_INFO,format_,valist);
va_end(valist);
}
void
syslog_notice(const char *format_,
...)
{
va_list valist;
va_start(valist,format_);
syslog_vlog(LOG_NOTICE,format_,valist);
va_end(valist);
}
void
syslog_warning(const char *format_,
...)
{
va_list valist;
va_start(valist,format_);
syslog_vlog(LOG_WARNING,format_,valist);
va_end(valist);
}
void
syslog_error(const char *format_,
...)
{
va_list valist;
va_start(valist,format_);
syslog_vlog(LOG_ERR,format_,valist);
va_end(valist);
}
-125
View File
@@ -1,125 +0,0 @@
#pragma once
#include "unbounded_queue.hpp"
#include "bounded_queue.hpp"
#include <tuple>
#include <atomic>
#include <vector>
#include <thread>
#include <memory>
#include <future>
#include <utility>
#include <functional>
#include <type_traits>
class ThreadPool
{
public:
explicit
ThreadPool(const std::size_t thread_count_ = std::thread::hardware_concurrency())
: _queues(thread_count_),
_count(thread_count_)
{
auto worker = [this](std::size_t i)
{
while(true)
{
Proc f;
for(std::size_t n = 0; n < (_count * K); ++n)
{
if(_queues[(i + n) % _count].try_pop(f))
break;
}
if(!f && !_queues[i].pop(f))
break;
f();
}
};
_threads.reserve(thread_count_);
for(std::size_t i = 0; i < thread_count_; ++i)
_threads.emplace_back(worker, i);
}
~ThreadPool()
{
for(auto& queue : _queues)
queue.unblock();
for(auto& thread : _threads)
thread.join();
}
template<typename F>
void
enqueue_work(F&& f_)
{
auto i = _index++;
for(std::size_t n = 0; n < (_count * K); ++n)
{
if(_queues[(i + n) % _count].try_push(f_))
return;
}
_queues[i % _count].push(std::move(f_));
}
template<typename F>
[[nodiscard]]
std::future<typename std::result_of<F()>::type>
enqueue_task(F&& f_)
{
using TaskReturnType = typename std::result_of<F()>::type;
using Promise = std::promise<TaskReturnType>;
auto i = _index++;
auto promise = std::make_shared<Promise>();
auto future = promise->get_future();
auto work = [=]() {
auto rv = f_();
promise->set_value(rv);
};
for(std::size_t n = 0; n < (_count * K); ++n)
{
if(_queues[(i + n) % _count].try_push(work))
return future;
}
_queues[i % _count].push(std::move(work));
return future;
}
public:
std::vector<pthread_t>
threads()
{
std::vector<pthread_t> rv;
for(auto &thread : _threads)
rv.push_back(thread.native_handle());
return rv;
}
private:
using Proc = std::function<void(void)>;
using Queue = UnboundedQueue<Proc>;
using Queues = std::vector<Queue>;
Queues _queues;
private:
std::vector<std::thread> _threads;
private:
const std::size_t _count;
std::atomic_uint _index;
static const unsigned int K = 2;
};
-161
View File
@@ -1,161 +0,0 @@
#pragma once
#include <condition_variable>
#include <mutex>
#include <queue>
#include <utility>
template<typename T>
class UnboundedQueue
{
public:
explicit
UnboundedQueue(bool block_ = true)
: _block(block_)
{
}
void
push(const T& item_)
{
{
std::lock_guard<std::mutex> guard(_queue_lock);
_queue.push(item_);
}
_condition.notify_one();
}
void
push(T&& item_)
{
{
std::lock_guard<std::mutex> guard(_queue_lock);
_queue.push(std::move(item_));
}
_condition.notify_one();
}
template<typename... Args>
void
emplace(Args&&... args_)
{
{
std::lock_guard<std::mutex> guard(_queue_lock);
_queue.emplace(std::forward<Args>(args_)...);
}
_condition.notify_one();
}
bool
try_push(const T& item_)
{
{
std::unique_lock<std::mutex> lock(_queue_lock, std::try_to_lock);
if(!lock)
return false;
_queue.push(item_);
}
_condition.notify_one();
return true;
}
bool
try_push(T&& item_)
{
{
std::unique_lock<std::mutex> lock(_queue_lock, std::try_to_lock);
if(!lock)
return false;
_queue.push(std::move(item_));
}
_condition.notify_one();
return true;
}
//TODO: push multiple T at once
bool
pop(T& item_)
{
std::unique_lock<std::mutex> guard(_queue_lock);
_condition.wait(guard, [&]() { return !_queue.empty() || !_block; });
if(_queue.empty())
return false;
item_ = std::move(_queue.front());
_queue.pop();
return true;
}
bool
try_pop(T& item_)
{
std::unique_lock<std::mutex> lock(_queue_lock, std::try_to_lock);
if(!lock || _queue.empty())
return false;
item_ = std::move(_queue.front());
_queue.pop();
return true;
}
std::size_t
size() const
{
std::lock_guard<std::mutex> guard(_queue_lock);
return _queue.size();
}
bool
empty() const
{
std::lock_guard<std::mutex> guard(_queue_lock);
return _queue.empty();
}
void
block()
{
std::lock_guard<std::mutex> guard(_queue_lock);
_block = true;
}
void
unblock()
{
{
std::lock_guard<std::mutex> guard(_queue_lock);
_block = false;
}
_condition.notify_all();
}
bool
blocking() const
{
std::lock_guard<std::mutex> guard(_queue_lock);
return _block;
}
private:
mutable std::mutex _queue_lock;
private:
bool _block;
std::queue<T> _queue;
std::condition_variable _condition;
};
+357 -121
View File
@@ -321,6 +321,11 @@ by the number of process threads plus read thread count.
\f[B]pin-threads=STR\f[R]: Selects a strategy to pin threads to CPUs
(default: unset)
.IP \[bu] 2
\f[B]flush-on-close=never|always|opened-for-write\f[R]: Flush data cache
on file close.
Mostly for when writeback is enabled or merging network filesystems.
(default: opened-for-write)
.IP \[bu] 2
\f[B]scheduling-priority=INT\f[R]: Set mergerfs\[cq] scheduling
priority.
Valid values range from -20 to 19.
@@ -337,6 +342,11 @@ policy.
See below for the list of value types.
Example: \f[B]func.getattr=newest\f[R]
.IP \[bu] 2
\f[B]func.readdir=seq|cosr|cor|cosr:INT|cor:INT\f[R]: Sets
\f[C]readdir\f[R] policy.
INT value sets the number of threads to use for concurrency.
(default: seq)
.IP \[bu] 2
\f[B]category.action=POLICY\f[R]: Sets policy of all FUSE functions in
the action category.
(default: epall)
@@ -386,7 +396,9 @@ to enable page caching for when \f[C]cache.files=per-process\f[R].
.IP \[bu] 2
\f[B]parallel-direct-writes=BOOL\f[R]: Allow the kernel to dispatch
multiple, parallel (non-extending) write requests for files opened with
\f[C]direct_io=true\f[R] (if supported by the kernel)
\f[C]cache.files=per-process\f[R] (if the process is not in
\f[C]process-names\f[R]) or \f[C]cache.files=off\f[R].
(This requires kernel support, and was added in v6.2)
.IP \[bu] 2
\f[B]direct_io\f[R]: deprecated - Bypass page cache.
Use \f[C]cache.files=off\f[R] instead.
@@ -414,8 +426,8 @@ Use \f[C]async_read=false\f[R] instead.
.IP \[bu] 2
\f[B]splice_move\f[R]: deprecated - Does nothing.
.IP \[bu] 2
\f[B]allow_other\f[R]: deprecated - mergerfs always sets this FUSE
option as normal permissions can be used to limit access.
\f[B]allow_other\f[R]: deprecated - mergerfs v2.35.0 and newer sets this
FUSE option automatically if running as root.
.IP \[bu] 2
\f[B]use_ino\f[R]: deprecated - mergerfs should always control inode
calculation so this is enabled all the time.
@@ -823,7 +835,7 @@ Even though it\[cq]s a more niche situation this hack breaks normal
security and behavior and as such is \f[C]off\f[R] by default.
If set to \f[C]git\f[R] it will only perform the hack when the path in
question includes \f[C]/.git/\f[R].
\f[C]all\f[R] will result it applying anytime a readonly file which is
\f[C]all\f[R] will result it applying anytime a read-only file which is
empty is opened for writing.
.SH FUNCTIONS, CATEGORIES and POLICIES
.PP
@@ -844,8 +856,6 @@ provide the file handle when a client calls \f[C]fgetattr\f[R],
\f[C]fchown\f[R], \f[C]fchmod\f[R], \f[C]futimens\f[R],
\f[C]ftruncate\f[R], etc.
This means it will call the regular, path based, versions.
\f[C]readdir\f[R] has no real need for a policy given the purpose is
merely to return a list of entries in a directory.
\f[C]statfs\f[R]\[cq]s behavior can be modified via other options.
.PP
When using policies which are based on a branch\[cq]s available space
@@ -856,8 +866,8 @@ calculations.
The reason is that it doesn\[cq]t really work for non-path preserving
policies and can lead to non-obvious behaviors.
.PP
NOTE: While any policy can be assigned to a function or category though
some may not be very useful in practice.
NOTE: While any policy can be assigned to a function or category, some
may not be very useful in practice.
For instance: \f[B]rand\f[R] (random) may be useful for file creation
(create) but could lead to very odd behavior if used for \f[C]chmod\f[R]
if there were more than one copy of the file.
@@ -902,8 +912,7 @@ In cases where something may be searched for (such as a path to clone)
.SS Policies
.PP
A policy is the algorithm used to choose a branch or branches for a
function to work on.
Think of them as ways to filter and sort branches.
function to work on or generally how the function behaves.
.PP
Any function in the \f[C]create\f[R] category will clone the relative
path if needed.
@@ -912,12 +921,12 @@ have special requirements or behaviors which you can read more about
below.
.SS Filtering
.PP
Policies basically search branches and create a list of files / paths
for functions to work on.
Most policies basically search branches and create a list of files /
paths for functions to work on.
The policy is responsible for filtering and sorting the branches.
Filters include \f[B]minfreespace\f[R], whether or not a branch is
mounted read-only, and the branch tagging (RO,NC,RW).
These filters are applied across all policies unless otherwise noted.
These filters are applied across most policies.
.IP \[bu] 2
No \f[B]search\f[R] function policies filter.
.IP \[bu] 2
@@ -1134,6 +1143,63 @@ T}@T{
ff
T}
.TE
.SS func.readdir
.PP
examples: \f[C]func.readdir=seq\f[R], \f[C]func.readdir=cor:4\f[R]
.PP
\f[C]readdir\f[R] has policies to control how it manages reading
directory content.
.PP
.TS
tab(@);
lw(26.7n) lw(43.3n).
T{
Policy
T}@T{
Description
T}
_
T{
seq
T}@T{
\[lq]sequential\[rq] : Iterate over branches in the order defined.
This is the default and traditional behavior found prior to the readdir
policy introduction.
T}
T{
cosr
T}@T{
\[lq]concurrent open, sequential read\[rq] : Concurrently open branch
directories using a thread pool and process them in order of definition.
This keeps memory and CPU usage low while also reducing the time spent
waiting on branches to respond.
Number of threads defaults to the number of logical cores.
Can be overwritten via the syntax \f[C]func.readdir=cosr:N\f[R] where
\f[C]N\f[R] is the number of threads.
T}
T{
cor
T}@T{
\[lq]concurrent open and read\[rq] : Concurrently open branch
directories and immediately start reading their contents using a thread
pool.
This will result in slightly higher memory and CPU usage but reduced
latency.
Particularly when using higher latency / slower speed network filesystem
branches.
Unlike \f[C]seq\f[R] and \f[C]cosr\f[R] the order of files could change
due the async nature of the thread pool.
Number of threads defaults to the number of logical cores.
Can be overwritten via the syntax \f[C]func.readdir=cor:N\f[R] where
\f[C]N\f[R] is the number of threads.
T}
.TE
.PP
Keep in mind that \f[C]readdir\f[R] mostly just provides a list of file
names in a directory and possibly some basic metadata about said files.
To know details about the files, as one would see from commands like
\f[C]find\f[R] or \f[C]ls\f[R], it is required to call \f[C]stat\f[R] on
the file which is controlled by \f[C]fuse.getattr\f[R].
.SS ioctl
.PP
When \f[C]ioctl\f[R] is used with an open file then it will use the file
@@ -1246,19 +1312,6 @@ The above behavior will help minimize the likelihood of EXDEV being
returned but it will still be possible.
.PP
\f[B]link\f[R] uses the same strategy but without the removals.
.SS readdir
.PP
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 \f[B]ls\f[R] is a \f[B]readdir\f[R] and for each
file/directory returned \f[B]getattr\f[R] is called.
Meaning the policy of \f[B]getattr\f[R] is responsible for choosing the
file/directory which is the source of the metadata you see in an
\f[B]ls\f[R].
.SS statfs / statvfs
.PP
statvfs (http://linux.die.net/man/2/statvfs) normalizes the source
@@ -1273,6 +1326,24 @@ be included when checking the mount\[cq]s stats.
.PP
The options \f[C]statfs\f[R] and \f[C]statfs_ignore\f[R] can be used to
modify \f[C]statfs\f[R] behavior.
.SS flush-on-close
.PP
https://lkml.kernel.org/linux-fsdevel/20211024132607.1636952-1-amir73il\[at]gmail.com/T/
.PP
By default FUSE would issue a flush before the release of a file
descriptor.
This was considered a bit aggressive and a feature added to give the
FUSE server the ability to choose when that happens.
.PP
Options: * always * never * opened-for-write
.PP
For now it defaults to \[lq]opened-for-write\[rq] which is less
aggressive than the behavior before this feature was added.
It should not be a problem because the flush is really only relevant
when a file is written to.
Given flush is irrelevant for many filesystems in the future a branch
specific flag may be added so only files opened on a specific branch
would be flushed on close.
.SH ERROR HANDLING
.PP
POSIX filesystem functions offer a single return code meaning that there
@@ -1651,6 +1722,108 @@ IOCTL_INVALIDATE_ALL_NODES: Same as SIGUSR1.
Send invalidation notifications to the kernel for all files causing
unused files to be released from memory.
.SH TOOLING
.SS preload.so
.PP
EXPERIMENTAL
.PP
For some time there has been work to enable passthrough IO in FUSE.
Passthrough IO would allow for near native performance with regards to
reads and writes (at the expense of certain mergerfs features.) However,
there have been several complications which have kept the feature from
making it into the mainline Linux kernel.
Until that feature is available there are two methods to provide similar
functionality.
One method is using the LD_PRELOAD feature of the dynamic linker.
The other leveraging ptrace to intercept syscalls.
Each has their disadvantages.
At the moment only a preload based tool is available.
A ptrace based tool may be developed later if there is a need.
.PP
\f[C]/usr/lib/mergerfs/preload.so\f[R]
.PP
This preloadable
library (https://man7.org/linux/man-pages/man8/ld.so.8.html#ENVIRONMENT)
overrides the creation and opening of files in order to simulate
passthrough file IO.
It catches the open/creat/fopen calls, has mergerfs do the call, queries
mergerfs for the branch the file exists on, reopens the file on the
underlying filesystem and returns that instead.
Meaning that you will get native read/write performance because mergerfs
is no longer part of the workflow.
Keep in mind that this also means certain mergerfs features that work by
interrupting the read/write workflow, such as \f[C]moveonenospc\f[R],
will no longer work.
.PP
Also understand that this will only work on dynamically linked software.
Anything statically compiled will not work.
Many GoLang and Rust apps are statically compiled.
.PP
The library will not interfere with non-mergerfs filesystems.
The library is written to always fallback to returning the mergerfs
opened file on error.
.PP
While the library was written to account for a number of edgecases there
could be some yet accounted for so please report any oddities.
.PP
Thank you to nohajc (https://github.com/nohajc/mergerfs-io-passthrough)
for prototyping the idea.
.SS general usage
.IP
.nf
\f[C]
LD_PRELOAD=/usr/lib/mergerfs/preload.so touch /mnt/mergerfs/filename
\f[R]
.fi
.SS Docker usage
.PP
Assume \f[C]/mnt/fs0\f[R] and \f[C]/mnt/fs1\f[R] are pooled with
mergerfs at \f[C]/media\f[R].
.PP
All mergerfs branch paths \f[I]must\f[R] be bind mounted into the
container at the same path as found on the host so the preload library
can see them.
.IP
.nf
\f[C]
docker run \[rs]
-e LD_PRELOAD=/usr/lib/mergerfs/preload.so \[rs]
-v /usr/lib/mergerfs/preload.so:/usr/lib/mergerfs/preload.so:ro \[rs]
-v /media:/data \[rs]
-v /mnt:/mnt \[rs]
ubuntu:latest \[rs]
bash
\f[R]
.fi
.PP
or more explicitly
.IP
.nf
\f[C]
docker run \[rs]
-e LD_PRELOAD=/usr/lib/mergerfs/preload.so \[rs]
-v /usr/lib/mergerfs/preload.so:/usr/lib/mergerfs/preload.so:ro \[rs]
-v /media:/data \[rs]
-v /mnt/fs0:/mnt/fs0 \[rs]
-v /mnt/fs1:/mnt/fs1 \[rs]
ubuntu:latest \[rs]
bash
\f[R]
.fi
.SS systemd unit
.PP
Use the \f[C]Environment\f[R] option to set the LD_PRELOAD variable.
.IP \[bu] 2
https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#Command%20lines
.IP \[bu] 2
https://serverfault.com/questions/413397/how-to-set-environment-variable-in-systemd-service
.IP
.nf
\f[C]
[Service]
Environment=LD_PRELOAD=/usr/lib/mergerfs/preload.so
\f[R]
.fi
.SS Misc
.IP \[bu] 2
https://github.com/trapexit/mergerfs-tools
.RS 2
@@ -1707,9 +1880,12 @@ size are unchanged since previous open.
cache.files=libfuse: follow traditional libfuse \f[C]direct_io\f[R],
\f[C]kernel_cache\f[R], and \f[C]auto_cache\f[R] arguments.
.IP \[bu] 2
cache.files=per-process: Enable page caching only for processes which
`comm' name matches one of the values defined in
cache.files=per-process: Enable page caching (equivalent to
\f[C]cache.files=partial\f[R]) only for processes whose `comm' name
matches one of the values defined in
\f[C]cache.files.process-names\f[R].
If the name does not match the file open is equivalent to
\f[C]cache.files=off\f[R].
.PP
FUSE, which mergerfs uses, offers a number of page caching modes.
mergerfs tries to simplify their use via the \f[C]cache.files\f[R]
@@ -1848,7 +2024,7 @@ The placing of usually smaller, faster storage as a transparent cache to
larger, slower storage.
NVMe, SSD, Optane in front of traditional HDDs for instance.
.PP
MergerFS does not natively support any sort of tiered caching.
mergerfs does not natively support any sort of tiered caching.
Most users have no use for such a feature and its inclusion would
complicate the code.
However, there are a few situations where a cache filesystem could help
@@ -2382,17 +2558,50 @@ All of which are fixed in stable releases.
.SS Can mergerfs be used with filesystems which already have data / are in use?
.PP
Yes.
MergerFS is a proxy and does \f[B]NOT\f[R] interfere with the normal
form or function of the filesystems / mounts / paths it manages.
mergerfs is really just a proxy and does \f[B]NOT\f[R] interfere with
the normal form or function of the filesystems / mounts / paths it
manages.
It is just another userland application that is acting as a
man-in-the-middle.
It can\[cq]t do anything that any other random piece of software
can\[cq]t do.
.PP
MergerFS is \f[B]not\f[R] a traditional filesystem.
MergerFS is \f[B]not\f[R] RAID.
mergerfs is \f[B]not\f[R] a traditional filesystem that takes control
over the underlying block device.
mergerfs is \f[B]not\f[R] RAID.
It does \f[B]not\f[R] manipulate the data that passes through it.
It does \f[B]not\f[R] shard data across filesystems.
It merely shards some \f[B]behavior\f[R] and aggregates others.
.SS Can drives/filesystems be removed from the pool at will?
.PP
Yes.
See previous question\[cq]s answer.
.SS Can mergerfs be removed without affecting the data?
.PP
Yes.
See the previous question\[cq]s answer.
.SS Can drives/filesystems be moved to another pool?
.PP
Yes.
See the previous question\[cq]s answer.
.SS How do I migrate data into or out of the pool when adding/removing drives/filesystems?
.PP
You don\[cq]t need to.
See the previous question\[cq]s answer.
.SS How do I remove a drive/filesystem but keep the data in the pool?
.PP
Nothing special needs to be done.
Remove the branch from mergerfs\[cq] config and copy (rsync) the data
from the removed filesystem into the pool.
Effectively the same as if it were you transfering data from one
filesystem to another.
.PP
If you wish to continue using the pool while performing the transfer
simply create another, temporary pool without the filesystem in question
and then copy the data.
It would probably be a good idea to set the branch to \f[C]RO\f[R] prior
to doing this to ensure no new content is written to the filesystem
while performing the copy.
.SS What policies should I use?
.PP
Unless you\[cq]re doing something more niche the average user is
@@ -2466,7 +2675,9 @@ Anything, files or directories, created in that first directory will be
placed on the same branch because it is preserving paths.
.PP
This catches a lot of new users off guard but changing the default would
break the setup for many existing users.
break the setup for many existing users and this policy is the safest
policy as it will not change the general layout of the existing
filesystems.
If you do not care about path preservation and wish your files to be
spread across all your filesystems change to \f[C]mfs\f[R] or similar
policy as described above.
@@ -2496,6 +2707,16 @@ considered different devices.
There is no way to link between them.
You should mount in the highest directory in the mergerfs pool that
includes all the paths you need if you want links to work.
.SS Does FICLONE or FICLONERANGE work?
.PP
Unfortunately not.
FUSE, the technology mergerfs is based on, does not support the
\f[C]clone_file_range\f[R] feature needed for it to work.
mergerfs won\[cq]t even know such a request is made.
The kernel will simply return an error back to the application making
the request.
.PP
Should FUSE gain the ability mergerfs will be updated to support it.
.SS Can I use mergerfs without SnapRAID? SnapRAID without mergerfs?
.PP
Yes.
@@ -2619,93 +2840,6 @@ best provide equivalent performance and in cases worse performance.
Splice is not supported on other platforms forcing a traditional
read/write fallback to be provided.
The splice code was removed to simplify the codebase.
.SS Why use mergerfs over mhddfs?
.PP
mhddfs is no longer maintained and has some known stability and security
issues (see below).
MergerFS provides a superset of mhddfs\[cq] features and should offer
the same or maybe better performance.
.PP
Below is an example of mhddfs and mergerfs setup to work similarly.
.PP
\f[C]mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool\f[R]
.PP
\f[C]mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool\f[R]
.SS Why use mergerfs over aufs?
.PP
aufs is mostly abandoned and no longer available in many distros.
.PP
While aufs can offer better peak performance mergerfs provides more
configurability and is generally easier to use.
mergerfs however does not offer the overlay / copy-on-write (CoW)
features which aufs and overlayfs have.
.SS Why use mergerfs over unionfs?
.PP
UnionFS is more like aufs than mergerfs in that it offers overlay / CoW
features.
If you\[cq]re just looking to create a union of filesystems and want
flexibility in file/directory placement then mergerfs offers that
whereas unionfs is more for overlaying RW filesystems over RO ones.
.SS Why use mergerfs over overlayfs?
.PP
Same reasons as with unionfs.
.SS Why use mergerfs over LVM/ZFS/BTRFS/RAID0 drive concatenation / striping?
.PP
With simple JBOD / drive concatenation / stripping / RAID0 a single
drive failure will result in full pool failure.
mergerfs performs a similar function without the possibility of
catastrophic failure and the difficulties in recovery.
Drives may fail, however, all other data will continue to be accessible.
.PP
When combined with something like SnapRaid (http://www.snapraid.it)
and/or an offsite backup solution you can have the flexibility of JBOD
without the single point of failure.
.SS Why use mergerfs over ZFS?
.PP
MergerFS is not intended to be a replacement for ZFS.
MergerFS is intended to provide flexible pooling of arbitrary
filesystems (local or remote), of arbitrary sizes, and arbitrary
filesystems.
For \f[C]write once, read many\f[R] usecases such as bulk media storage.
Where data integrity and backup is managed in other ways.
In 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).
.SS Why use mergerfs over UnRAID?
.PP
UnRAID is a full OS and its storage layer, as I understand, is
proprietary and closed source.
Users who have experience with both have said they prefer the
flexibility offered by mergerfs and for some the fact it is free and
open source is important.
.PP
There are a number of UnRAID users who use mergerfs as well though
I\[cq]m not entirely familiar with the use case.
.SS Why use mergerfs over StableBit\[cq]s DrivePool?
.PP
DrivePool works only on Windows so not as common an alternative as other
Linux solutions.
If you want to use Windows then DrivePool is a good option.
Functionally the two projects work a bit differently.
DrivePool always writes to the filesystem with the most free space and
later rebalances.
mergerfs does not offer rebalance but chooses a branch at file/directory
create time.
DrivePool\[cq]s rebalancing can be done differently in any directory and
has file pattern matching to further customize the behavior.
mergerfs, not having rebalancing does not have these features, but
similar features are planned for mergerfs v3.
DrivePool has builtin file duplication which mergerfs does not natively
support (but can be done via an external script.)
.PP
There are a lot of misc differences between the two projects but most
features in DrivePool can be replicated with external tools in
combination with mergerfs.
.PP
Additionally DrivePool is a closed source commercial product vs mergerfs
a ISC licensed OSS project.
.SS What should mergerfs NOT be used for?
.IP \[bu] 2
databases: Even if the database stored data in separate files (mergerfs
@@ -2861,6 +2995,108 @@ If the ability to give writers priority is supported then that flag will
be used so threads trying to change credentials don\[cq]t starve.
This isn\[cq]t the best solution but should work reasonably well
assuming there are few users.
.SH mergerfs versus X
.SS mhddfs
.PP
mhddfs had not been maintained for some time and has some known
stability and security issues.
mergerfs provides a superset of mhddfs\[cq] features and should offer
the same or better performance.
.PP
Below is an example of mhddfs and mergerfs setup to work similarly.
.PP
\f[C]mhddfs -o mlimit=4G,allow_other /mnt/drive1,/mnt/drive2 /mnt/pool\f[R]
.PP
\f[C]mergerfs -o minfreespace=4G,category.create=ff /mnt/drive1:/mnt/drive2 /mnt/pool\f[R]
.SS aufs
.PP
aufs is mostly abandoned and no longer available in most Linux distros.
.PP
While aufs can offer better peak performance mergerfs provides more
configurability and is generally easier to use.
mergerfs however does not offer the overlay / copy-on-write (CoW)
features which aufs has.
.SS unionfs-fuse
.PP
unionfs-fuse is more like aufs than mergerfs in that it offers overlay /
copy-on-write (CoW) features.
If you\[cq]re just looking to create a union of filesystems and want
flexibility in file/directory placement then mergerfs offers that
whereas unionfs is more for overlaying read/write filesystems over
read-only ones.
.SS overlayfs
.PP
overlayfs is similar to aufs and unionfs-fuse in that it also is
primarily used to layer a read/write filesystem over one or more
read-only filesystems.
It does not have the ability to spread files/directories across numerous
filesystems.
.SS RAID0, JBOD, drive concatenation, striping
.PP
With simple JBOD / drive concatenation / stripping / RAID0 a single
drive failure will result in full pool failure.
mergerfs performs a similar function without the possibility of
catastrophic failure and the difficulties in recovery.
Drives may fail but all other filesystems and their data will continue
to be accessible.
.PP
The main practical difference with mergerfs is the fact you don\[cq]t
actually have contiguous space as large as if you used those other
technologies.
Meaning you can\[cq]t create a 2TB file on a pool of 2 1TB filesystems.
.PP
When combined with something like SnapRaid (http://www.snapraid.it)
and/or an offsite backup solution you can have the flexibility of JBOD
without the single point of failure.
.SS UnRAID
.PP
UnRAID is a full OS and its storage layer, as I understand, is
proprietary and closed source.
Users who have experience with both have often said they prefer the
flexibility offered by mergerfs and for some the fact it is open source
is important.
.PP
There are a number of UnRAID users who use mergerfs as well though
I\[cq]m not entirely familiar with the use case.
.PP
For semi-static data mergerfs + SnapRaid (http://www.snapraid.it)
provides a similar solution.
.SS ZFS
.PP
mergerfs is very different from ZFS.
mergerfs is intended to provide flexible pooling of arbitrary
filesystems (local or remote), of arbitrary sizes, and arbitrary
filesystems.
For \f[C]write once, read many\f[R] usecases such as bulk media storage.
Where data integrity and backup is managed in other ways.
In those usecases ZFS can introduce a number of costs and limitations as
described
here (http://louwrentius.com/the-hidden-cost-of-using-zfs-for-your-home-nas.html),
here (https://markmcb.com/2020/01/07/five-years-of-btrfs/), and
here (https://utcc.utoronto.ca/~cks/space/blog/solaris/ZFSWhyNoRealReshaping).
.SS StableBit\[cq]s DrivePool
.PP
DrivePool works only on Windows so not as common an alternative as other
Linux solutions.
If you want to use Windows then DrivePool is a good option.
Functionally the two projects work a bit differently.
DrivePool always writes to the filesystem with the most free space and
later rebalances.
mergerfs does not offer rebalance but chooses a branch at file/directory
create time.
DrivePool\[cq]s rebalancing can be done differently in any directory and
has file pattern matching to further customize the behavior.
mergerfs, not having rebalancing does not have these features, but
similar features are planned for mergerfs v3.
DrivePool has builtin file duplication which mergerfs does not natively
support (but can be done via an external script.)
.PP
There are a lot of misc differences between the two projects but most
features in DrivePool can be replicated with external tools in
combination with mergerfs.
.PP
Additionally DrivePool is a closed source commercial product vs mergerfs
a ISC licensed OSS project.
.SH SUPPORT
.PP
Filesystems are complex and difficult to debug.
+1
View File
@@ -35,6 +35,7 @@ make install PREFIX=%{_prefix} DESTDIR=%{buildroot}
/usr/bin/mergerfs
/usr/bin/mergerfs-fusermount
/sbin/mount.mergerfs
/usr/lib/mergerfs/preload.so
%doc %{_mandir}/*
%changelog
+5 -4
View File
@@ -94,8 +94,9 @@ Config::Config()
category(func),
direct_io(false),
dropcacheonclose(false),
fsname(),
flushonclose(FlushOnClose::ENUM::OPENED_FOR_WRITE),
follow_symlinks(FollowSymlinks::ENUM::NEVER),
fsname(),
func(),
fuse_msg_size(FUSE_MAX_MAX_PAGES),
ignorepponrename(false),
@@ -109,10 +110,9 @@ Config::Config()
nfsopenhack(NFSOpenHack::ENUM::OFF),
nullrw(false),
parallel_direct_writes(false),
pid(::getpid()),
posix_acl(false),
readahead(0),
readdir(ReadDir::ENUM::POSIX),
readdir("seq"),
readdirplus(false),
rename_exdev(RenameEXDEV::ENUM::PASSTHROUGH),
scheduling_priority(-10),
@@ -149,6 +149,7 @@ Config::Config()
_map["category.search"] = &category.search;
_map["direct_io"] = &direct_io;
_map["dropcacheonclose"] = &dropcacheonclose;
_map["flush-on-close"] = &flushonclose;
_map["follow-symlinks"] = &follow_symlinks;
_map["fsname"] = &fsname;
_map["func.access"] = &func.access;
@@ -162,6 +163,7 @@ Config::Config()
_map["func.mkdir"] = &func.mkdir;
_map["func.mknod"] = &func.mknod;
_map["func.open"] = &func.open;
_map["func.readdir"] = &readdir;
_map["func.readlink"] = &func.readlink;
_map["func.removexattr"] = &func.removexattr;
_map["func.rename"] = &func.rename;
@@ -189,7 +191,6 @@ Config::Config()
_map["pin-threads"] = &fuse_pin_threads;
_map["posix_acl"] = &posix_acl;
_map["readahead"] = &readahead;
// _map["readdir"] = &readdir;
_map["readdirplus"] = &readdirplus;
_map["rename-exdev"] = &rename_exdev;
_map["scheduling-priority"] = &scheduling_priority;
+8 -5
View File
@@ -19,21 +19,23 @@
#include "branches.hpp"
#include "category.hpp"
#include "config_cachefiles.hpp"
#include "config_flushonclose.hpp"
#include "config_follow_symlinks.hpp"
#include "config_pid.hpp"
#include "config_inodecalc.hpp"
#include "config_link_exdev.hpp"
#include "config_log_metrics.hpp"
#include "config_moveonenospc.hpp"
#include "config_nfsopenhack.hpp"
#include "config_readdir.hpp"
#include "config_rename_exdev.hpp"
#include "config_set.hpp"
#include "config_statfs.hpp"
#include "config_statfsignore.hpp"
#include "config_xattr.hpp"
#include "config_set.hpp"
#include "enum.hpp"
#include "errno.hpp"
#include "funcs.hpp"
#include "fuse_readdir.hpp"
#include "policy.hpp"
#include "rwlock.hpp"
#include "tofrom_wrapper.hpp"
@@ -116,8 +118,9 @@ public:
Categories category;
ConfigBOOL direct_io;
ConfigBOOL dropcacheonclose;
ConfigSTR fsname;
FlushOnClose flushonclose;
FollowSymlinks follow_symlinks;
ConfigSTR fsname;
Funcs func;
ConfigUINT64 fuse_msg_size;
ConfigBOOL ignorepponrename;
@@ -132,10 +135,10 @@ public:
NFSOpenHack nfsopenhack;
ConfigBOOL nullrw;
ConfigBOOL parallel_direct_writes;
ConfigUINT64 pid;
ConfigGetPid pid;
ConfigBOOL posix_acl;
ConfigUINT64 readahead;
ReadDir readdir;
FUSE::ReadDir readdir;
ConfigBOOL readdirplus;
RenameEXDEV rename_exdev;
ConfigINT scheduling_priority;
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -16,36 +16,39 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config_readdir.hpp"
#include "config_flushonclose.hpp"
#include "ef.hpp"
#include "errno.hpp"
template<>
std::string
FlushOnClose::to_string() const
{
switch(_data)
{
case FlushOnClose::ENUM::NEVER:
return "never";
case FlushOnClose::ENUM::OPENED_FOR_WRITE:
return "opened-for-write";
case FlushOnClose::ENUM::ALWAYS:
return "always";
}
return {};
}
template<>
int
ReadDir::from_string(const std::string &s_)
FlushOnClose::from_string(const std::string &s_)
{
if(s_ == "posix")
_data = ReadDir::ENUM::POSIX;
ef(s_ == "linux")
_data = ReadDir::ENUM::LINUX;
if(s_ == "never")
_data = FlushOnClose::ENUM::NEVER;
ef(s_ == "opened-for-write")
_data = FlushOnClose::ENUM::OPENED_FOR_WRITE;
ef(s_ == "always")
_data = FlushOnClose::ENUM::ALWAYS;
else
return -EINVAL;
return 0;
}
template<>
std::string
ReadDir::to_string(void) const
{
switch(_data)
{
case ReadDir::ENUM::POSIX:
return "posix";
case ReadDir::ENUM::LINUX:
return "linux";
}
return "invalid";
}
@@ -18,13 +18,14 @@
#pragma once
#include <syslog.h>
#include "enum.hpp"
void syslog_open();
void syslog_log(const int priority, const char *format, ...);
void syslog_info(const char *format, ...);
void syslog_notice(const char *format, ...);
void syslog_warning(const char *format, ...);
void syslog_error(const char *format, ...);
void syslog_close();
enum class FlushOnCloseEnum
{
NEVER,
OPENED_FOR_WRITE,
ALWAYS
};
typedef Enum<FlushOnCloseEnum> FlushOnClose;
+42
View File
@@ -0,0 +1,42 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "tofrom_string.hpp"
#include "fmt/core.h"
#include <sys/types.h>
#include <unistd.h>
class ConfigGetPid : public ToFromString
{
public:
std::string
to_string() const final
{
return fmt::format("{}",::getpid());
}
int
from_string(const std::string &) final
{
return -EROFS;
}
};
+477 -279
View File
File diff suppressed because it is too large Load Diff
+14 -32
View File
@@ -11,7 +11,7 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
@@ -423,26 +423,6 @@ FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
return ansi_color_escape<Char>(em);
}
template <typename Char> inline void fputs(const Char* chars, FILE* stream) {
int result = std::fputs(chars, stream);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
template <> inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) {
int result = std::fputws(chars, stream);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
template <typename Char> inline void reset_color(FILE* stream) {
fputs("\x1b[0m", stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) {
fputs(L"\x1b[0m", stream);
}
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end());
@@ -479,17 +459,19 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
FMT_END_DETAIL_NAMESPACE
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, detail::to_string_view(format), args);
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
format_args args) {
// Legacy wide streams are not supported.
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
if (detail::is_utf8()) {
detail::print(f, basic_string_view<Char>(buf.begin(), buf.size()));
} else {
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
detail::print(f, string_view(buf.begin(), buf.size()));
return;
}
buf.push_back('\0');
int result = std::fputs(buf.data(), f);
if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
/**
@@ -566,7 +548,7 @@ OutputIt vformat_to(
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
/**
@@ -645,7 +627,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts)
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_
+8 -12
View File
@@ -331,14 +331,14 @@ template <typename T, typename Char> struct parse_specs_result {
int next_arg_id;
};
constexpr int manual_indexing_id = -1;
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
str.remove_prefix(pos);
auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {},
next_arg_id);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
@@ -348,22 +348,18 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
template <typename Char> struct arg_id_handler {
arg_ref<Char> arg_id;
constexpr int operator()() {
constexpr int on_auto() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int operator()(int id) {
constexpr int on_index(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int operator()(basic_string_view<Char> id) {
constexpr int on_name(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr void on_error(const char* message) {
FMT_THROW(format_error(message));
}
};
template <typename Char> struct parse_arg_id_result {
@@ -501,7 +497,7 @@ constexpr auto compile(S format_str) {
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
@@ -605,7 +601,7 @@ template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
} // namespace literals
#endif
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_
+535 -907
View File
File diff suppressed because it is too large Load Diff
+89 -131
View File
@@ -9,13 +9,9 @@
#define FMT_FORMAT_INL_H_
#include <algorithm>
#include <cctype>
#include <cerrno> // errno
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstring> // std::memmove
#include <cwchar>
#include <exception>
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
@@ -115,16 +111,43 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#endif
FMT_FUNC auto write_loc(appender out, loc_value value,
const format_specs<>& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto locale = loc.get<std::locale>();
// We cannot use the num_put<char> facet because it may produce output in
// a wrong encoding.
using facet = format_facet<std::locale>;
if (std::has_facet<facet>(locale))
return std::use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs);
#endif
return false;
}
} // namespace detail
#if !FMT_MSC_VERSION
FMT_API FMT_FUNC format_error::~format_error() noexcept = default;
template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
grouping_ = numpunct.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
}
template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, loc_value val, const format_specs<>& specs) const -> bool {
return val.visit(
detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_});
}
#endif
FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str,
FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
format_args args) {
auto ec = std::error_code(error_code, std::generic_category());
return std::system_error(ec, vformat(format_str, args));
return std::system_error(ec, vformat(fmt, args));
}
namespace detail {
@@ -143,58 +166,8 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
return (n >> r) | (n << (64 - r));
}
// Computes 128-bit result of multiplication of two 64-bit unsigned integers.
inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept {
#if FMT_USE_INT128
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};
#elif defined(_MSC_VER) && defined(_M_X64)
auto result = uint128_fallback();
result.lo_ = _umul128(x, y, &result.hi_);
return result;
#else
const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>());
uint64_t a = x >> 32;
uint64_t b = x & mask;
uint64_t c = y >> 32;
uint64_t d = y & mask;
uint64_t ac = a * c;
uint64_t bc = b * c;
uint64_t ad = a * d;
uint64_t bd = b * d;
uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);
return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),
(intermediate << 32) + (bd & mask)};
#endif
}
// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.
namespace dragonbox {
// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.
inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept {
#if FMT_USE_INT128
auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);
return static_cast<uint64_t>(p >> 64);
#elif defined(_MSC_VER) && defined(_M_X64)
return __umulh(x, y);
#else
return umul128(x, y).high();
#endif
}
// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a
// 128-bit unsigned integer.
inline uint128_fallback umul192_upper128(uint64_t x,
uint128_fallback y) noexcept {
uint128_fallback r = umul128(x, y.high());
r += umul128_upper64(x, y.low());
return r;
}
// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a
// 64-bit unsigned integer.
inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept {
@@ -216,25 +189,13 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
return x * y;
}
// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from
// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.
inline int floor_log10_pow2(int e) noexcept {
FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent");
static_assert((-1 >> 1) == -1, "right shift is not arithmetic");
return (e * 315653) >> 20;
}
// Various fast log computations.
inline int floor_log2_pow10(int e) noexcept {
FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent");
return (e * 1741647) >> 19;
}
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
return (e * 631305 - 261663) >> 21;
}
static constexpr struct {
FMT_INLINE_VARIABLE constexpr struct {
uint32_t divisor;
int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@@ -288,7 +249,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
}
// Various subroutines using pow10 cache
template <class T> struct cache_accessor;
template <typename T> struct cache_accessor;
template <> struct cache_accessor<float> {
using carrier_uint = float_info<float>::carrier_uint;
@@ -1009,8 +970,23 @@ template <> struct cache_accessor<double> {
{0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},
{0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},
{0xc5a05277621be293, 0xc7098b7305241886},
{ 0xf70867153aa2db38,
0xb8cbee4fc66d1ea8 }
{0xf70867153aa2db38, 0xb8cbee4fc66d1ea8},
{0x9a65406d44a5c903, 0x737f74f1dc043329},
{0xc0fe908895cf3b44, 0x505f522e53053ff3},
{0xf13e34aabb430a15, 0x647726b9e7c68ff0},
{0x96c6e0eab509e64d, 0x5eca783430dc19f6},
{0xbc789925624c5fe0, 0xb67d16413d132073},
{0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890},
{0x933e37a534cbaae7, 0x8e91b962f7b6f15a},
{0xb80dc58e81fe95a1, 0x723627bbb5a4adb1},
{0xe61136f2227e3b09, 0xcec3b1aaa30dd91d},
{0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2},
{0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e},
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
{ 0xdb68c2ca82ed2a05,
0xa67398db9f6820e2 }
#else
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
@@ -1034,8 +1010,8 @@ template <> struct cache_accessor<double> {
{0x8da471a9de737e24, 0x5ceaecfed289e5d3},
{0xe4d5e82392a40515, 0x0fabaf3feaa5334b},
{0xb8da1662e7b00a17, 0x3d6a751f3b936244},
{ 0x95527a5202df0ccb,
0x0f37801e0c43ebc9 }
{0x95527a5202df0ccb, 0x0f37801e0c43ebc9},
{0xf13e34aabb430a15, 0x647726b9e7c68ff0}
#endif
};
@@ -1138,8 +1114,12 @@ template <> struct cache_accessor<double> {
}
};
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
return cache_accessor<double>::get_cached_power(k);
}
// Various integer checks
template <class T>
template <typename T>
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
@@ -1150,8 +1130,12 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
// Remove trailing zeros from n and return the number of zeros removed (float)
FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
// See https://github.com/fmtlib/fmt/issues/3163 for more details.
const uint32_t mod_inv_5 = 0xcccccccd;
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
// Casts are needed to workaround a bug in MSVC 19.22 and older.
const uint32_t mod_inv_25 =
static_cast<uint32_t>(uint64_t(mod_inv_5) * mod_inv_5);
int s = 0;
while (true) {
@@ -1165,7 +1149,6 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept {
n = q;
s |= 1;
}
return s;
}
@@ -1223,7 +1206,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
}
// The main algorithm for shorter interval case
template <class T>
template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
decimal_fp<T> ret_value;
// Compute k and beta
@@ -1394,17 +1377,6 @@ small_divisor_case_label:
return ret_value;
}
} // namespace dragonbox
#ifdef _MSC_VER
FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...)
-> int {
auto args = va_list();
va_start(args, fmt);
int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args);
va_end(args);
return result;
}
#endif
} // namespace detail
template <> struct formatter<detail::bigint> {
@@ -1413,9 +1385,8 @@ template <> struct formatter<detail::bigint> {
return ctx.begin();
}
template <typename FormatContext>
auto format(const detail::bigint& n, FormatContext& ctx) const ->
typename FormatContext::iterator {
auto format(const detail::bigint& n, format_context& ctx) const
-> format_context::iterator {
auto out = ctx.out();
bool first = true;
for (auto i = n.bigits_.size(); i > 0; --i) {
@@ -1474,57 +1445,44 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
}
namespace detail {
#ifdef _WIN32
#ifndef _WIN32
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
#else
using dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;
extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( //
void*, const void*, dword, dword*, void*);
FMT_FUNC bool write_console(std::FILE* f, string_view text) {
auto fd = _fileno(f);
if (_isatty(fd)) {
detail::utf8_to_utf16 u16(string_view(text.data(), text.size()));
auto written = detail::dword();
if (detail::WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)),
u16.c_str(), static_cast<uint32_t>(u16.size()),
&written, nullptr)) {
return true;
}
}
// We return false if the file descriptor was not TTY, or it was but
// SetConsoleW failed which can happen if the output has been redirected to
// NUL. In both cases when we return false, we should attempt to do regular
// write via fwrite or std::ostream::write.
return false;
}
#endif
FMT_FUNC void print(std::FILE* f, string_view text) {
#ifdef _WIN32
if (write_console(f, text)) return;
#endif
detail::fwrite_fully(text.data(), 1, text.size(), f);
}
} // namespace detail
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
memory_buffer buffer;
detail::vformat_to(buffer, format_str, args);
detail::print(f, {buffer.data(), buffer.size()});
if (!_isatty(fd)) return false;
auto u16 = utf8_to_utf16(text);
auto written = dword();
return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),
static_cast<uint32_t>(u16.size()), &written, nullptr);
}
#ifdef _WIN32
// Print assuming legacy (non-Unicode) encoding.
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
format_args args) {
memory_buffer buffer;
detail::vformat_to(buffer, format_str,
FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt,
basic_format_args<buffer_context<char>>(args));
fwrite_fully(buffer.data(), 1, buffer.size(), f);
}
#endif
FMT_FUNC void vprint(string_view format_str, format_args args) {
vprint(stdout, format_str, args);
FMT_FUNC void print(std::FILE* f, string_view text) {
if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
}
} // namespace detail
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
detail::print(f, {buffer.data(), buffer.size()});
}
FMT_FUNC void vprint(string_view fmt, format_args args) {
vprint(stdout, fmt, args);
}
namespace detail {
+948 -430
View File
File diff suppressed because it is too large Load Diff
+40 -67
View File
@@ -71,7 +71,7 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
/**
\rst
@@ -120,48 +120,10 @@ template <typename Char> class basic_cstring_view {
using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>;
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(),
basic_format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#ifdef _WIN32
FMT_API const std::error_category& system_category() noexcept;
FMT_BEGIN_DETAIL_NAMESPACE
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
private:
memory_buffer buffer_;
public:
utf16_to_utf8() {}
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
operator string_view() const { return string_view(&buffer_[0], size()); }
size_t size() const { return buffer_.size() - 1; }
const char* c_str() const { return &buffer_[0]; }
std::string str() const { return std::string(&buffer_[0], size()); }
// Performs conversion returning a system error code instead of
// throwing exception on conversion error. This method may still throw
// in case of memory allocation error.
FMT_API int convert(basic_string_view<wchar_t> s);
};
FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
FMT_END_DETAIL_NAMESPACE
@@ -355,6 +317,12 @@ class FMT_API file {
// Creates a buffered_file object associated with this file and detaches
// this file object from the file.
buffered_file fdopen(const char* mode);
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
};
// Returns the memory page size.
@@ -397,6 +365,28 @@ struct ostream_params {
# endif
};
class file_buffer final : public buffer<char> {
file file_;
FMT_API void grow(size_t) override;
public:
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other);
FMT_API ~file_buffer();
void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
FMT_END_DETAIL_NAMESPACE
// Added {} below to work around default constructor error known to
@@ -404,49 +394,32 @@ FMT_END_DETAIL_NAMESPACE
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> {
class FMT_API ostream {
private:
file file_;
void grow(size_t) override;
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
: buffer_(path, params) {}
public:
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() {
flush();
file_.close();
}
void close() { buffer_.close(); }
/**
Formats ``args`` according to specifications in ``fmt`` and writes the
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(*this), fmt,
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
fmt::make_format_args(args...));
}
};
@@ -472,7 +445,7 @@ inline ostream output_file(cstring_view path, T... params) {
}
#endif // FMT_USE_FCNTL
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_OS_H_
+19 -47
View File
@@ -8,8 +8,8 @@
#ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_
#include <fstream>
#include <ostream>
#include <fstream> // std::filebuf
#if defined(_WIN32) && defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
@@ -21,48 +21,14 @@
FMT_BEGIN_NAMESPACE
template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail {
// Checks if T has a user-defined operator<<.
template <typename T, typename Char, typename Enable = void>
class is_streamable {
private:
template <typename U>
static auto test(int)
-> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
<< std::declval<U>()) != 0>;
template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0));
public:
is_streamable() = default;
static const bool value = result::value;
};
// Formatting of built-in types and arrays is intentionally disabled because
// it's handled by standard (non-ostream) formatters.
template <typename T, typename Char>
struct is_streamable<
T, Char,
enable_if_t<
std::is_arithmetic<T>::value || std::is_array<T>::value ||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
std::is_convertible<T, fmt::basic_string_view<Char>>::value ||
std::is_same<T, std_string_view<Char>>::value ||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
: std::false_type {};
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
template <class Tag, class BufType, FILE* BufType::*FileMemberPtr>
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
@@ -84,8 +50,8 @@ inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
#elif defined(_WIN32) && defined(__GLIBCXX__)
auto* rdbuf = os.rdbuf();
FILE* c_file;
if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
c_file = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
c_file = fbuf->file();
else
@@ -145,7 +111,7 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const
-> OutputIt {
auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale());
detail::format_value(buffer, value, ctx.locale());
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
@@ -180,13 +146,6 @@ auto streamed(const T& value) -> detail::streamed_view<T> {
namespace detail {
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: basic_ostream_formatter<Char> {
using basic_ostream_formatter<Char>::format;
};
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer();
@@ -232,6 +191,19 @@ void print(std::wostream& os,
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
}
FMT_MODULE_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_MODULE_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
}
FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_
+93 -54
View File
@@ -14,7 +14,7 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter { printf_formatter() = delete; };
@@ -81,13 +81,13 @@ class printf_precision_handler {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
int operator()(T value) {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
FMT_THROW(format_error("number is too big"));
throw_format_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
int operator()(T) {
FMT_THROW(format_error("precision is not integer"));
throw_format_error("precision is not integer");
return 0;
}
};
@@ -194,12 +194,10 @@ template <typename Char> struct get_cstring {
// left alignment if it is negative.
template <typename Char> class printf_width_handler {
private:
using format_specs = basic_format_specs<Char>;
format_specs& specs_;
format_specs<Char>& specs_;
public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
explicit printf_width_handler(format_specs<Char>& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) {
@@ -209,24 +207,31 @@ template <typename Char> class printf_width_handler {
width = 0 - width;
}
unsigned int_max = max_value<int>();
if (width > int_max) FMT_THROW(format_error("number is too big"));
if (width > int_max) throw_format_error("number is too big");
return static_cast<unsigned>(width);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
unsigned operator()(T) {
FMT_THROW(format_error("width is not integer"));
throw_format_error("width is not integer");
return 0;
}
};
// Workaround for a bug with the XL compiler when initializing
// printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The ``printf`` argument formatter.
template <typename OutputIt, typename Char>
class printf_arg_formatter : public arg_formatter<Char> {
private:
using base = arg_formatter<Char>;
using context_type = basic_printf_context<OutputIt, Char>;
using format_specs = basic_format_specs<Char>;
context_type& context_;
@@ -237,8 +242,8 @@ class printf_arg_formatter : public arg_formatter<Char> {
}
public:
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
: base{iter, s, locale_ref()}, context_(ctx) {}
printf_arg_formatter(OutputIt iter, format_specs<Char>& s, context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); }
@@ -247,7 +252,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs;
format_specs<Char> fmt_specs = this->specs;
if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
@@ -300,8 +305,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
};
template <typename Char>
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
const Char* end) {
void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
@@ -328,8 +332,8 @@ void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
}
template <typename Char, typename GetArg>
int parse_header(const Char*& it, const Char* end,
basic_format_specs<Char>& specs, GetArg get_arg) {
int parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
GetArg get_arg) {
int arg_index = -1;
Char c = *it;
if (c >= '0' && c <= '9') {
@@ -344,7 +348,7 @@ int parse_header(const Char*& it, const Char* end,
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
if (value == -1) FMT_THROW(format_error("number is too big"));
if (value == -1) throw_format_error("number is too big");
specs.width = value;
return arg_index;
}
@@ -355,7 +359,7 @@ int parse_header(const Char*& it, const Char* end,
if (it != end) {
if (*it >= '0' && *it <= '9') {
specs.width = parse_nonnegative_int(it, end, -1);
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
if (specs.width == -1) throw_format_error("number is too big");
} else if (*it == '*') {
++it;
specs.width = static_cast<int>(visit_format_arg(
@@ -365,12 +369,52 @@ int parse_header(const Char*& it, const Char* end,
return arg_index;
}
inline auto parse_printf_presentation_type(char c, type t)
-> presentation_type {
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'x':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none;
case 'a':
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
case 'A':
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none;
case 'f':
return in(t, float_set) ? pt::fixed_lower : pt::none;
case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none;
case 'g':
return in(t, float_set) ? pt::general_lower : pt::none;
case 'G':
return in(t, float_set) ? pt::general_upper : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
}
}
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using OutputIt = buffer_appender<Char>;
auto out = OutputIt(buf);
auto context = basic_printf_context<OutputIt, Char>(out, args);
using iterator = buffer_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<iterator, Char>(out, args);
auto parse_ctx = basic_printf_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
@@ -387,26 +431,25 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
const Char* end = parse_ctx.end();
auto it = start;
while (it != end) {
if (!detail::find<false, Char>(it, end, '%', it)) {
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
if (!find<false, Char>(it, end, '%', it)) {
it = end; // find leaves it == nullptr if it doesn't find '%'.
break;
}
Char c = *it++;
if (it != end && *it == c) {
out = detail::write(
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
out = write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
start = ++it;
continue;
}
out = detail::write(out, basic_string_view<Char>(
start, detail::to_unsigned(it - 1 - start)));
out =
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
basic_format_specs<Char> specs;
auto specs = format_specs<Char>();
specs.align = align::right;
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) parse_ctx.on_error("argument not found");
if (arg_index == 0) throw_format_error("argument not found");
// Parse precision.
if (it != end && *it == '.') {
@@ -417,7 +460,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
} else if (c == '*') {
++it;
specs.precision = static_cast<int>(
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
visit_format_arg(printf_precision_handler(), get_arg(-1)));
} else {
specs.precision = 0;
}
@@ -429,17 +472,15 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
if (specs.precision >= 0 && arg.is_integral())
specs.fill[0] =
' '; // Ignore '0' flag for non-numeric types or if '-' present.
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = visit_format_arg(get_cstring<Char>(), arg);
auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
arg = make_arg<basic_printf_context<iterator, Char>>(
basic_string_view<Char>(
str, detail::to_unsigned(nul != str_end ? nul - str
: specs.precision)));
str, to_unsigned(nul != str_end ? nul - str : specs.precision)));
}
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
specs.alt = false;
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
if (specs.fill[0] == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
@@ -451,7 +492,6 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
// Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0;
Char t = it != end ? *it : 0;
using detail::convert_arg;
switch (c) {
case 'h':
if (t == 'h') {
@@ -490,7 +530,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
}
// Parse type.
if (it == end) FMT_THROW(format_error("invalid format string"));
if (it == end) throw_format_error("invalid format string");
char type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
@@ -501,22 +541,21 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
break;
case 'c':
visit_format_arg(
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
arg);
char_converter<basic_printf_context<iterator, Char>>(arg), arg);
break;
}
}
specs.type = parse_presentation_type(type);
specs.type = parse_printf_presentation_type(type, arg.type());
if (specs.type == presentation_type::none)
parse_ctx.on_error("invalid type specifier");
throw_format_error("invalid format specifier");
start = it;
// Format argument.
out = visit_format_arg(
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
printf_arg_formatter<iterator, Char>(out, specs, context), arg);
}
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
}
FMT_END_DETAIL_NAMESPACE
@@ -559,9 +598,9 @@ inline auto vsprintf(
const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buffer;
vprintf(buffer, detail::to_string_view(fmt), args);
return to_string(buffer);
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, detail::to_string_view(fmt), args);
return to_string(buf);
}
/**
@@ -586,10 +625,10 @@ inline auto vfprintf(
std::FILE* f, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer;
vprintf(buffer, detail::to_string_view(fmt), args);
size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, detail::to_string_view(fmt), args);
size_t size = buf.size();
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
? -1
: static_cast<int>(size);
}
@@ -634,7 +673,7 @@ inline auto printf(const S& fmt, const T&... args) -> int {
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_
+153 -143
View File
@@ -22,27 +22,25 @@ FMT_BEGIN_NAMESPACE
namespace detail {
template <typename RangeT, typename OutputIterator>
OutputIterator copy(const RangeT& range, OutputIterator out) {
template <typename Range, typename OutputIt>
auto copy(const Range& range, OutputIt out) -> OutputIt {
for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it;
return out;
}
template <typename OutputIterator>
OutputIterator copy(const char* str, OutputIterator out) {
template <typename OutputIt>
auto copy(const char* str, OutputIt out) -> OutputIt {
while (*str) *out++ = *str++;
return out;
}
template <typename OutputIterator>
OutputIterator copy(char ch, OutputIterator out) {
template <typename OutputIt> auto copy(char ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
template <typename OutputIterator>
OutputIterator copy(wchar_t ch, OutputIterator out) {
template <typename OutputIt> auto copy(wchar_t ch, OutputIt out) -> OutputIt {
*out++ = ch;
return out;
}
@@ -69,7 +67,7 @@ template <typename T> class is_map {
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_MAP_AS_LIST
#ifdef FMT_FORMAT_MAP_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
@@ -82,7 +80,7 @@ template <typename T> class is_set {
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_SET_AS_LIST
#ifdef FMT_FORMAT_SET_AS_LIST // DEPRECATED!
static constexpr const bool value = false;
#else
static constexpr const bool value =
@@ -157,8 +155,9 @@ template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_end(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {};
// the extra int here is because older versions of MSVC don't
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T>
struct is_range_<T, void>
@@ -211,41 +210,61 @@ class is_tuple_formattable_ {
static constexpr const bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <std::size_t... I>
static std::true_type check2(index_sequence<I...>,
integer_sequence<bool, (I == I)...>);
template <std::size_t... Is>
static std::true_type check2(index_sequence<Is...>,
integer_sequence<bool, (Is == Is)...>);
static std::false_type check2(...);
template <std::size_t... I>
template <std::size_t... Is>
static decltype(check2(
index_sequence<I...>{},
index_sequence<Is...>{},
integer_sequence<
bool, (is_formattable<typename std::tuple_element<I, T>::type,
C>::value)...>{})) check(index_sequence<I...>);
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
C>::value)...>{})) check(index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
template <class Tuple, class F, size_t... Is>
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept {
template <typename Tuple, typename F, size_t... Is>
FMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {
using std::get;
// using free function get<I>(T) now.
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
(void)_; // blocks warnings
// Using a free function get<Is>(Tuple) now.
const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};
ignore_unused(unused);
}
template <class T>
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
T const&) {
return {};
template <typename Tuple, typename F>
FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {
for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),
std::forward<Tuple>(t), std::forward<F>(f));
}
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
const auto indexes = get_indexes(tup);
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
template <typename Tuple1, typename Tuple2, typename F, size_t... Is>
void for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {
using std::get;
const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};
ignore_unused(unused);
}
template <typename Tuple1, typename Tuple2, typename F>
void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {
for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),
std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f));
}
namespace tuple {
// Workaround a bug in MSVC 2019 (v140).
template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920
// Older MSVC doesn't get the reference type correctly for arrays.
template <typename R> struct range_reference_type_impl {
@@ -269,45 +288,37 @@ using range_reference_type =
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Range>
using uncvref_first_type =
remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>;
template <typename Range>
using uncvref_second_type = remove_cvref_t<
decltype(std::declval<range_reference_type<Range>>().second)>;
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
*out++ = ',';
*out++ = ' ';
return out;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename Char, typename OutputIt>
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
return write_escaped_string(out, str);
}
// These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
auto sv = std_string_view<Char>(str);
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
}
template <typename T>
void operator()(const formatter<T, char_type>& f, const T& v) {
if (i > 0)
ctx.advance_to(detail::copy_str<char_type>(separator, ctx.out()));
ctx.advance_to(f.format(v, ctx));
++i;
}
template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) {
return write_escaped_char(out, v);
}
template <
typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
!std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg& v) {
return write<Char>(out, v);
}
int i;
FormatContext& ctx;
basic_string_view<char_type> separator;
};
} // namespace detail
@@ -321,29 +332,20 @@ template <typename T, typename C> struct is_tuple_formattable {
detail::is_tuple_formattable_<T, C>::value;
};
template <typename TupleT, typename Char>
struct formatter<TupleT, Char,
enable_if_t<fmt::is_tuple_like<TupleT>::value &&
fmt::is_tuple_formattable<TupleT, Char>::value>> {
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
fmt::is_tuple_formattable<Tuple, Char>::value>> {
private:
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{};
// C++11 generic lambda for format().
template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::copy_str<Char>(separator, out);
out = detail::write_range_entry<Char>(out, v);
++i;
}
int i;
typename FormatContext::iterator& out;
basic_string_view<Char> separator;
};
public:
FMT_CONSTEXPR formatter() {}
@@ -359,17 +361,21 @@ struct formatter<TupleT, Char,
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
auto it = ctx.begin();
if (it != ctx.end() && *it != '}')
FMT_THROW(format_error("invalid format specifier"));
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
return it;
}
template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) const
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::copy_str<Char>(opening_bracket_, out);
detail::for_each(values, format_each<FormatContext>{0, out, separator_});
out = detail::copy_str<Char>(closing_bracket_, out);
return out;
ctx.advance_to(detail::copy_str<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
formatters_, value,
detail::format_tuple_element<FormatContext>{0, ctx, separator_});
return detail::copy_str<Char>(closing_bracket_, ctx.out());
}
};
@@ -398,12 +404,10 @@ template <typename Context> struct range_mapper {
};
template <typename Char, typename Element>
using range_formatter_type = conditional_t<
is_formattable<Element, Char>::value,
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(
std::declval<Element>()))>,
Char>,
fallback_formatter<Element, Char>>;
Char>;
template <typename R>
using maybe_const_range =
@@ -413,11 +417,8 @@ using maybe_const_range =
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: disjunction<
is_formattable<uncvref_type<maybe_const_range<R>>, Char>,
has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> {};
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename T, typename Char, typename Enable = void>
@@ -426,32 +427,16 @@ struct range_formatter;
template <typename T, typename Char>
struct range_formatter<
T, Char,
enable_if_t<conjunction<
std::is_same<T, remove_cvref_t<T>>,
disjunction<is_formattable<T, Char>,
detail::has_fallback_formatter<T, Char>>>::value>> {
enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,
is_formattable<T, Char>>::value>> {
private:
detail::range_formatter_type<Char, T> underlying_;
bool custom_specs_ = false;
basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};
basic_string_view<Char> opening_bracket_ =
detail::string_literal<Char, '['>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, int)
-> decltype(u.set_debug_format()) {
u.set_debug_format();
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
FMT_CONSTEXPR void maybe_set_debug_format() {
maybe_set_debug_format(underlying_, 0);
}
public:
FMT_CONSTEXPR range_formatter() {}
@@ -473,31 +458,24 @@ struct range_formatter<
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') {
maybe_set_debug_format();
return it;
}
if (*it == 'n') {
if (it != end && *it == 'n') {
set_brackets({}, {});
++it;
}
if (*it == '}') {
maybe_set_debug_format();
return it;
if (it != end && *it != '}') {
if (*it != ':') FMT_THROW(format_error("invalid format specifier"));
++it;
} else {
detail::maybe_set_debug_format(underlying_, true);
}
if (*it != ':')
FMT_THROW(format_error("no other top-level range formatters supported"));
custom_specs_ = true;
++it;
ctx.advance_to(it);
return underlying_.parse(ctx);
}
template <typename R, class FormatContext>
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
detail::range_mapper<buffer_context<Char>> mapper;
auto out = ctx.out();
@@ -507,7 +485,6 @@ struct range_formatter<
auto end = detail::range_end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::copy_str<Char>(separator_, out);
;
ctx.advance_to(out);
out = underlying_.format(mapper.map(*it), ctx);
++i;
@@ -520,13 +497,14 @@ struct range_formatter<
enum class range_format { disabled, map, set, sequence, string, debug_string };
namespace detail {
template <typename T> struct range_format_kind_ {
static constexpr auto value = std::is_same<range_reference_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence;
};
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
std::is_same<uncvref_type<T>, T>::value
? range_format::disabled
: is_map<T>::value ? range_format::map
: is_set<T>::value ? range_format::set
: range_format::sequence> {};
template <range_format K, typename R, typename Char, typename Enable = void>
struct range_default_formatter;
@@ -601,9 +579,6 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
: tuple(t), sep{s} {}
};
template <typename Char, typename... T>
using tuple_arg_join = tuple_join_view<Char, T...>;
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
@@ -673,7 +648,42 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
}
};
FMT_MODULE_EXPORT_BEGIN
namespace detail {
// Check if T has an interface like a container adaptor (e.g. std::stack,
// std::queue, std::priority_queue).
template <typename T> class is_container_adaptor_like {
template <typename U> static auto check(U* p) -> typename U::container_type;
template <typename> static void check(...);
public:
static constexpr const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Container> struct all {
const Container& c;
auto begin() const -> typename Container::const_iterator { return c.begin(); }
auto end() const -> typename Container::const_iterator { return c.end(); }
};
} // namespace detail
template <typename T, typename Char>
struct formatter<T, Char,
enable_if_t<detail::is_container_adaptor_like<T>::value>>
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
}
};
FMT_BEGIN_EXPORT
/**
\rst
@@ -716,7 +726,7 @@ auto join(std::initializer_list<T> list, string_view sep)
return join(std::begin(list), std::end(list), sep);
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_RANGES_H_
+200 -22
View File
@@ -8,8 +8,12 @@
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include <cstdlib>
#include <exception>
#include <memory>
#include <thread>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include "ostream.h"
@@ -25,6 +29,19 @@
# if FMT_HAS_INCLUDE(<variant>)
# include <variant>
# endif
# if FMT_HAS_INCLUDE(<optional>)
# include <optional>
# endif
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif
#ifdef __cpp_lib_filesystem
@@ -39,12 +56,13 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
}
# ifdef _WIN32
template <>
inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted,
inline void write_escaped_path<char>(memory_buffer& quoted,
const std::filesystem::path& p) {
auto s = p.u8string();
write_escaped_string<char>(
std::back_inserter(quoted),
string_view(reinterpret_cast<const char*>(s.c_str()), s.size()));
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), p.native());
// Convert UTF-16 to UTF-8.
if (!unicode_to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()}))
FMT_THROW(std::runtime_error("invalid utf16"));
}
# endif
template <>
@@ -57,13 +75,19 @@ inline void write_escaped_path<std::filesystem::path::value_type>(
} // namespace detail
FMT_MODULE_EXPORT
template <typename Char>
struct formatter<std::filesystem::path, Char>
: formatter<basic_string_view<Char>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
auto out = formatter<basic_string_view<Char>>::parse(ctx);
this->set_debug_format(false);
return out;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
typename FormatContext::iterator {
basic_memory_buffer<Char> quoted;
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p);
return formatter<basic_string_view<Char>>::format(
basic_string_view<Char>(quoted.data(), quoted.size()), ctx);
@@ -73,12 +97,58 @@ FMT_END_NAMESPACE
#endif
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(std::optional<T> const& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, optional);
ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#ifdef __cpp_lib_variant
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
@@ -100,19 +170,16 @@ template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
// variant_size and variant_alternative check.
template <typename T, typename U = void>
struct is_variant_like_ : std::false_type {};
template <typename T>
struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>>
: std::true_type {};
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... I>
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<I, T>, C>...>
check(std::index_sequence<I...>);
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
@@ -130,7 +197,6 @@ auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt {
}
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
@@ -140,6 +206,7 @@ template <typename T, typename C> struct is_variant_formattable {
detail::is_variant_formattable_<T, C>::value;
};
FMT_MODULE_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
@@ -156,16 +223,127 @@ struct formatter<
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
try {
std::visit(
[&](const auto& v) {
out = detail::write_variant_alternative<Char>(out, v);
},
value);
} catch (const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_variant
FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(), format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
FMT_MODULE_EXPORT
template <typename T, typename Char>
struct formatter<
T, Char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = true;
}
return it;
}
template <typename OutputIt>
auto format(const std::exception& ex,
basic_format_context<OutputIt, Char>& ctx) const -> OutputIt {
format_specs<Char> spec;
auto out = ctx.out();
if (!with_typename_)
return detail::write_bytes(out, string_view(ex.what()), spec);
const std::type_info& ti = typeid(ex);
#ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
out = detail::write_bytes(out, demangled_name_view, spec);
#elif FMT_MSC_VERSION
string_view demangled_name_view(ti.name());
if (demangled_name_view.starts_with("class "))
demangled_name_view.remove_prefix(6);
else if (demangled_name_view.starts_with("struct "))
demangled_name_view.remove_prefix(7);
out = detail::write_bytes(out, demangled_name_view, spec);
#else
out = detail::write_bytes(out, string_view(ti.name()), spec);
#endif
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, Char(' '));
out = detail::write_bytes(out, string_view(ex.what()), spec);
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_
+37 -7
View File
@@ -12,13 +12,32 @@
#include "format.h"
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
# include <locale>
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
}
FMT_MODULE_EXPORT_BEGIN
inline auto write_loc(std::back_insert_iterator<detail::buffer<wchar_t>> out,
loc_value value, const format_specs<wchar_t>& specs,
locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
@@ -33,7 +52,9 @@ inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
inline auto runtime(wstring_view s) -> basic_runtime<wchar_t> { return {{s}}; }
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
@@ -126,7 +147,7 @@ auto vformat_to(OutputIt out, const S& format_str,
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... Args,
@@ -149,7 +170,7 @@ inline auto vformat_to(
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
return detail::get_iterator(buf);
return detail::get_iterator(buf, out);
}
template <
@@ -160,7 +181,7 @@ template <
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, to_string_view(format_str),
return vformat_to(out, loc, detail::to_string_view(format_str),
fmt::make_format_args<buffer_context<Char>>(args...));
}
@@ -217,13 +238,22 @@ template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename... T>
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/**
Converts *value* to ``std::wstring`` using the default format for type *T*.
*/
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);
}
FMT_MODULE_EXPORT_END
FMT_END_EXPORT
FMT_END_NAMESPACE
#endif // FMT_XCHAR_H_
+43
View File
@@ -0,0 +1,43 @@
// Formatting library for C++
//
// Copyright (c) 2012 - 2016, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept
-> dragonbox::decimal_fp<float>;
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
// Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char;
template FMT_API void buffer<char>::append(const char*, const char*);
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail
FMT_END_NAMESPACE
+13
View File
@@ -19,6 +19,7 @@
#pragma once
#include "fs_fstat.hpp"
#include "fs_dirfd.hpp"
namespace fs
@@ -37,4 +38,16 @@ namespace fs
return st.st_dev;
}
static
inline
dev_t
devid(DIR *dh_)
{
int dirfd;
dirfd = fs::dirfd(dh_);
return fs::devid(dirfd);
}
}
+2 -2
View File
@@ -27,8 +27,8 @@ namespace fs
static
inline
int
dirfd(DIR *dirp_)
dirfd(DIR *dh_)
{
return ::dirfd(dirp_);
return ::dirfd(dh_);
}
}
+13
View File
@@ -208,6 +208,19 @@ namespace fs
return g_func(fusepath_,fusepath_len_,mode_,dev_,ino_);
}
uint64_t
calc(std::string const &fusepath_,
const mode_t mode_,
const dev_t dev_,
const ino_t ino_)
{
return calc(fusepath_.c_str(),
fusepath_.size(),
mode_,
dev_,
ino_);
}
void
calc(const char *fusepath_,
const uint64_t fusepath_len_,
+5 -1
View File
@@ -37,7 +37,11 @@ namespace fs
const uint64_t fusepath_len,
const mode_t mode,
const dev_t dev,
const ino_t ion);
const ino_t ino);
uint64_t calc(std::string const &fusepath,
mode_t const mode,
dev_t const dev,
ino_t ino);
void calc(const char *fusepath,
const uint64_t fusepath_len,
struct stat *st);
+9 -5
View File
@@ -19,16 +19,20 @@
#include "errno.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "rnd.hpp"
#include <limits.h>
#include <cstdlib>
#include <string>
#include <tuple>
#define PAD_LEN 16
#define MAX_ATTEMPTS 3
static char const CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static char const CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static size_t const CHARS_SIZE = (sizeof(CHARS) - 1);
namespace l
{
@@ -41,7 +45,7 @@ namespace l
filename = '.';
for(int i = 0; i < PAD_LEN; i++)
filename += CHARS[std::rand() % (sizeof(CHARS) - 1)];
filename += CHARS[RND::rand64(CHARS_SIZE)];
path = base_;
path /= filename;
@@ -72,12 +76,12 @@ namespace fs
if((fd == -1) && (errno == EEXIST))
continue;
if(fd == -1)
return {-errno,std::string{}};
return std::make_tuple(-errno,std::string());
return {fd,tmp_filepath};
return std::make_tuple(fd,tmp_filepath);
}
return {-EEXIST,std::string{}};
return std::make_tuple(-EEXIST,std::string());
}
std::tuple<int,std::string>
+28
View File
@@ -51,6 +51,31 @@ namespace l
*flags_ &= ~O_APPEND;
}
static
bool
rdonly(const int flags_)
{
return ((flags_ & O_ACCMODE) == O_RDONLY);
}
static
bool
calculate_flush(FlushOnClose const flushonclose_,
int const flags_)
{
switch(flushonclose_)
{
case FlushOnCloseEnum::NEVER:
return false;
case FlushOnCloseEnum::OPENED_FOR_WRITE:
return !l::rdonly(flags_);
case FlushOnCloseEnum::ALWAYS:
return true;
}
return true;
}
static
void
config_to_ffi_flags(Config::Read &cfg_,
@@ -200,6 +225,9 @@ namespace FUSE
if(cfg->writeback_cache)
l::tweak_flags_writeback_cache(&ffi_->flags);
ffi_->noflush = !l::calculate_flush(cfg->flushonclose,
ffi_->flags);
rv = l::create(cfg->func.getattr.policy,
cfg->func.create.policy,
cfg->branches,
+2
View File
@@ -137,6 +137,8 @@ namespace FUSE
ugid::init();
cfg->readdir.initialize();
l::want_if_capable(conn_,FUSE_CAP_ASYNC_DIO);
l::want_if_capable(conn_,FUSE_CAP_ASYNC_READ,&cfg->async_read);
l::want_if_capable(conn_,FUSE_CAP_ATOMIC_O_TRUNC);
+7 -1
View File
@@ -24,6 +24,7 @@
#include "fs_ioctl.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "gidcache.hpp"
#include "str.hpp"
#include "ugid.hpp"
@@ -41,11 +42,13 @@ using std::vector;
#endif
typedef char IOCTL_BUF[4096];
#define IOCTL_APP_TYPE 0xDF
#define IOCTL_APP_TYPE 0xDF
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
#define IOCTL_GC _IO(IOCTL_APP_TYPE,1)
#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2)
#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3)
#define IOCTL_INVALIDATE_GID_CACHE _IO(IOCTL_APP_TYPE,4)
// From linux/btrfs.h
#define BTRFS_IOCTL_MAGIC 0x94
@@ -346,6 +349,9 @@ namespace l
case IOCTL_INVALIDATE_ALL_NODES:
fuse_invalidate_all_nodes();
return 0;
case IOCTL_INVALIDATE_GID_CACHE:
GIDCache::invalidate_all_caches();
break;
}
return -ENOTTY;
+21
View File
@@ -114,6 +114,24 @@ namespace l
*flags_ &= ~O_APPEND;
}
static
bool
calculate_flush(FlushOnClose const flushonclose_,
int const flags_)
{
switch(flushonclose_)
{
case FlushOnCloseEnum::NEVER:
return false;
case FlushOnCloseEnum::OPENED_FOR_WRITE:
return !l::rdonly(flags_);
case FlushOnCloseEnum::ALWAYS:
return true;
}
return true;
}
static
void
config_to_ffi_flags(Config::Read &cfg_,
@@ -236,6 +254,9 @@ namespace FUSE
if(cfg->writeback_cache)
l::tweak_flags_writeback_cache(&ffi_->flags);
ffi_->noflush = !l::calculate_flush(cfg->flushonclose,
ffi_->flags);
rv = l::open(cfg->func.open.policy,
cfg->branches,
fusepath_,
+2
View File
@@ -30,6 +30,8 @@ namespace FUSE
ffi_->fh = reinterpret_cast<uint64_t>(new DirInfo(fusepath_));
ffi_->noflush = true;
if(cfg->cache_readdir)
{
ffi_->keep_cache = 1;
+82 -22
View File
@@ -16,35 +16,95 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_readdir_posix.hpp"
#include "fuse_readdir_linux.hpp"
#include "fuse_readdir.hpp"
#include "config.hpp"
#include "dirinfo.hpp"
#include "rwlock.hpp"
#include "ugid.hpp"
#include "fuse_readdir_factory.hpp"
#include "fuse.h"
/*
The _initialized stuff is not pretty but easiest way to deal with
the fact that mergerfs is doing arg parsing and setting up of things
(including thread pools) before the daemonizing
*/
namespace FUSE
int
FUSE::readdir(const fuse_file_info_t *ffi_,
fuse_dirents_t *buf_)
{
int
readdir(const fuse_file_info_t *ffi_,
fuse_dirents_t *buf_)
{
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
Config::Write cfg;
switch(cfg->readdir)
return cfg->readdir(ffi_,buf_);
}
FUSE::ReadDir::ReadDir(std::string const s_)
: _initialized(false)
{
from_string(s_);
if(_initialized)
assert(_readdir);
}
std::string
FUSE::ReadDir::to_string() const
{
std::lock_guard<std::mutex> lg(_mutex);
return _type;
}
void
FUSE::ReadDir::initialize()
{
_initialized = true;
from_string(_type);
}
int
FUSE::ReadDir::from_string(std::string const &str_)
{
if(_initialized)
{
std::shared_ptr<FUSE::ReadDirBase> tmp;
tmp = FUSE::ReadDirFactory::make(str_);
if(!tmp)
return -EINVAL;
{
case ReadDir::ENUM::LINUX:
return FUSE::readdir_linux(cfg->branches,di->fusepath.c_str(),buf_);
default:
case ReadDir::ENUM::POSIX:
return FUSE::readdir_posix(cfg->branches,di->fusepath.c_str(),buf_);
std::lock_guard<std::mutex> lg(_mutex);
_type = str_;
std::swap(_readdir,tmp);
}
}
else
{
std::lock_guard<std::mutex> lg(_mutex);
if(!FUSE::ReadDirFactory::valid(str_))
return -EINVAL;
_type = str_;
}
return 0;
}
/*
Yeah... if not initialized it will crash... ¯\_()_/¯
This will be resolved once initialization of internal objects and
handling of input is better seperated.
*/
int
FUSE::ReadDir::operator()(fuse_file_info_t const *ffi_,
fuse_dirents_t *buf_)
{
std::shared_ptr<FUSE::ReadDirBase> readdir;
{
std::lock_guard<std::mutex> lg(_mutex);
readdir = _readdir;
}
return (*readdir)(ffi_,buf_);
}
+37 -3
View File
@@ -17,11 +17,45 @@
#pragma once
#include "fuse.h"
#include "tofrom_string.hpp"
#include "fuse_readdir_base.hpp"
#include <memory>
#include <mutex>
#include <assert.h>
// The initialization behavior is not pretty but for the moment
// needed due to the daemonizing function of the libfuse library when
// not using foreground mode. The threads need to be created after the
// fork, not before.
namespace FUSE
{
int
readdir(const fuse_file_info_t *ffi,
fuse_dirents_t *buf);
int readdir(fuse_file_info_t const *ffi,
fuse_dirents_t *buf);
class ReadDir : public ToFromString
{
public:
ReadDir(std::string const s_);
public:
std::string to_string() const;
int from_string(const std::string &);
public:
int operator()(fuse_file_info_t const *ffi,
fuse_dirents_t *buf);
public:
void initialize();
private:
mutable std::mutex _mutex;
private:
bool _initialized;
std::string _type;
std::shared_ptr<FUSE::ReadDirBase> _readdir;
};
}
+34
View File
@@ -0,0 +1,34 @@
/*
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fuse.h"
namespace FUSE
{
class ReadDirBase
{
public:
ReadDirBase() {};
virtual ~ReadDirBase() {};
public:
virtual int operator()(fuse_file_info_t const *ffi,
fuse_dirents_t *buf) = 0;
};
}
+208
View File
@@ -0,0 +1,208 @@
/*
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_readdir_cor.hpp"
#include "config.hpp"
#include "dirinfo.hpp"
#include "errno.hpp"
#include "fs_close.hpp"
#include "fs_devid.hpp"
#include "fs_getdents64.hpp"
#include "fs_inode.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "fs_readdir.hpp"
#include "hashset.hpp"
#include "scope_guard.hpp"
#include "ugid.hpp"
#include "fuse_dirents.h"
#include "linux_dirent64.h"
FUSE::ReadDirCOR::ReadDirCOR(unsigned concurrency_,
unsigned max_queue_depth_)
: _tp(concurrency_,max_queue_depth_,"readdir.cor")
{
}
FUSE::ReadDirCOR::~ReadDirCOR()
{
}
namespace l
{
struct Error
{
private:
int _err;
public:
Error()
: _err(ENOENT)
{
}
operator int()
{
return _err;
}
Error&
operator=(int v_)
{
if(_err != 0)
_err = v_;
return *this;
}
};
static
inline
int
readdir(std::string basepath_,
HashSet &names_,
fuse_dirents_t *buf_,
std::mutex &mutex_)
{
int rv;
int dfd;
dev_t dev;
std::string filepath;
dfd = fs::open_dir_ro(basepath_);
if(dfd == -1)
return errno;
DEFER{ fs::close(dfd); };
dev = fs::devid(dfd);
rv = 0;
for(;;)
{
long nread;
char buf[32 * 1024];
nread = fs::getdents_64(dfd,buf,sizeof(buf));
if(nread == -1)
return errno;
if(nread == 0)
break;
linux_dirent64_t *d;
std::lock_guard<std::mutex> lk(mutex_);
for(long pos = 0; pos < nread; pos += d->reclen)
{
std::uint64_t namelen;
d = (linux_dirent64_t*)&buf[pos];
namelen = DIRENT_NAMELEN(d);
rv = names_.put(d->name,namelen);
if(rv == 0)
continue;
filepath = fs::path::make(basepath_,d->name);
d->ino = fs::inode::calc(filepath,
DTTOIF(d->type),
dev,
d->ino);
rv = fuse_dirents_add_linux(buf_,d,namelen);
if(rv >= 0)
continue;
return ENOMEM;
}
}
return 0;
}
static
int
concurrent_readdir(ThreadPool &tp_,
const Branches::CPtr &branches_,
const char *dirname_,
fuse_dirents_t *buf_,
uid_t const uid_,
gid_t const gid_)
{
HashSet names;
std::mutex mutex;
std::vector<std::future<int>> futures;
futures.reserve(branches_->size());
for(auto const &branch : *branches_)
{
auto func = [&,dirname_,buf_,uid_,gid_]()
{
std::string basepath;
ugid::Set const ugid(uid_,gid_);
basepath = fs::path::make(branch.path,dirname_);
return l::readdir(basepath,names,buf_,mutex);
};
auto rv = tp_.enqueue_task(func);
futures.emplace_back(std::move(rv));
}
Error error;
for(auto &future : futures)
error = future.get();
return -error;
}
static
int
readdir(ThreadPool &tp_,
const Branches::CPtr &branches_,
const char *dirname_,
fuse_dirents_t *buf_,
uid_t const uid_,
gid_t const gid_)
{
fuse_dirents_reset(buf_);
return l::concurrent_readdir(tp_,branches_,dirname_,buf_,uid_,gid_);
}
}
int
FUSE::ReadDirCOR::operator()(fuse_file_info_t const *ffi_,
fuse_dirents_t *buf_)
{
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
return l::readdir(_tp,
cfg->branches,
di->fusepath.c_str(),
buf_,
fc->uid,
fc->gid);
}
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -18,19 +18,25 @@
#pragma once
#include "branches.hpp"
#include "fuse_readdir_base.hpp"
#include "fuse.h"
#include <cstdint>
#include "thread_pool.hpp"
// concurrent open & read
namespace FUSE
{
int
readdir_plus_linux(const Branches::CPtr &branches,
const char *dirname,
const uint64_t entry_timeout,
const uint64_t attr_timeout,
fuse_dirents_t *buf);
class ReadDirCOR final : public FUSE::ReadDirBase
{
public:
ReadDirCOR(unsigned concurrency,
unsigned max_queue_depth);
~ReadDirCOR();
int operator()(fuse_file_info_t const *ffi,
fuse_dirents_t *buf);
private:
ThreadPool _tp;
};
}
+224
View File
@@ -0,0 +1,224 @@
/*
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_readdir_cosr.hpp"
#include "config.hpp"
#include "dirinfo.hpp"
#include "errno.hpp"
#include "fs_closedir.hpp"
#include "fs_devid.hpp"
#include "fs_dirfd.hpp"
#include "fs_inode.hpp"
#include "fs_opendir.hpp"
#include "fs_path.hpp"
#include "fs_readdir.hpp"
#include "fs_stat.hpp"
#include "hashset.hpp"
#include "scope_guard.hpp"
#include "ugid.hpp"
#include "fuse_dirents.h"
FUSE::ReadDirCOSR::ReadDirCOSR(unsigned concurrency_,
unsigned max_queue_depth_)
: _tp(concurrency_,max_queue_depth_,"readdir.cosr")
{
}
FUSE::ReadDirCOSR::~ReadDirCOSR()
{
}
namespace l
{
struct DirRV
{
DIR *dir;
int err;
};
struct Error
{
private:
int _err;
public:
Error()
: _err(ENOENT)
{
}
operator int()
{
return _err;
}
Error&
operator=(int const v_)
{
if(_err != 0)
_err = v_;
return *this;
}
};
static
uint64_t
dirent_exact_namelen(const struct dirent *d_)
{
#ifdef _D_EXACT_NAMLEN
return _D_EXACT_NAMLEN(d_);
#elif defined _DIRENT_HAVE_D_NAMLEN
return d_->d_namlen;
#else
return strlen(d_->d_name);
#endif
}
static
inline
std::vector<std::future<DirRV>>
opendir(ThreadPool &tp_,
const Branches::CPtr &branches_,
char const *dirname_,
uid_t const uid_,
gid_t const gid_)
{
std::vector<std::future<DirRV>> futures;
futures.reserve(branches_->size());
for(auto const &branch : *branches_)
{
auto func = [&branch,dirname_,uid_,gid_]()
{
DirRV rv;
std::string basepath;
ugid::Set const ugid(uid_,gid_);
basepath = fs::path::make(branch.path,dirname_);
errno = 0;
rv.dir = fs::opendir(basepath);
rv.err = errno;
return rv;
};
auto rv = tp_.enqueue_task(func);
futures.emplace_back(std::move(rv));
}
return futures;
}
static
inline
int
readdir(std::vector<std::future<DirRV>> &dh_futures_,
char const *dirname_,
fuse_dirents_t *buf_)
{
Error error;
HashSet names;
std::string fullpath;
for(auto &dh_future : dh_futures_)
{
int rv;
dev_t dev;
DirRV dirrv;
dirrv = dh_future.get();
error = dirrv.err;
if(dirrv.dir == NULL)
continue;
DEFER { fs::closedir(dirrv.dir); };
dev = fs::devid(dirrv.dir);
rv = 0;
for(dirent *de = fs::readdir(dirrv.dir); de && !rv; de = fs::readdir(dirrv.dir))
{
std::uint64_t namelen;
namelen = l::dirent_exact_namelen(de);
rv = names.put(de->d_name,namelen);
if(rv == 0)
continue;
fullpath = fs::path::make(dirname_,de->d_name);
de->d_ino = fs::inode::calc(fullpath,
DTTOIF(de->d_type),
dev,
de->d_ino);
rv = fuse_dirents_add(buf_,de,namelen);
if(rv == 0)
continue;
error = ENOMEM;
}
}
return -error;
}
static
inline
int
readdir(ThreadPool &tp_,
const Branches::CPtr &branches_,
const char *dirname_,
fuse_dirents_t *buf_,
uid_t const uid_,
gid_t const gid_)
{
int rv;
std::vector<std::future<DirRV>> futures;
fuse_dirents_reset(buf_);
futures = l::opendir(tp_,branches_,dirname_,uid_,gid_);
rv = l::readdir(futures,dirname_,buf_);
return rv;
}
}
int
FUSE::ReadDirCOSR::operator()(fuse_file_info_t const *ffi_,
fuse_dirents_t *buf_)
{
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
return l::readdir(_tp,
cfg->branches,
di->fusepath.c_str(),
buf_,
fc->uid,
fc->gid);
}
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -18,19 +18,24 @@
#pragma once
#include "branches.hpp"
#include "fuse.h"
#include <cstdint>
#include "fuse_readdir_base.hpp"
#include "thread_pool.hpp"
// concurrent open, sequential read
namespace FUSE
{
int
readdir_plus_posix(const Branches::CPtr &branches,
const char *dirname,
const uint64_t entry_timeout,
const uint64_t attr_timeout,
fuse_dirents_t *buf);
class ReadDirCOSR final : public FUSE::ReadDirBase
{
public:
ReadDirCOSR(unsigned concurrency,
unsigned max_queue_depth);
~ReadDirCOSR();
int operator()(fuse_file_info_t const *ffi,
fuse_dirents_t *buf);
private:
ThreadPool _tp;
};
}
+114
View File
@@ -0,0 +1,114 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_readdir_factory.hpp"
#include "fuse_readdir_cor.hpp"
#include "fuse_readdir_cosr.hpp"
#include "fuse_readdir_seq.hpp"
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <set>
#define DEFAULT_MAX_QUEUE_DEPTH 3
namespace l
{
static
void
read_cfg(std::string const str_,
std::string &type_,
unsigned &concurrency_,
unsigned &max_queue_depth_)
{
char type[16];
int concurrency;
int max_queue_depth;
concurrency = 0;
max_queue_depth = 0;
std::sscanf(str_.c_str(),
"%15[a-z]:%d:%d",
type,
&concurrency,
&max_queue_depth);
if(concurrency == 0)
concurrency = std::thread::hardware_concurrency();
else if(concurrency < 0)
concurrency = (std::thread::hardware_concurrency() / std::abs(concurrency));
if(concurrency == 0)
concurrency = 1;
if(max_queue_depth == 0)
max_queue_depth = DEFAULT_MAX_QUEUE_DEPTH;
max_queue_depth *= concurrency;
type_ = type;
concurrency_ = concurrency;
max_queue_depth_ = max_queue_depth;
}
}
bool
FUSE::ReadDirFactory::valid(std::string const str_)
{
unsigned concurrency;
unsigned max_queue_depth;
std::string type;
static const std::set<std::string> types =
{
"seq", "cosr", "cor"
};
l::read_cfg(str_,type,concurrency,max_queue_depth);
if(types.find(type) == types.end())
return false;
if(concurrency <= 0)
return false;
return true;
}
std::shared_ptr<FUSE::ReadDirBase>
FUSE::ReadDirFactory::make(std::string const str_)
{
unsigned concurrency;
unsigned max_queue_depth;
std::string type;
if(!valid(str_))
return {};
l::read_cfg(str_,type,concurrency,max_queue_depth);
if(type == "seq")
return std::make_shared<FUSE::ReadDirSeq>();
if(type == "cosr")
return std::make_shared<FUSE::ReadDirCOSR>(concurrency,max_queue_depth);
if(type == "cor")
return std::make_shared<FUSE::ReadDirCOR>(concurrency,max_queue_depth);
return {};
}
+35
View File
@@ -0,0 +1,35 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fuse_readdir_base.hpp"
#include <string>
#include <memory>
namespace FUSE
{
class ReadDirFactory
{
public:
static bool valid(std::string str);
static std::shared_ptr<ReadDirBase> make(std::string const str);
};
}
-135
View File
@@ -1,135 +0,0 @@
/*
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "branches.hpp"
#include "errno.hpp"
#include "fs_close.hpp"
#include "fs_devid.hpp"
#include "fs_getdents64.hpp"
#include "fs_inode.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "fs_stat.hpp"
#include "hashset.hpp"
#include "linux_dirent64.h"
#include "mempools.hpp"
#include "fuse.h"
#include "fuse_dirents.h"
#include <string>
#include <vector>
#include <stddef.h>
using std::string;
using std::vector;
namespace l
{
static
int
close_free_ret_enomem(int fd_,
void *buf_)
{
fs::close(fd_);
g_DENTS_BUF_POOL.free(buf_);
return -ENOMEM;
}
static
int
readdir(const Branches::CPtr &branches_,
const char *dirname_,
fuse_dirents_t *buf_)
{
int rv;
dev_t dev;
char *buf;
HashSet names;
string basepath;
string fullpath;
uint64_t namelen;
struct linux_dirent64 *d;
fuse_dirents_reset(buf_);
buf = (char*)g_DENTS_BUF_POOL.alloc();
if(buf == NULL)
return -ENOMEM;
for(const auto &branch : *branches_)
{
int dirfd;
int64_t nread;
basepath = fs::path::make(branch.path,dirname_);
dirfd = fs::open_dir_ro(basepath);
if(dirfd == -1)
continue;
dev = fs::devid(dirfd);
for(;;)
{
nread = fs::getdents_64(dirfd,buf,g_DENTS_BUF_POOL.size());
if(nread == -1)
break;
if(nread == 0)
break;
for(int64_t pos = 0; pos < nread; pos += d->reclen)
{
d = (struct linux_dirent64*)(buf + pos);
namelen = strlen(d->name);
rv = names.put(d->name,namelen);
if(rv == 0)
continue;
fullpath = fs::path::make(dirname_,d->name);
d->ino = fs::inode::calc(fullpath.c_str(),
fullpath.size(),
DTTOIF(d->type),
dev,
d->ino);
rv = fuse_dirents_add_linux(buf_,d,namelen);
if(rv)
return close_free_ret_enomem(dirfd,buf);
}
}
fs::close(dirfd);
}
g_DENTS_BUF_POOL.free(buf);
return 0;
}
}
namespace FUSE
{
int
readdir_linux(const Branches::CPtr &branches_,
const char *dirname_,
fuse_dirents_t *buf_)
{
return l::readdir(branches_,dirname_,buf_);
}
}
+7 -36
View File
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -16,43 +16,14 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_readdir_plus_linux.hpp"
#include "fuse_readdir_plus_posix.hpp"
#include "fuse_readdir_plus.hpp"
#include "config.hpp"
#include "dirinfo.hpp"
#include "rwlock.hpp"
#include "ugid.hpp"
#include "fuse.h"
#include <errno.h>
namespace FUSE
int
FUSE::readdir_plus(const fuse_file_info_t *ffi_,
fuse_dirents_t *buf_)
{
int
readdir_plus(const fuse_file_info_t *ffi_,
fuse_dirents_t *buf_)
{
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
switch(cfg->readdir)
{
case ReadDir::ENUM::LINUX:
return FUSE::readdir_plus_linux(cfg->branches,
di->fusepath.c_str(),
cfg->cache_entry,
cfg->cache_attr,
buf_);
default:
case ReadDir::ENUM::POSIX:
return FUSE::readdir_plus_posix(cfg->branches,
di->fusepath.c_str(),
cfg->cache_entry,
cfg->cache_attr,
buf_);
}
}
return -ENOTSUP;
}
+3 -4
View File
@@ -1,5 +1,5 @@
/*
Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -21,7 +21,6 @@
namespace FUSE
{
int
readdir_plus(const fuse_file_info_t *ffi,
fuse_dirents_t *buf);
int readdir_plus(fuse_file_info_t const *ffi,
fuse_dirents_t *buf);
}
-151
View File
@@ -1,151 +0,0 @@
/*
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 "branches.hpp"
#include "errno.hpp"
#include "fs_close.hpp"
#include "fs_devid.hpp"
#include "fs_fstatat.hpp"
#include "fs_getdents64.hpp"
#include "fs_inode.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "fs_stat.hpp"
#include "hashset.hpp"
#include "linux_dirent64.h"
#include "mempools.hpp"
#include "fuse.h"
#include "fuse_dirents.h"
#include <string>
#include <vector>
#include <stddef.h>
using std::string;
using std::vector;
namespace l
{
static
int
close_free_ret_enomem(int fd_,
void *buf_)
{
fs::close(fd_);
g_DENTS_BUF_POOL.free(buf_);
return -ENOMEM;
}
static
int
readdir_plus(const Branches::CPtr &branches_,
const char *dirname_,
const uint64_t entry_timeout_,
const uint64_t attr_timeout_,
fuse_dirents_t *buf_)
{
int rv;
dev_t dev;
char *buf;
HashSet names;
string basepath;
string fullpath;
uint64_t namelen;
struct stat st;
fuse_entry_t entry;
struct linux_dirent64 *d;
fuse_dirents_reset(buf_);
buf = (char*)g_DENTS_BUF_POOL.alloc();
entry.nodeid = 0;
entry.generation = 0;
entry.entry_valid = entry_timeout_;
entry.attr_valid = attr_timeout_;
entry.entry_valid_nsec = 0;
entry.attr_valid_nsec = 0;
for(auto &branch : *branches_)
{
int dirfd;
int64_t nread;
basepath = fs::path::make(branch.path,dirname_);
dirfd = fs::open_dir_ro(basepath);
if(dirfd == -1)
continue;
dev = fs::devid(dirfd);
for(;;)
{
nread = fs::getdents_64(dirfd,buf,g_DENTS_BUF_POOL.size());
if(nread == -1)
break;
if(nread == 0)
break;
for(int64_t pos = 0; pos < nread; pos += d->reclen)
{
d = (struct linux_dirent64*)(buf + pos);
namelen = (strlen(d->name) + 1);
rv = names.put(d->name,namelen);
if(rv == 0)
continue;
rv = fs::fstatat_nofollow(dirfd,d->name,&st);
if(rv == -1)
{
memset(&st,0,sizeof(st));
st.st_ino = d->ino;
st.st_dev = dev;
st.st_mode = DTTOIF(d->type);
}
fullpath = fs::path::make(dirname_,d->name);
fs::inode::calc(fullpath,&st);
d->ino = st.st_ino;
rv = fuse_dirents_add_linux_plus(buf_,d,namelen,&entry,&st);
if(rv)
return close_free_ret_enomem(dirfd,buf);
}
}
fs::close(dirfd);
}
g_DENTS_BUF_POOL.free(buf);
return 0;
}
}
namespace FUSE
{
int
readdir_plus_linux(const Branches::CPtr &branches_,
const char *dirname_,
const uint64_t entry_timeout_,
const uint64_t attr_timeout_,
fuse_dirents_t *buf_)
{
return l::readdir_plus(branches_,dirname_,entry_timeout_,attr_timeout_,buf_);
}
}
-142
View File
@@ -1,142 +0,0 @@
/*
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.
*/
#define _DEFAULT_SOURCE
#include "branches.hpp"
#include "errno.hpp"
#include "fs_closedir.hpp"
#include "fs_devid.hpp"
#include "fs_dirfd.hpp"
#include "fs_fstatat.hpp"
#include "fs_inode.hpp"
#include "fs_opendir.hpp"
#include "fs_path.hpp"
#include "fs_readdir.hpp"
#include "fs_stat.hpp"
#include "hashset.hpp"
#include "fuse.h"
#include "fuse_dirents.h"
#include <string>
#include <vector>
#include <dirent.h>
using std::string;
using std::vector;
namespace l
{
static
uint64_t
dirent_exact_namelen(const struct dirent *d_)
{
#ifdef _D_EXACT_NAMLEN
return _D_EXACT_NAMLEN(d_);
#elif defined _DIRENT_HAVE_D_NAMLEN
return d_->d_namlen;
#else
return strlen(d_->d_name);
#endif
}
static
int
readdir_plus(const Branches::CPtr &branches_,
const char *dirname_,
const uint64_t entry_timeout_,
const uint64_t attr_timeout_,
fuse_dirents_t *buf_)
{
dev_t dev;
HashSet names;
string basepath;
string fullpath;
struct stat st;
uint64_t namelen;
fuse_entry_t entry;
fuse_dirents_reset(buf_);
entry.nodeid = 0;
entry.generation = 0;
entry.entry_valid = entry_timeout_;
entry.attr_valid = attr_timeout_;
entry.entry_valid_nsec = 0;
entry.attr_valid_nsec = 0;
for(auto &branch : *branches_)
{
int rv;
int dirfd;
DIR *dh;
basepath = fs::path::make(branch.path,dirname_);
dh = fs::opendir(basepath);
if(!dh)
continue;
dirfd = fs::dirfd(dh);
dev = fs::devid(dirfd);
rv = 0;
for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh))
{
namelen = l::dirent_exact_namelen(de);
rv = names.put(de->d_name,namelen);
if(rv == 0)
continue;
rv = fs::fstatat_nofollow(dirfd,de->d_name,&st);
if(rv == -1)
{
memset(&st,0,sizeof(st));
st.st_ino = de->d_ino;
st.st_dev = dev;
st.st_mode = DTTOIF(de->d_type);
}
fullpath = fs::path::make(dirname_,de->d_name);
fs::inode::calc(fullpath,&st);
de->d_ino = st.st_ino;
rv = fuse_dirents_add_plus(buf_,de,namelen,&entry,&st);
if(rv)
return (fs::closedir(dh),-ENOMEM);
}
fs::closedir(dh);
}
return 0;
}
}
namespace FUSE
{
int
readdir_plus_posix(const Branches::CPtr &branches_,
const char *dirname_,
const uint64_t entry_timeout_,
const uint64_t attr_timeout_,
fuse_dirents_t *buf_)
{
return l::readdir_plus(branches_,dirname_,entry_timeout_,attr_timeout_,buf_);
}
}
@@ -16,7 +16,11 @@
#define _DEFAULT_SOURCE
#include "fuse_readdir_seq.hpp"
#include "branches.hpp"
#include "config.hpp"
#include "dirinfo.hpp"
#include "errno.hpp"
#include "fs_closedir.hpp"
#include "fs_devid.hpp"
@@ -27,21 +31,44 @@
#include "fs_readdir.hpp"
#include "fs_stat.hpp"
#include "hashset.hpp"
#include "scope_guard.hpp"
#include "ugid.hpp"
#include "fuse.h"
#include "fuse_dirents.h"
#include <string>
#include <vector>
#include <dirent.h>
using std::string;
using std::vector;
namespace l
{
struct Error
{
private:
int _err;
public:
Error()
: _err(ENOENT)
{
}
operator int()
{
return _err;
}
Error&
operator=(int v_)
{
if(_err != 0)
_err = v_;
return *this;
}
};
static
uint64_t
dirent_exact_namelen(const struct dirent *d_)
@@ -61,32 +88,36 @@ namespace l
const char *dirname_,
fuse_dirents_t *buf_)
{
dev_t dev;
Error error;
HashSet names;
string basepath;
string fullpath;
uint64_t namelen;
std::string basepath;
std::string fullpath;
fuse_dirents_reset(buf_);
for(const auto &branch : *branches_)
for(auto const &branch : *branches_)
{
int rv;
int dirfd;
DIR *dh;
dev_t dev;
basepath = fs::path::make(branch.path,dirname_);
errno = 0;
dh = fs::opendir(basepath);
error = errno;
if(!dh)
continue;
dirfd = fs::dirfd(dh);
dev = fs::devid(dirfd);
DEFER{ fs::closedir(dh); };
dev = fs::devid(dh);
rv = 0;
for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh))
for(dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh))
{
std::uint64_t namelen;
namelen = l::dirent_exact_namelen(de);
rv = names.put(de->d_name,namelen);
@@ -94,31 +125,31 @@ namespace l
continue;
fullpath = fs::path::make(dirname_,de->d_name);
de->d_ino = fs::inode::calc(fullpath.c_str(),
fullpath.size(),
de->d_ino = fs::inode::calc(fullpath,
DTTOIF(de->d_type),
dev,
de->d_ino);
rv = fuse_dirents_add(buf_,de,namelen);
if(rv)
return (fs::closedir(dh),-ENOMEM);
return -ENOMEM;
}
fs::closedir(dh);
}
return 0;
return -error;
}
}
namespace FUSE
int
FUSE::ReadDirSeq::operator()(fuse_file_info_t const *ffi_,
fuse_dirents_t *buf_)
{
int
readdir_posix(const Branches::CPtr &branches_,
const char *dirname_,
fuse_dirents_t *buf_)
{
return l::readdir(branches_,dirname_,buf_);
}
Config::Read cfg;
DirInfo *di = reinterpret_cast<DirInfo*>(ffi_->fh);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::readdir(cfg->branches,
di->fusepath.c_str(),
buf_);
}
@@ -18,17 +18,18 @@
#pragma once
#include "branches.hpp"
#include "fuse.h"
#include <cstdint>
#include "fuse_readdir_base.hpp"
namespace FUSE
{
int
readdir_linux(const Branches::CPtr &branches,
const char *dirname,
fuse_dirents_t *buf);
class ReadDirSeq final : public FUSE::ReadDirBase
{
public:
ReadDirSeq() {}
~ReadDirSeq() {}
int operator()(fuse_file_info_t const *ffi,
fuse_dirents_t *buf);
};
}
+28
View File
@@ -0,0 +1,28 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_removemapping.hpp"
#include <errno.h>
int
FUSE::removemapping()
{
return -ENOSYS;
}
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -18,13 +18,10 @@
#pragma once
#include "enum.hpp"
#include "fuse.h"
enum class ReadDirEnum
{
POSIX,
LINUX
};
typedef Enum<ReadDirEnum> ReadDir;
namespace FUSE
{
int removemapping();
}
+35
View File
@@ -0,0 +1,35 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_setupmapping.hpp"
#include <sys/types.h>
#include <errno.h>
#include <cstdint>
ssize_t
FUSE::setupmapping(uint64_t *fh_,
uint64_t foffset_,
uint64_t len_,
uint64_t flags_,
uint64_t moffset_)
{
return -ENOSYS;
}
+31
View File
@@ -0,0 +1,31 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fuse.h"
namespace FUSE
{
ssize_t setupmapping(uint64_t *fh_,
uint64_t foffset_,
uint64_t len_,
uint64_t flags_,
uint64_t moffset_);
}
+28
View File
@@ -0,0 +1,28 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_syncfs.hpp"
#include <errno.h>
int
FUSE::syncfs()
{
return -ENOSYS;
}
+27
View File
@@ -0,0 +1,27 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fuse.h"
namespace FUSE
{
int syncfs();
}
+32
View File
@@ -0,0 +1,32 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "fuse_tmpfile.hpp"
#include "fuse_create.hpp"
#include <errno.h>
#include <stdio.h>
int
FUSE::tmpfile(const char *fusepath_,
mode_t mode_,
fuse_file_info_t *ffi_)
{
return -ENOSYS;
}
@@ -1,7 +1,7 @@
/*
ISC License
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2023, Antonio SJ Musumeci <trapexit@spawn.link>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -18,17 +18,15 @@
#pragma once
#include "branches.hpp"
#include "fuse.h"
#include <cstdint>
#include <sys/types.h>
namespace FUSE
{
int
readdir_posix(const Branches::CPtr &branches,
const char *dirname,
fuse_dirents_t *buf);
tmpfile(const char *fusepath,
mode_t mode,
fuse_file_info_t *ffi);
}
+253 -113
View File
@@ -67,6 +67,8 @@
#define GHC_OS_WIN32
#elif defined(__CYGWIN__)
#define GHC_OS_CYGWIN
#elif defined(__sun) && defined(__SVR4)
#define GHC_OS_SOLARIS
#elif defined(__svr4__)
#define GHC_OS_SYS5R4
#elif defined(BSD)
@@ -76,7 +78,8 @@
#include <wasi/api.h>
#elif defined(__QNX__)
#define GHC_OS_QNX
#define GHC_NO_DIRENT_D_TYPE
#elif defined(__HAIKU__)
#define GHC_OS_HAIKU
#else
#error "Operating system currently not supported!"
#endif
@@ -214,6 +217,7 @@
#include <compare>
#endif
#endif
#include <chrono>
#include <fstream>
#include <memory>
#include <stack>
@@ -253,6 +257,10 @@
#include <experimental/string_view>
#endif
#if !defined(GHC_OS_WINDOWS) && !defined(PATH_MAX)
#define PATH_MAX 4096
#endif
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp):
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -264,7 +272,7 @@
// configure LWG conformance ()
#define LWG_2682_BEHAVIOUR
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// LWG #2395 makes create_directory/create_directories not emit an error if there is a regular
// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular
// file with that name, it is superseded by P1164R1, so only activate if really needed
// #define LWG_2935_BEHAVIOUR
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -273,19 +281,19 @@
// * if this->has_root_directory() and !p.has_root_directory() return -1
// * if !this->has_root_directory() and p.has_root_directory() return -1
// * else result of element wise comparison of path iteration where first comparison is != 0 or 0
// if all comparisons are 0 (on Windows this implementation does case insensitive root_name()
// if all comparisons are 0 (on Windows this implementation does case-insensitive root_name()
// comparison)
#define LWG_2936_BEHAVIOUR
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2)
#define LWG_2937_BEHAVIOUR
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the windows
// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the Windows
// version defaults to std::wstring storage backend. Still all std::string will be interpreted
// as UTF-8 encoded. With this define you can enfoce the old behavior on Windows, using
// as UTF-8 encoded. With this define you can enforce the old behavior on Windows, using
// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This
// needs more conversions so it is (an was before v1.5) slower, bot might help keeping source
// homogeneous in a multi platform project.
// needs more conversions, so it is (and was before v1.5) slower, bot might help keeping source
// homogeneous in a multi-platform project.
// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found,
@@ -300,7 +308,7 @@
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch)
#define GHC_FILESYSTEM_VERSION 10509L
#define GHC_FILESYSTEM_VERSION 10514L
#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND))
#define GHC_WITH_EXCEPTIONS
@@ -423,10 +431,11 @@ public:
template <typename T>
using path_type_EcharT = typename std::enable_if<std::is_same<T, char>::value || std::is_same<T, char8_t>::value || std::is_same<T, char16_t>::value || std::is_same<T, char32_t>::value || std::is_same<T, wchar_t>::value, path>::type;
#else
using path_from_string = typename std::enable_if<_is_basic_string<T>::value || std::is_same<char const*, typename std::decay<T>::type>::value || std::is_same<char*, typename std::decay<T>::type>::value ||
using path_from_string =
typename std::enable_if<_is_basic_string<T>::value || std::is_same<char const*, typename std::decay<T>::type>::value || std::is_same<char*, typename std::decay<T>::type>::value ||
std::is_same<char16_t const*, typename std::decay<T>::type>::value || std::is_same<char16_t*, typename std::decay<T>::type>::value || std::is_same<char32_t const*, typename std::decay<T>::type>::value ||
std::is_same<char32_t*, typename std::decay<T>::type>::value || std::is_same<wchar_t const*, typename std::decay<T>::type>::value || std::is_same<wchar_t*, typename std::decay<T>::type>::value,
path>::type;
path>::type;
template <typename T>
using path_type_EcharT = typename std::enable_if<std::is_same<T, char>::value || std::is_same<T, char16_t>::value || std::is_same<T, char32_t>::value || std::is_same<T, wchar_t>::value, path>::type;
#endif
@@ -604,7 +613,7 @@ private:
friend bool detail::has_executable_extension(const path& p);
#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH
string_type::size_type _prefixLength{0};
#else // GHC_WIN_AUTO_PREFIX_LONG_PATH
#else // GHC_WIN_AUTO_PREFIX_LONG_PATH
static const string_type::size_type _prefixLength{0};
#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH
#else
@@ -799,6 +808,7 @@ public:
file_type type() const noexcept;
perms permissions() const noexcept;
friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); }
private:
file_type _type;
perms _perms;
@@ -1295,6 +1305,65 @@ GHC_FS_API std::error_code make_error_code(portable_error err);
GHC_FS_API std::error_code make_system_error(uint32_t err = 0);
#else
GHC_FS_API std::error_code make_system_error(int err = 0);
template <typename T, typename = int>
struct has_d_type : std::false_type{};
template <typename T>
struct has_d_type<T, decltype((void)T::d_type, 0)> : std::true_type {};
template <typename T>
GHC_INLINE file_type file_type_from_dirent_impl(const T&, std::false_type)
{
return file_type::none;
}
template <typename T>
GHC_INLINE file_type file_type_from_dirent_impl(const T& t, std::true_type)
{
switch (t.d_type) {
#ifdef DT_BLK
case DT_BLK:
return file_type::block;
#endif
#ifdef DT_CHR
case DT_CHR:
return file_type::character;
#endif
#ifdef DT_DIR
case DT_DIR:
return file_type::directory;
#endif
#ifdef DT_FIFO
case DT_FIFO:
return file_type::fifo;
#endif
#ifdef DT_LNK
case DT_LNK:
return file_type::symlink;
#endif
#ifdef DT_REG
case DT_REG:
return file_type::regular;
#endif
#ifdef DT_SOCK
case DT_SOCK:
return file_type::socket;
#endif
#ifdef DT_UNKNOWN
case DT_UNKNOWN:
return file_type::none;
#endif
default:
return file_type::unknown;
}
}
template <class T>
GHC_INLINE file_type file_type_from_dirent(const T& t)
{
return file_type_from_dirent_impl(t, has_d_type<T>{});
}
#endif
} // namespace detail
@@ -1696,7 +1765,7 @@ inline std::wstring toWChar(const charT* unicodeString)
return toWChar(std::basic_string<charT, std::char_traits<charT>>(unicodeString));
#endif
}
#endif // GHC_USE_WCHAR_T
#endif // GHC_USE_WCHAR_T
} // namespace detail
@@ -1815,16 +1884,16 @@ GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const pa
return true;
}
return false;
#else // __GNUC__
#else // __GNUC__
#ifdef GHC_USE_WCHAR_T
return 0 == ::_wcsicmp(str1, str2);
#else // GHC_USE_WCHAR_T
#else // GHC_USE_WCHAR_T
return 0 == ::_stricmp(str1, str2);
#endif // GHC_USE_WCHAR_T
#endif // __GNUC__
#else // GHC_OS_WINDOWS
#endif // GHC_USE_WCHAR_T
#endif // __GNUC__
#else // GHC_OS_WINDOWS
return 0 == ::strcasecmp(str1, str2);
#endif // GHC_OS_WINDOWS
#endif // GHC_OS_WINDOWS
}
GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2)
@@ -1888,10 +1957,15 @@ GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink,
#if defined(__GNUC__) && __GNUC__ >= 8
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__)
#pragma warning(push)
#pragma warning(disable : 4191)
#endif
static CreateSymbolicLinkW_fp api_call = reinterpret_cast<CreateSymbolicLinkW_fp>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW"));
#if defined(__GNUC__) && __GNUC__ >= 8
#pragma GCC diagnostic pop
#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__)
#pragma warning(pop)
#endif
if (api_call) {
if (api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 1 : 0) == 0) {
@@ -1912,10 +1986,15 @@ GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlin
#if defined(__GNUC__) && __GNUC__ >= 8
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__)
#pragma warning(push)
#pragma warning(disable : 4191)
#endif
static CreateHardLinkW_fp api_call = reinterpret_cast<CreateHardLinkW_fp>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW"));
#if defined(__GNUC__) && __GNUC__ >= 8
#pragma GCC diagnostic pop
#elif defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__)
#pragma warning(pop)
#endif
if (api_call) {
if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) {
@@ -2004,6 +2083,52 @@ GHC_INLINE file_status file_status_from_st_mode(T mode)
}
#ifdef GHC_OS_WINDOWS
class unique_handle
{
public:
typedef HANDLE element_type;
unique_handle() noexcept
: _handle(INVALID_HANDLE_VALUE)
{
}
explicit unique_handle(element_type h) noexcept
: _handle(h)
{
}
unique_handle(unique_handle&& u) noexcept
: _handle(u.release())
{
}
~unique_handle() { reset(); }
unique_handle& operator=(unique_handle&& u) noexcept
{
reset(u.release());
return *this;
}
element_type get() const noexcept { return _handle; }
explicit operator bool() const noexcept { return _handle != INVALID_HANDLE_VALUE; }
element_type release() noexcept
{
element_type tmp = _handle;
_handle = INVALID_HANDLE_VALUE;
return tmp;
}
void reset(element_type h = INVALID_HANDLE_VALUE) noexcept
{
element_type tmp = _handle;
_handle = h;
if (tmp != INVALID_HANDLE_VALUE) {
CloseHandle(tmp);
}
}
void swap(unique_handle& u) noexcept { std::swap(_handle, u._handle); }
private:
element_type _handle;
};
#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE
typedef struct _REPARSE_DATA_BUFFER
{
@@ -2040,15 +2165,21 @@ typedef struct _REPARSE_DATA_BUFFER
#endif
#endif
GHC_INLINE std::shared_ptr<REPARSE_DATA_BUFFER> getReparseData(const path& p, std::error_code& ec)
template <class T>
struct free_deleter
{
std::shared_ptr<void> file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
if (file.get() == INVALID_HANDLE_VALUE) {
void operator()(T* p) const { std::free(p); }
};
GHC_INLINE std::unique_ptr<REPARSE_DATA_BUFFER, free_deleter<REPARSE_DATA_BUFFER>> getReparseData(const path& p, std::error_code& ec)
{
unique_handle file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0));
if (!file) {
ec = detail::make_system_error();
return nullptr;
}
std::shared_ptr<REPARSE_DATA_BUFFER> reparseData((REPARSE_DATA_BUFFER*)std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE), std::free);
std::unique_ptr<REPARSE_DATA_BUFFER, free_deleter<REPARSE_DATA_BUFFER>> reparseData(reinterpret_cast<REPARSE_DATA_BUFFER*>(std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE)));
ULONG bufferUsed;
if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) {
return reparseData;
@@ -2085,7 +2216,7 @@ GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec)
}
case IO_REPARSE_TAG_MOUNT_POINT:
result = detail::getFullPathName(GHC_NATIVEWP(p), ec);
//result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
// result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
break;
default:
break;
@@ -2122,10 +2253,10 @@ GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft)
GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft)
{
LONGLONG ll;
ll = Int32x32To64(t, 10000000) + 116444736000000000;
ft.dwLowDateTime = static_cast<DWORD>(ll);
ft.dwHighDateTime = static_cast<DWORD>(ll >> 32);
ULARGE_INTEGER ull;
ull.QuadPart = static_cast<ULONGLONG>((t * 10000000LL) + 116444736000000000LL);
ft.dwLowDateTime = ull.LowPart;
ft.dwHighDateTime = ull.HighPart;
}
template <typename INFO>
@@ -2141,41 +2272,43 @@ GHC_INLINE uintmax_t hard_links_from_INFO<BY_HANDLE_FILE_INFORMATION>(const BY_H
}
template <typename INFO>
GHC_INLINE DWORD reparse_tag_from_INFO(const INFO*)
GHC_INLINE bool is_symlink_from_INFO(const path &p, const INFO* info, std::error_code& ec)
{
return 0;
if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
auto reparseData = detail::getReparseData(p, ec);
if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
return true;
}
}
return false;
}
template <>
GHC_INLINE DWORD reparse_tag_from_INFO(const WIN32_FIND_DATAW* info)
GHC_INLINE bool is_symlink_from_INFO(const path &, const WIN32_FIND_DATAW* info, std::error_code&)
{
return info->dwReserved0;
// dwReserved0 is undefined unless dwFileAttributes includes the
// FILE_ATTRIBUTE_REPARSE_POINT attribute according to microsoft
// documentation. In practice, dwReserved0 is not reset which
// causes it to report the incorrect symlink status.
// Note that microsoft documentation does not say whether there is
// a null value for dwReserved0, so we test for symlink directly
// instead of returning the tag which requires returning a null
// value for non-reparse-point files.
return (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && info->dwReserved0 == IO_REPARSE_TAG_SYMLINK;
}
template <typename INFO>
GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr)
{
file_type ft = file_type::unknown;
if (sizeof(INFO) == sizeof(WIN32_FIND_DATAW)) {
if (detail::reparse_tag_from_INFO(info) == IO_REPARSE_TAG_SYMLINK) {
ft = file_type::symlink;
}
if (is_symlink_from_INFO(p, info, ec)) {
ft = file_type::symlink;
}
else if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
ft = file_type::directory;
}
else {
if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
auto reparseData = detail::getReparseData(p, ec);
if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
ft = file_type::symlink;
}
}
}
if (ft == file_type::unknown) {
if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
ft = file_type::directory;
}
else {
ft = file_type::regular;
}
ft = file_type::regular;
}
perms prms = perms::owner_read | perms::group_read | perms::others_read;
if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
@@ -3056,7 +3189,8 @@ GHC_INLINE bool has_executable_extension(const path& p)
return false;
}
const path::value_type* ext = fn._path.c_str() + pos + 1;
if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) {
if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) ||
detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) {
return true;
}
}
@@ -3215,7 +3349,7 @@ GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const
, _root(p.has_root_directory() ? _first + static_cast<string_type::difference_type>(p._prefixLength + p.root_name_length()) : _last)
, _iter(pos)
{
if(pos != _last) {
if (pos != _last) {
updateCurrent();
}
}
@@ -3232,7 +3366,7 @@ GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(cons
// we can only sit on a slash if it is a network name or a root
if (i != _last && *i == preferred_separator) {
if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) {
// leading double slashes detected, treat this and the
// leadind double slashes detected, treat this and the
// following until a slash as one unit
i = std::find(++i, _last, preferred_separator);
}
@@ -3245,10 +3379,14 @@ GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(cons
}
}
else {
#ifdef GHC_OS_WINDOWS
if (fromStart && i != _last && *i == ':') {
++i;
}
else {
#else
{
#endif
i = std::find(i, _last, preferred_separator);
}
}
@@ -3295,10 +3433,10 @@ GHC_INLINE void path::iterator::updateCurrent()
GHC_INLINE path::iterator& path::iterator::operator++()
{
_iter = increment(_iter);
while (_iter != _last && // we didn't reach the end
_iter != _root && // this is not a root position
while (_iter != _last && // we didn't reach the end
_iter != _root && // this is not a root position
*_iter == preferred_separator && // we are on a separator
(_iter + 1) != _last // the slash is not the last char
(_iter + 1) != _last // the slash is not the last char
) {
++_iter;
}
@@ -3792,11 +3930,14 @@ GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options
ec = tecf;
return false;
}
if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) {
ec = tect ? tect : detail::make_error_code(detail::portable_error::exists);
return false;
}
if (exists(st)) {
if ((options & copy_options::skip_existing) == copy_options::skip_existing) {
return false;
}
if (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none) {
ec = tect ? tect : detail::make_error_code(detail::portable_error::exists);
return false;
}
if ((options & copy_options::update_existing) == copy_options::update_existing) {
auto from_time = last_write_time(from, ec);
if (ec) {
@@ -3836,15 +3977,33 @@ GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options
::close(in);
return false;
}
if (st.permissions() != sf.permissions()) {
if (::fchmod(out, static_cast<mode_t>(sf.permissions() & perms::all)) != 0) {
ec = detail::make_system_error();
::close(in);
::close(out);
return false;
}
}
ssize_t br, bw;
while ((br = ::read(in, buffer.data(), buffer.size())) > 0) {
while (true) {
do { br = ::read(in, buffer.data(), buffer.size()); } while(errno == EINTR);
if(!br) {
break;
}
if(br < 0) {
ec = detail::make_system_error();
::close(in);
::close(out);
return false;
}
ssize_t offset = 0;
do {
if ((bw = ::write(out, buffer.data() + offset, static_cast<size_t>(br))) > 0) {
br -= bw;
offset += bw;
}
else if (bw < 0) {
else if (bw < 0 && errno != EINTR) {
ec = detail::make_system_error();
::close(in);
::close(out);
@@ -4079,6 +4238,13 @@ GHC_INLINE path current_path(std::error_code& ec)
return path();
}
return path(std::wstring(buffer.get()), path::native_format);
#elif defined(__GLIBC__)
std::unique_ptr<char, decltype(&std::free)> buffer { ::getcwd(NULL, 0), std::free };
if (buffer == nullptr) {
ec = detail::make_system_error();
return path();
}
return path(buffer.get());
#else
size_t pathlen = static_cast<size_t>(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX)));
std::unique_ptr<char[]> buffer(new char[pathlen + 1]);
@@ -4152,10 +4318,10 @@ GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec)
{
ec.clear();
#ifdef GHC_OS_WINDOWS
std::shared_ptr<void> file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
detail::unique_handle file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0));
auto e1 = ::GetLastError();
std::shared_ptr<void> file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
if (file1.get() == INVALID_HANDLE_VALUE || file2.get() == INVALID_HANDLE_VALUE) {
detail::unique_handle file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0));
if (!file1 || !file2) {
#ifdef LWG_2937_BEHAVIOUR
ec = detail::make_system_error(e1 ? e1 : ::GetLastError());
#else
@@ -4245,9 +4411,9 @@ GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcep
ec.clear();
#ifdef GHC_OS_WINDOWS
uintmax_t result = static_cast<uintmax_t>(-1);
std::shared_ptr<void> file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle);
detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0));
BY_HANDLE_FILE_INFORMATION inf;
if (file.get() == INVALID_HANDLE_VALUE) {
if (!file) {
ec = detail::make_system_error();
}
else {
@@ -4476,7 +4642,7 @@ GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::err
ec.clear();
auto d = new_time.time_since_epoch();
#ifdef GHC_OS_WINDOWS
std::shared_ptr<void> file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle);
detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL));
FILETIME ft;
auto tt = std::chrono::duration_cast<std::chrono::microseconds>(d).count() * 10 + 116444736000000000;
ft.dwLowDateTime = static_cast<DWORD>(tt);
@@ -4484,9 +4650,9 @@ GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::err
if (!::SetFileTime(file.get(), 0, 0, &ft)) {
ec = detail::make_system_error();
}
#elif defined(GHC_OS_MACOS)
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300
#elif defined(GHC_OS_MACOS) && \
(__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_13) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_11_0) || \
(__TV_OS_VERSION_MIN_REQUIRED < __TVOS_11_0) || (__WATCH_OS_VERSION_MIN_REQUIRED < __WATCHOS_4_0)
struct ::stat fs;
if (::stat(p.c_str(), &fs) == 0) {
struct ::timeval tv[2];
@@ -4500,18 +4666,6 @@ GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::err
}
ec = detail::make_system_error();
return;
#else
struct ::timespec times[2];
times[0].tv_sec = 0;
times[0].tv_nsec = UTIME_OMIT;
times[1].tv_sec = std::chrono::duration_cast<std::chrono::seconds>(d).count();
times[1].tv_nsec = 0; // std::chrono::duration_cast<std::chrono::nanoseconds>(d).count() % 1000000000;
if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
ec = detail::make_system_error();
}
return;
#endif
#endif
#else
#ifndef UTIME_OMIT
#define UTIME_OMIT ((1l << 30) - 2l)
@@ -4524,7 +4678,7 @@ GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::err
#if defined(__ANDROID_API__) && __ANDROID_API__ < 12
if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
#else
if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
if (::utimensat((int)AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) {
#endif
ec = detail::make_system_error();
}
@@ -4687,9 +4841,9 @@ GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept
}
ec = detail::make_system_error(error);
}
else if(attr & FILE_ATTRIBUTE_READONLY) {
else if (attr & FILE_ATTRIBUTE_READONLY) {
auto new_attr = attr & ~static_cast<DWORD>(FILE_ATTRIBUTE_READONLY);
if(!SetFileAttributesW(cstr, new_attr)) {
if (!SetFileAttributesW(cstr, new_attr)) {
auto error = ::GetLastError();
ec = detail::make_system_error(error);
}
@@ -4739,7 +4893,7 @@ GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept
return static_cast<uintmax_t>(-1);
}
std::error_code tec;
auto fs = status(p, tec);
auto fs = symlink_status(p, tec);
if (exists(fs) && is_directory(fs)) {
for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) {
if (ec && !detail::is_not_found_error(ec)) {
@@ -4830,8 +4984,8 @@ GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec)
#endif
return;
}
std::shared_ptr<void> file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle);
if (file.get() == INVALID_HANDLE_VALUE) {
detail::unique_handle file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL));
if (!file) {
ec = detail::make_system_error();
}
else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) {
@@ -5139,7 +5293,7 @@ GHC_INLINE void directory_entry::refresh()
{
std::error_code ec;
refresh(ec);
if (ec) {
if (ec && (_status.type() == file_type::none || _symlink_status.type() != file_type::symlink)) {
throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec);
}
}
@@ -5174,7 +5328,7 @@ GHC_INLINE file_type directory_entry::status_file_type() const
GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept
{
if(_status.type() != file_type::none) {
if (_status.type() != file_type::none) {
ec.clear();
return _status.type();
}
@@ -5288,7 +5442,7 @@ GHC_INLINE bool directory_entry::is_symlink() const
GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept
{
if(_symlink_status.type() != file_type::none) {
if (_symlink_status.type() != file_type::none) {
ec.clear();
return _symlink_status.type() == file_type::symlink;
}
@@ -5550,7 +5704,7 @@ public:
, _entry(nullptr)
{
if (!path.empty()) {
_dir = ::opendir(path.native().c_str());
do { _dir = ::opendir(path.native().c_str()); } while(errno == EINTR);
if (!_dir) {
auto error = errno;
_base = filesystem::path();
@@ -5577,7 +5731,7 @@ public:
do {
skip = false;
errno = 0;
_entry = ::readdir(_dir);
do { _entry = ::readdir(_dir); } while(errno == EINTR);
if (_entry) {
_dir_entry._path = _base;
_dir_entry._path.append_name(_entry->d_name);
@@ -5591,7 +5745,7 @@ public:
::closedir(_dir);
_dir = nullptr;
_dir_entry._path.clear();
if (errno) {
if (errno && errno != EINTR) {
ec = detail::make_system_error();
}
break;
@@ -5602,30 +5756,16 @@ public:
void copyToDirEntry()
{
#ifdef GHC_NO_DIRENT_D_TYPE
_dir_entry._symlink_status = file_status();
_dir_entry._status = file_status();
#else
_dir_entry._symlink_status.permissions(perms::unknown);
switch(_entry->d_type) {
case DT_BLK: _dir_entry._symlink_status.type(file_type::block); break;
case DT_CHR: _dir_entry._symlink_status.type(file_type::character); break;
case DT_DIR: _dir_entry._symlink_status.type(file_type::directory); break;
case DT_FIFO: _dir_entry._symlink_status.type(file_type::fifo); break;
case DT_LNK: _dir_entry._symlink_status.type(file_type::symlink); break;
case DT_REG: _dir_entry._symlink_status.type(file_type::regular); break;
case DT_SOCK: _dir_entry._symlink_status.type(file_type::socket); break;
case DT_UNKNOWN: _dir_entry._symlink_status.type(file_type::none); break;
default: _dir_entry._symlink_status.type(file_type::unknown); break;
}
if (_entry->d_type != DT_LNK) {
auto ft = detail::file_type_from_dirent(*_entry);
_dir_entry._symlink_status.type(ft);
if (ft != file_type::symlink) {
_dir_entry._status = _dir_entry._symlink_status;
}
else {
_dir_entry._status.type(file_type::none);
_dir_entry._status.permissions(perms::unknown);
}
#endif
_dir_entry._file_size = static_cast<uintmax_t>(-1);
_dir_entry._hard_link_count = static_cast<uintmax_t>(-1);
_dir_entry._last_write_time = 0;
@@ -5856,10 +5996,10 @@ GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment
{
bool isSymLink = (*this)->is_symlink(ec);
bool isDir = !ec && (*this)->is_directory(ec);
if(isSymLink && detail::is_not_found_error(ec)) {
if (isSymLink && detail::is_not_found_error(ec)) {
ec.clear();
}
if(!ec) {
if (!ec) {
if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) {
_impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec));
}
+64 -24
View File
@@ -14,6 +14,8 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "rnd.hpp"
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
@@ -26,51 +28,74 @@
# include <sys/param.h>
#endif
#include <cassert>
#include <cstdlib>
#include <algorithm>
#include <unordered_map>
#include <mutex>
#include "gidcache.hpp"
std::mutex g_REGISTERED_CACHES_MUTEX;
std::unordered_map<pthread_t,GIDCache*> g_REGISTERED_CACHES;
inline
bool
gid_t_rec::operator<(const struct gid_t_rec &b) const
GIDRecord::operator<(const struct GIDRecord &b) const
{
return uid < b.uid;
}
inline
gid_t_rec *
gid_t_cache::begin(void)
GIDCache::GIDCache()
: invalidate(false),
size(0),
recs()
{
return recs;
std::lock_guard<std::mutex> guard(g_REGISTERED_CACHES_MUTEX);
bool inserted;
pthread_t pthid;
pthid = pthread_self();
inserted = g_REGISTERED_CACHES.emplace(pthid,this).second;
assert(inserted == true);
}
inline
gid_t_rec *
gid_t_cache::end(void)
GIDRecord *
GIDCache::begin(void)
{
return recs + size;
return &recs[0];
}
inline
gid_t_rec *
gid_t_cache::allocrec(void)
GIDRecord *
GIDCache::end(void)
{
return &recs[size];
}
inline
GIDRecord *
GIDCache::allocrec(void)
{
if(size == MAXRECS)
return &recs[rand() % MAXRECS];
return &recs[RND::rand64(MAXRECS)];
else
return &recs[size++];
}
inline
gid_t_rec *
gid_t_cache::lower_bound(gid_t_rec *begin,
gid_t_rec *end,
const uid_t uid)
GIDRecord *
GIDCache::lower_bound(GIDRecord *begin,
GIDRecord *end,
const uid_t uid)
{
int step;
int count;
gid_t_rec *iter;
GIDRecord *iter;
count = std::distance(begin,end);
while(count > 0)
@@ -106,15 +131,15 @@ _getgrouplist(const char *user,
#endif
}
gid_t_rec *
gid_t_cache::cache(const uid_t uid,
const gid_t gid)
GIDRecord *
GIDCache::cache(const uid_t uid,
const gid_t gid)
{
int rv;
char buf[4096];
struct passwd pwd;
struct passwd *pwdrv;
gid_t_rec *rec;
GIDRecord *rec;
rec = allocrec();
@@ -139,7 +164,7 @@ gid_t_cache::cache(const uid_t uid,
static
inline
int
setgroups(const gid_t_rec *rec)
setgroups(const GIDRecord *rec)
{
#if defined __linux__ and UGID_USE_RWLOCK == 0
# if defined SYS_setgroups32
@@ -153,11 +178,17 @@ setgroups(const gid_t_rec *rec)
}
int
gid_t_cache::initgroups(const uid_t uid,
const gid_t gid)
GIDCache::initgroups(const uid_t uid,
const gid_t gid)
{
int rv;
gid_t_rec *rec;
GIDRecord *rec;
if(invalidate)
{
size = 0;
invalidate = false;
}
rec = lower_bound(begin(),end(),uid);
if(rec == end() || rec->uid != uid)
@@ -173,3 +204,12 @@ gid_t_cache::initgroups(const uid_t uid,
return rv;
}
void
GIDCache::invalidate_all_caches()
{
std::lock_guard<std::mutex> guard(g_REGISTERED_CACHES_MUTEX);
for(auto &p : g_REGISTERED_CACHES)
p.second->invalidate = true;
}
+31 -13
View File
@@ -19,37 +19,55 @@
#include <sys/types.h>
#include <unistd.h>
#include <array>
#define MAXGIDS 32
#define MAXRECS 256
struct gid_t_rec
// GIDCache is a global, per thread cache of uid to gid + supplemental
// groups mapping for use when threads change credentials. This is
// needed due to the high cost of querying such information. The cache
// instance should always be thread local and live the lifetime of the
// app. The constructor will register the instance so they can each be
// told to invalidate the cache on demand. A second instance on the
// same thread will cause an assert to be triggered.
struct GIDRecord
{
uid_t uid;
int size;
gid_t gids[MAXGIDS];
bool
operator<(const struct gid_t_rec &b) const;
operator<(const struct GIDRecord &b) const;
};
struct gid_t_cache
struct GIDCache
{
public:
size_t size;
gid_t_rec recs[MAXRECS];
GIDCache();
public:
bool invalidate;
size_t size;
std::array<GIDRecord,MAXRECS> recs;
private:
gid_t_rec * begin(void);
gid_t_rec * end(void);
gid_t_rec * allocrec(void);
gid_t_rec * lower_bound(gid_t_rec *begin,
gid_t_rec *end,
const uid_t uid);
gid_t_rec * cache(const uid_t uid,
const gid_t gid);
GIDRecord *begin(void);
GIDRecord *end(void);
GIDRecord *allocrec(void);
GIDRecord *lower_bound(GIDRecord *begin,
GIDRecord *end,
const uid_t uid);
GIDRecord *cache(const uid_t uid,
const gid_t gid);
public:
int
initgroups(const uid_t uid,
const gid_t gid);
public:
static void invalidate_all_caches();
};
+35 -3
View File
@@ -25,6 +25,7 @@
#include "procfs_get_name.hpp"
#include "resources.hpp"
#include "strvec.hpp"
#include "gidcache.hpp"
#include "fuse_access.hpp"
#include "fuse_bmap.hpp"
@@ -63,12 +64,16 @@
#include "fuse_readlink.hpp"
#include "fuse_release.hpp"
#include "fuse_releasedir.hpp"
#include "fuse_removemapping.hpp"
#include "fuse_removexattr.hpp"
#include "fuse_rename.hpp"
#include "fuse_rmdir.hpp"
#include "fuse_setupmapping.hpp"
#include "fuse_setxattr.hpp"
#include "fuse_statfs.hpp"
#include "fuse_symlink.hpp"
#include "fuse_syncfs.hpp"
#include "fuse_tmpfile.hpp"
#include "fuse_truncate.hpp"
#include "fuse_unlink.hpp"
#include "fuse_utimens.hpp"
@@ -127,12 +132,16 @@ namespace l
ops_.readlink = FUSE::readlink;
ops_.release = FUSE::release;
ops_.releasedir = FUSE::releasedir;
ops_.removemapping = FUSE::removemapping;
ops_.removexattr = FUSE::removexattr;
ops_.rename = FUSE::rename;
ops_.rmdir = FUSE::rmdir;
ops_.setupmapping = FUSE::setupmapping;
ops_.setxattr = FUSE::setxattr;
ops_.statfs = FUSE::statfs;
ops_.symlink = FUSE::symlink;
ops_.syncfs = FUSE::syncfs;
ops_.tmpfile = FUSE::tmpfile;
ops_.truncate = FUSE::truncate;
ops_.unlink = FUSE::unlink;
ops_.utimens = FUSE::utimens;
@@ -210,6 +219,7 @@ namespace l
{
syslog_info("Received SIGUSR2 - triggering thorough gc");
fuse_gc();
GIDCache::invalidate_all_caches();
}
static
@@ -220,10 +230,26 @@ namespace l
std::signal(SIGUSR2,l::usr2_signal_handler);
}
static
void
warn_if_not_root()
{
uid_t uid;
uid = geteuid();
if(uid == 0)
return;
char const *s = "mergerfs is not running as root and may not work correctly\n";
fprintf(stderr,"warning: %s",s);
syslog_warning(s);
}
int
main(const int argc_,
char **argv_)
{
int rv;
Config::Read cfg;
Config::ErrVec errs;
fuse_args args;
@@ -231,6 +257,8 @@ namespace l
syslog_open();
l::warn_if_not_root();
memset(&ops,0,sizeof(fuse_operations));
args.argc = argc_;
@@ -256,9 +284,13 @@ namespace l
procfs::init();
return fuse_main(args.argc,
args.argv,
&ops);
rv = fuse_main(args.argc,
args.argv,
&ops);
syslog_info("exiting main loop with return code %d",rv);
return rv;
}
}
+31 -11
View File
@@ -26,9 +26,11 @@
#include "num.hpp"
#include "policy.hpp"
#include "str.hpp"
#include "syslog.hpp"
#include "version.hpp"
#include "fuse.h"
#include "fuse_config.hpp"
#include <fstream>
#include <iomanip>
@@ -77,13 +79,12 @@ set_kv_option(const std::string &key_,
static
void
set_fuse_threads(Config::Write &cfg_,
fuse_args *args_)
set_fuse_threads(Config::Write &cfg_)
{
set_kv_option("read-thread-count",cfg_->fuse_read_thread_count.to_string(),args_);
set_kv_option("process-thread-count",cfg_->fuse_process_thread_count.to_string(),args_);
set_kv_option("process-thread-queue-depth",cfg_->fuse_process_thread_queue_depth.to_string(),args_);
set_kv_option("pin-threads",cfg_->fuse_pin_threads.to_string(),args_);
fuse_config_set_read_thread_count(cfg_->fuse_read_thread_count);
fuse_config_set_process_thread_count(cfg_->fuse_process_thread_count);
fuse_config_set_process_thread_queue_depth(cfg_->fuse_process_thread_queue_depth);
fuse_config_set_pin_threads(cfg_->fuse_pin_threads);
}
static
@@ -116,6 +117,10 @@ void
set_default_options(fuse_args *args_)
{
set_option("default_permissions",args_);
if(geteuid() == 0)
set_option("allow_other",args_);
else
syslog_notice("not auto setting allow_other since not running as root");
}
static
@@ -124,7 +129,6 @@ should_ignore(const std::string &key_)
{
static const std::set<std::string> IGNORED_KEYS =
{
"allow_other",
"atomic_o_trunc",
"big_writes",
"cache.open",
@@ -380,9 +384,25 @@ option_processor(void *data_,
exit(0);
case MERGERFS_OPT_VERSION:
std::cout << "mergerfs version: "
<< (MERGERFS_VERSION[0] ? MERGERFS_VERSION : "unknown")
<< std::endl;
fmt::print("mergerfs v{}\n\n"
"https://github.com/trapexit/mergerfs\n"
"https://github.com/trapexit/support\n\n"
"ISC License (ISC)\n\n"
"Copyright 2023, Antonio SJ Musumeci <trapexit@spawn.link>\n\n"
"Permission to use, copy, modify, and/or distribute this software for\n"
"any purpose with or without fee is hereby granted, provided that the\n"
"above copyright notice and this permission notice appear in all\n"
"copies.\n\n"
"THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\n"
"WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED\n"
"WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE\n"
"AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL\n"
"DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR\n"
"PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\n"
"TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n"
"PERFORMANCE OF THIS SOFTWARE.\n\n"
,
(MERGERFS_VERSION[0] ? MERGERFS_VERSION : "unknown"));
exit(0);
default:
@@ -447,7 +467,7 @@ namespace options
set_default_options(args_);
set_fsname(cfg,args_);
set_subtype(args_);
set_fuse_threads(cfg,args_);
set_fuse_threads(cfg);
cfg->finish_initializing();
}
+4 -2
View File
@@ -26,9 +26,11 @@
static uint64_t G_SEED;
static RND G_RND;
RND::RND()
__attribute__((constructor))
static
void
_constructor()
{
struct timeval tv;
+369
View File
@@ -0,0 +1,369 @@
// _____ _____ _ _____
// / ____| / ____| | | / ____|_ _
// | (___ ___ ___ _ __ ___ | | __ _ _ __ _ _ __ __| | | | _| |_ _| |_
// \___ \ / __/ _ \| '_ \ / _ \ | | |_ | | | |/ _` | '__/ _` | | | |_ _|_ _|
// ____) | (_| (_) | |_) | __/ | |__| | |_| | (_| | | | (_| | | |____|_| |_|
// |_____/ \___\___/| .__/ \___| \_____|\__,_|\__,_|_| \__,_| \_____|
// | | https://github.com/Neargye/scope_guard
// |_| version 0.9.1
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
// SPDX-License-Identifier: MIT
// Copyright (c) 2018 - 2021 Daniil Goncharov <neargye@gmail.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef NEARGYE_SCOPE_GUARD_HPP
#define NEARGYE_SCOPE_GUARD_HPP
#define SCOPE_GUARD_VERSION_MAJOR 0
#define SCOPE_GUARD_VERSION_MINOR 9
#define SCOPE_GUARD_VERSION_PATCH 1
#include <type_traits>
#if (defined(_MSC_VER) && _MSC_VER >= 1900) || ((defined(__clang__) || defined(__GNUC__)) && __cplusplus >= 201700L)
#include <exception>
#endif
// scope_guard throwable settings:
// SCOPE_GUARD_NO_THROW_CONSTRUCTIBLE requires nothrow constructible action.
// SCOPE_GUARD_MAY_THROW_ACTION action may throw exceptions.
// SCOPE_GUARD_NO_THROW_ACTION requires noexcept action.
// SCOPE_GUARD_SUPPRESS_THROW_ACTION exceptions during action will be suppressed.
// SCOPE_GUARD_CATCH_HANDLER exceptions handler. If SCOPE_GUARD_SUPPRESS_THROW_ACTIONS is not defined, it will do nothing.
#if !defined(SCOPE_GUARD_MAY_THROW_ACTION) && !defined(SCOPE_GUARD_NO_THROW_ACTION) && !defined(SCOPE_GUARD_SUPPRESS_THROW_ACTION)
# define SCOPE_GUARD_MAY_THROW_ACTION
#elif (defined(SCOPE_GUARD_MAY_THROW_ACTION) + defined(SCOPE_GUARD_NO_THROW_ACTION) + defined(SCOPE_GUARD_SUPPRESS_THROW_ACTION)) > 1
# error Only one of SCOPE_GUARD_MAY_THROW_ACTION and SCOPE_GUARD_NO_THROW_ACTION and SCOPE_GUARD_SUPPRESS_THROW_ACTION may be defined.
#endif
#if !defined(SCOPE_GUARD_CATCH_HANDLER)
# define SCOPE_GUARD_CATCH_HANDLER /* Suppress exception.*/
#endif
namespace scope_guard {
namespace detail {
#if defined(SCOPE_GUARD_SUPPRESS_THROW_ACTION) && (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS))
# define NEARGYE_NOEXCEPT(...) noexcept
# define NEARGYE_TRY try {
# define NEARGYE_CATCH } catch (...) { SCOPE_GUARD_CATCH_HANDLER }
#else
# define NEARGYE_NOEXCEPT(...) noexcept(__VA_ARGS__)
# define NEARGYE_TRY
# define NEARGYE_CATCH
#endif
#define NEARGYE_MOV(...) static_cast<typename std::remove_reference<decltype(__VA_ARGS__)>::type&&>(__VA_ARGS__)
#define NEARGYE_FWD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
// NEARGYE_NODISCARD encourages the compiler to issue a warning if the return value is discarded.
#if !defined(NEARGYE_NODISCARD)
# if defined(__clang__)
# if (__clang_major__ * 10 + __clang_minor__) >= 39 && __cplusplus >= 201703L
# define NEARGYE_NODISCARD [[nodiscard]]
# else
# define NEARGYE_NODISCARD __attribute__((__warn_unused_result__))
# endif
# elif defined(__GNUC__)
# if __GNUC__ >= 7 && __cplusplus >= 201703L
# define NEARGYE_NODISCARD [[nodiscard]]
# else
# define NEARGYE_NODISCARD __attribute__((__warn_unused_result__))
# endif
# elif defined(_MSC_VER)
# if _MSC_VER >= 1911 && defined(_MSVC_LANG) && _MSVC_LANG >= 201703L
# define NEARGYE_NODISCARD [[nodiscard]]
# elif defined(_Check_return_)
# define NEARGYE_NODISCARD _Check_return_
# else
# define NEARGYE_NODISCARD
# endif
# else
# define NEARGYE_NODISCARD
# endif
#endif
#if defined(_MSC_VER) && _MSC_VER < 1900
inline int uncaught_exceptions() noexcept {
return *(reinterpret_cast<int*>(static_cast<char*>(static_cast<void*>(_getptd())) + (sizeof(void*) == 8 ? 0x100 : 0x90)));
}
#elif (defined(__clang__) || defined(__GNUC__)) && __cplusplus < 201700L
struct __cxa_eh_globals;
extern "C" __cxa_eh_globals* __cxa_get_globals() noexcept;
inline int uncaught_exceptions() noexcept {
return static_cast<int>(*(reinterpret_cast<unsigned int*>(static_cast<char*>(static_cast<void*>(__cxa_get_globals())) + sizeof(void*))));
}
#else
inline int uncaught_exceptions() noexcept {
return std::uncaught_exceptions();
}
#endif
class on_exit_policy {
bool execute_;
public:
explicit on_exit_policy(bool execute) noexcept : execute_{execute} {}
void dismiss() noexcept {
execute_ = false;
}
bool should_execute() const noexcept {
return execute_;
}
};
class on_fail_policy {
int ec_;
public:
explicit on_fail_policy(bool execute) noexcept : ec_{execute ? uncaught_exceptions() : -1} {}
void dismiss() noexcept {
ec_ = -1;
}
bool should_execute() const noexcept {
return ec_ != -1 && ec_ < uncaught_exceptions();
}
};
class on_success_policy {
int ec_;
public:
explicit on_success_policy(bool execute) noexcept : ec_{execute ? uncaught_exceptions() : -1} {}
void dismiss() noexcept {
ec_ = -1;
}
bool should_execute() const noexcept {
return ec_ != -1 && ec_ >= uncaught_exceptions();
}
};
template <typename T, typename = void>
struct is_noarg_returns_void_action
: std::false_type {};
template <typename T>
struct is_noarg_returns_void_action<T, decltype((std::declval<T>())())>
: std::true_type {};
template <typename T, bool = is_noarg_returns_void_action<T>::value>
struct is_nothrow_invocable_action
: std::false_type {};
template <typename T>
struct is_nothrow_invocable_action<T, true>
: std::integral_constant<bool, noexcept((std::declval<T>())())> {};
template <typename F, typename P>
class scope_guard {
using A = typename std::decay<F>::type;
static_assert(is_noarg_returns_void_action<A>::value,
"scope_guard requires no-argument action, that returns void.");
static_assert(std::is_same<P, on_exit_policy>::value || std::is_same<P, on_fail_policy>::value || std::is_same<P, on_success_policy>::value,
"scope_guard requires on_exit_policy, on_fail_policy or on_success_policy.");
#if defined(SCOPE_GUARD_NO_THROW_ACTION)
static_assert(is_nothrow_invocable_action<A>::value,
"scope_guard requires noexcept invocable action.");
#endif
#if defined(SCOPE_GUARD_NO_THROW_CONSTRUCTIBLE)
static_assert(std::is_nothrow_move_constructible<A>::value,
"scope_guard requires nothrow constructible action.");
#endif
P policy_;
A action_;
void* operator new(std::size_t) = delete;
void operator delete(void*) = delete;
public:
scope_guard() = delete;
scope_guard(const scope_guard&) = delete;
scope_guard& operator=(const scope_guard&) = delete;
scope_guard& operator=(scope_guard&&) = delete;
scope_guard(scope_guard&& other) noexcept(std::is_nothrow_move_constructible<A>::value)
: policy_{false},
action_{NEARGYE_MOV(other.action_)} {
policy_ = NEARGYE_MOV(other.policy_);
other.policy_.dismiss();
}
scope_guard(const A& action) = delete;
scope_guard(A& action) = delete;
explicit scope_guard(A&& action) noexcept(std::is_nothrow_move_constructible<A>::value)
: policy_{true},
action_{NEARGYE_MOV(action)} {}
void dismiss() noexcept {
policy_.dismiss();
}
~scope_guard() NEARGYE_NOEXCEPT(is_nothrow_invocable_action<A>::value) {
if (policy_.should_execute()) {
NEARGYE_TRY
action_();
NEARGYE_CATCH
}
}
};
template <typename F>
using scope_exit = scope_guard<F, on_exit_policy>;
template <typename F, typename std::enable_if<is_noarg_returns_void_action<F>::value, int>::type = 0>
NEARGYE_NODISCARD scope_exit<F> make_scope_exit(F&& action) noexcept(noexcept(scope_exit<F>{NEARGYE_FWD(action)})) {
return scope_exit<F>{NEARGYE_FWD(action)};
}
template <typename F>
using scope_fail = scope_guard<F, on_fail_policy>;
template <typename F, typename std::enable_if<is_noarg_returns_void_action<F>::value, int>::type = 0>
NEARGYE_NODISCARD scope_fail<F> make_scope_fail(F&& action) noexcept(noexcept(scope_fail<F>{NEARGYE_FWD(action)})) {
return scope_fail<F>{NEARGYE_FWD(action)};
}
template <typename F>
using scope_success = scope_guard<F, on_success_policy>;
template <typename F, typename std::enable_if<is_noarg_returns_void_action<F>::value, int>::type = 0>
NEARGYE_NODISCARD scope_success<F> make_scope_success(F&& action) noexcept(noexcept(scope_success<F>{NEARGYE_FWD(action)})) {
return scope_success<F>{NEARGYE_FWD(action)};
}
struct scope_exit_tag {};
template <typename F, typename std::enable_if<is_noarg_returns_void_action<F>::value, int>::type = 0>
scope_exit<F> operator<<(scope_exit_tag, F&& action) noexcept(noexcept(scope_exit<F>{NEARGYE_FWD(action)})) {
return scope_exit<F>{NEARGYE_FWD(action)};
}
struct scope_fail_tag {};
template <typename F, typename std::enable_if<is_noarg_returns_void_action<F>::value, int>::type = 0>
scope_fail<F> operator<<(scope_fail_tag, F&& action) noexcept(noexcept(scope_fail<F>{NEARGYE_FWD(action)})) {
return scope_fail<F>{NEARGYE_FWD(action)};
}
struct scope_success_tag {};
template <typename F, typename std::enable_if<is_noarg_returns_void_action<F>::value, int>::type = 0>
scope_success<F> operator<<(scope_success_tag, F&& action) noexcept(noexcept(scope_success<F>{NEARGYE_FWD(action)})) {
return scope_success<F>{NEARGYE_FWD(action)};
}
#undef NEARGYE_MOV
#undef NEARGYE_FWD
#undef NEARGYE_NOEXCEPT
#undef NEARGYE_TRY
#undef NEARGYE_CATCH
#undef NEARGYE_NODISCARD
} // namespace scope_guard::detail
using detail::make_scope_exit;
using detail::make_scope_fail;
using detail::make_scope_success;
} // namespace scope_guard
// NEARGYE_MAYBE_UNUSED suppresses compiler warnings on unused entities, if any.
#if !defined(NEARGYE_MAYBE_UNUSED)
# if defined(__clang__)
# if (__clang_major__ * 10 + __clang_minor__) >= 39 && __cplusplus >= 201703L
# define NEARGYE_MAYBE_UNUSED [[maybe_unused]]
# else
# define NEARGYE_MAYBE_UNUSED __attribute__((__unused__))
# endif
# elif defined(__GNUC__)
# if __GNUC__ >= 7 && __cplusplus >= 201703L
# define NEARGYE_MAYBE_UNUSED [[maybe_unused]]
# else
# define NEARGYE_MAYBE_UNUSED __attribute__((__unused__))
# endif
# elif defined(_MSC_VER)
# if _MSC_VER >= 1911 && defined(_MSVC_LANG) && _MSVC_LANG >= 201703L
# define NEARGYE_MAYBE_UNUSED [[maybe_unused]]
# else
# define NEARGYE_MAYBE_UNUSED __pragma(warning(suppress : 4100 4101 4189))
# endif
# else
# define NEARGYE_MAYBE_UNUSED
# endif
#endif
#if !defined(NEARGYE_STR_CONCAT)
# define NEARGYE_STR_CONCAT_(s1, s2) s1##s2
# define NEARGYE_STR_CONCAT(s1, s2) NEARGYE_STR_CONCAT_(s1, s2)
#endif
#if !defined(NEARGYE_COUNTER)
# if defined(__COUNTER__)
# define NEARGYE_COUNTER __COUNTER__
# elif defined(__LINE__)
# define NEARGYE_COUNTER __LINE__
# endif
#endif
#if defined(SCOPE_GUARD_NO_THROW_ACTION)
# define NEARGYE_MAKE_SCOPE_GUARD_ACTION [&]() noexcept -> void
#else
# define NEARGYE_MAKE_SCOPE_GUARD_ACTION [&]() -> void
#endif
#define NEARGYE_MAKE_SCOPE_EXIT ::scope_guard::detail::scope_exit_tag{} << NEARGYE_MAKE_SCOPE_GUARD_ACTION
#define NEARGYE_MAKE_SCOPE_FAIL ::scope_guard::detail::scope_fail_tag{} << NEARGYE_MAKE_SCOPE_GUARD_ACTION
#define NEARGYE_MAKE_SCOPE_SUCCESS ::scope_guard::detail::scope_success_tag{} << NEARGYE_MAKE_SCOPE_GUARD_ACTION
#define NEARGYE_SCOPE_GUARD_WITH_(g, i) for (int i = 1; i--; g)
#define NEARGYE_SCOPE_GUARD_WITH(g) NEARGYE_SCOPE_GUARD_WITH_(g, NEARGYE_STR_CONCAT(NEARGYE_INTERNAL_OBJECT_, NEARGYE_COUNTER))
// SCOPE_EXIT executing action on scope exit.
#define MAKE_SCOPE_EXIT(name) auto name = NEARGYE_MAKE_SCOPE_EXIT
#define SCOPE_EXIT NEARGYE_MAYBE_UNUSED const MAKE_SCOPE_EXIT(NEARGYE_STR_CONCAT(NEARGYE_SCOPE_EXIT_, NEARGYE_COUNTER))
#define WITH_SCOPE_EXIT(guard) NEARGYE_SCOPE_GUARD_WITH(NEARGYE_MAKE_SCOPE_EXIT{ guard })
// SCOPE_FAIL executing action on scope exit when an exception has been thrown before scope exit.
#define MAKE_SCOPE_FAIL(name) auto name = NEARGYE_MAKE_SCOPE_FAIL
#define SCOPE_FAIL NEARGYE_MAYBE_UNUSED const MAKE_SCOPE_FAIL(NEARGYE_STR_CONCAT(NEARGYE_SCOPE_FAIL_, NEARGYE_COUNTER))
#define WITH_SCOPE_FAIL(guard) NEARGYE_SCOPE_GUARD_WITH(NEARGYE_MAKE_SCOPE_FAIL{ guard })
// SCOPE_SUCCESS executing action on scope exit when no exceptions have been thrown before scope exit.
#define MAKE_SCOPE_SUCCESS(name) auto name = NEARGYE_MAKE_SCOPE_SUCCESS
#define SCOPE_SUCCESS NEARGYE_MAYBE_UNUSED const MAKE_SCOPE_SUCCESS(NEARGYE_STR_CONCAT(NEARGYE_SCOPE_SUCCESS_, NEARGYE_COUNTER))
#define WITH_SCOPE_SUCCESS(guard) NEARGYE_SCOPE_GUARD_WITH(NEARGYE_MAKE_SCOPE_SUCCESS{ guard })
// DEFER executing action on scope exit.
#define MAKE_DEFER(name) MAKE_SCOPE_EXIT(name)
#define DEFER SCOPE_EXIT
#define WITH_DEFER(guard) WITH_SCOPE_EXIT(guard)
#endif // NEARGYE_SCOPE_GUARD_HPP
+11
View File
@@ -56,6 +56,17 @@ syslog_log(const int priority_,
va_end(valist);
}
void
syslog_debug(const char *format_,
...)
{
va_list valist;
va_start(valist,format_);
syslog_log(LOG_DEBUG,format_,valist);
va_end(valist);
}
void
syslog_info(const char *format_,
...)
+1
View File
@@ -23,6 +23,7 @@
void syslog_open();
void syslog_log(const int priority, const char *format, ...);
void syslog_debug(const char *format, ...);
void syslog_info(const char *format, ...);
void syslog_notice(const char *format, ...);
void syslog_warning(const char *format, ...);
+1 -1
View File
@@ -28,7 +28,7 @@ namespace ugid
initgroups(const uid_t uid_,
const gid_t gid_)
{
static __thread gid_t_cache cache = {0};
static thread_local GIDCache cache;
cache.initgroups(uid_,gid_);
}
+3 -3
View File
@@ -53,9 +53,9 @@
namespace ugid
{
extern __thread uid_t currentuid;
extern __thread gid_t currentgid;
extern __thread bool initialized;
extern thread_local uid_t currentuid;
extern thread_local gid_t currentgid;
extern thread_local bool initialized;
struct Set
{
+3 -3
View File
@@ -23,9 +23,9 @@
namespace ugid
{
__thread uid_t currentuid = 0;
__thread gid_t currentgid = 0;
__thread bool initialized = false;
thread_local uid_t currentuid = 0;
thread_local gid_t currentgid = 0;
thread_local bool initialized = false;
void
init()
+231 -77
View File
@@ -1,140 +1,294 @@
//Author: Wang Yi <godspeed_china@yeah.net>
#ifndef wyhash_final_version
#define wyhash_final_version
//defines that change behavior
// This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/)
// main repo: https://github.com/wangyi-fudan/wyhash
// author: 王一 Wang Yi <godspeed_china@yeah.net>
// contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric
/* quick example:
string s="fjsakfdsjkf";
uint64_t hash=wyhash(s.c_str(), s.size(), 0, _wyp);
*/
#ifndef wyhash_final_version_4_2
#define wyhash_final_version_4_2
#ifndef WYHASH_CONDOM
#define WYHASH_CONDOM 1 //0: read 8 bytes before and after boundaries, dangerous but faster. 1: normal valid behavior 2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication"
//protections that produce different results:
//1: normal valid behavior
//2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication"
#define WYHASH_CONDOM 1
#endif
#define WYHASH_32BIT_MUM 0 //faster on 32 bit system
#ifndef WYHASH_32BIT_MUM
//0: normal version, slow on 32 bit systems
//1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function
#define WYHASH_32BIT_MUM 0
#endif
//includes
#include <stdint.h>
#include <string.h>
#if defined(_MSC_VER) && defined(_M_X64)
#include <intrin.h>
#pragma intrinsic(_umul128)
#include <intrin.h>
#pragma intrinsic(_umul128)
#endif
//likely and unlikely macros
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
#define _likely_(x) __builtin_expect(x,1)
#define _unlikely_(x) __builtin_expect(x,0)
#define _likely_(x) __builtin_expect(x,1)
#define _unlikely_(x) __builtin_expect(x,0)
#else
#define _likely_(x) (x)
#define _unlikely_(x) (x)
#define _likely_(x) (x)
#define _unlikely_(x) (x)
#endif
//mum function
//128bit multiply function
static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); }
static inline void _wymum(uint64_t *A, uint64_t *B){
#if(WYHASH_32BIT_MUM)
uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(unsigned)*B, lh=(unsigned)*A*(*B>>32), ll=(uint64_t)(unsigned)*A*(unsigned)*B;
#if(WYHASH_CONDOM>1)
uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B;
#if(WYHASH_CONDOM>1)
*A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll;
#else
#else
*A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll;
#endif
#endif
#elif defined(__SIZEOF_INT128__)
__uint128_t r=*A; r*=*B;
#if(WYHASH_CONDOM>1)
#if(WYHASH_CONDOM>1)
*A^=(uint64_t)r; *B^=(uint64_t)(r>>64);
#else
#else
*A=(uint64_t)r; *B=(uint64_t)(r>>64);
#endif
#endif
#elif defined(_MSC_VER) && defined(_M_X64)
#if(WYHASH_CONDOM>1)
#if(WYHASH_CONDOM>1)
uint64_t a, b;
a=_umul128(*A,*B,&b);
*A^=a; *B^=b;
#else
#else
*A=_umul128(*A,*B,B);
#endif
#endif
#else
uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo;
uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl;
lo=t+(rm1<<32); c+=lo<t; hi=rh+(rm0>>32)+(rm1>>32)+c;
#if(WYHASH_CONDOM>1)
#if(WYHASH_CONDOM>1)
*A^=lo; *B^=hi;
#else
#else
*A=lo; *B=hi;
#endif
#endif
#endif
}
//multiply and xor mix function, aka MUM
static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; }
//read functions
//endian macros
#ifndef WYHASH_LITTLE_ENDIAN
#if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define WYHASH_LITTLE_ENDIAN 1
#elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define WYHASH_LITTLE_ENDIAN 0
#endif
#if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define WYHASH_LITTLE_ENDIAN 1
#elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define WYHASH_LITTLE_ENDIAN 0
#else
#warning could not determine endianness! Falling back to little endian.
#define WYHASH_LITTLE_ENDIAN 1
#endif
#endif
//read functions
#if (WYHASH_LITTLE_ENDIAN)
static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;}
static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return v;}
static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;}
#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);}
static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return __builtin_bswap32(v);}
static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);}
#elif defined(_MSC_VER)
static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);}
static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return _byteswap_ulong(v);}
static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);}
#else
static inline uint64_t _wyr8(const uint8_t *p) {
uint64_t v; memcpy(&v, p, 8);
return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000));
}
static inline uint64_t _wyr4(const uint8_t *p) {
uint32_t v; memcpy(&v, p, 4);
return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000));
}
#endif
static inline uint64_t _wyr3(const uint8_t *p, unsigned k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];}
//wyhash function
static inline uint64_t _wyfinish16(const uint8_t *p, uint64_t len, uint64_t seed, const uint64_t *secret, uint64_t i){
#if(WYHASH_CONDOM>0)
uint64_t a, b;
if(_likely_(i<=8)){
if(_likely_(i>=4)){ a=_wyr4(p); b=_wyr4(p+i-4); }
else if (_likely_(i)){ a=_wyr3(p,i); b=0; }
static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];}
//wyhash main function
static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){
const uint8_t *p=(const uint8_t *)key; seed^=_wymix(seed^secret[0],secret[1]); uint64_t a, b;
if(_likely_(len<=16)){
if(_likely_(len>=4)){ a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); }
else if(_likely_(len>0)){ a=_wyr3(p,len); b=0;}
else a=b=0;
}
else{ a=_wyr8(p); b=_wyr8(p+i-8); }
return _wymix(secret[1]^len,_wymix(a^secret[1], b^seed));
#else
#define oneshot_shift ((i<8)*((8-i)<<3))
return _wymix(secret[1]^len,_wymix((_wyr8(p)<<oneshot_shift)^secret[1],(_wyr8(p+i-8)>>oneshot_shift)^seed));
#endif
}
static inline uint64_t _wyfinish(const uint8_t *p, uint64_t len, uint64_t seed, const uint64_t *secret, uint64_t i){
if(_likely_(i<=16)) return _wyfinish16(p,len,seed,secret,i);
return _wyfinish(p+16,len,_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed),secret,i-16);
}
static inline uint64_t wyhash(const void *key, uint64_t len, uint64_t seed, const uint64_t *secret){
const uint8_t *p=(const uint8_t *)key;
uint64_t i=len; seed^=*secret;
if(_unlikely_(i>64)){
uint64_t see1=seed;
do{
seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed)^_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^seed);
see1=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see1)^_wymix(_wyr8(p+48)^secret[4],_wyr8(p+56)^see1);
p+=64; i-=64;
}while(i>64);
seed^=see1;
else{
size_t i=len;
if(_unlikely_(i>=48)){
uint64_t see1=seed, see2=seed;
do{
seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed);
see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1);
see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2);
p+=48; i-=48;
}while(_likely_(i>=48));
seed^=see1^see2;
}
while(_unlikely_(i>16)){ seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; }
a=_wyr8(p+i-16); b=_wyr8(p+i-8);
}
return _wyfinish(p,len,seed,secret,i);
a^=secret[1]; b^=seed; _wymum(&a,&b);
return _wymix(a^secret[0]^len,b^secret[1]);
}
//utility functions
const uint64_t _wyp[5] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull, 0x1d8e4e27c47d124full};
static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=_wyp[0]; B^=_wyp[1]; _wymum(&A,&B); return _wymix(A^_wyp[0],B^_wyp[1]);}
static inline uint64_t wyrand(uint64_t *seed){ *seed+=_wyp[0]; return _wymix(*seed,*seed^_wyp[1]);}
//the default secret parameters
static const uint64_t _wyp[4] = {0x2d358dccaa6c78a5ull, 0x8bb84b93962eacc9ull, 0x4b33a62ed433d4a3ull, 0x4d5a2da51de1aa47ull};
//a useful 64bit-64bit mix function to produce deterministic pseudo random numbers that can pass BigCrush and PractRand
static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0x2d358dccaa6c78a5ull; B^=0x8bb84b93962eacc9ull; _wymum(&A,&B); return _wymix(A^0x2d358dccaa6c78a5ull,B^0x8bb84b93962eacc9ull);}
//The wyrand PRNG that pass BigCrush and PractRand
static inline uint64_t wyrand(uint64_t *seed){ *seed+=0x2d358dccaa6c78a5ull; return _wymix(*seed,*seed^0x8bb84b93962eacc9ull);}
//convert any 64 bit pseudo random numbers to uniform distribution [0,1). It can be combined with wyrand, wyhash64 or wyhash.
static inline double wy2u01(uint64_t r){ const double _wynorm=1.0/(1ull<<52); return (r>>12)*_wynorm;}
//convert any 64 bit pseudo random numbers to APPROXIMATE Gaussian distribution. It can be combined with wyrand, wyhash64 or wyhash.
static inline double wy2gau(uint64_t r){ const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0;}
#ifdef WYTRNG
#include <sys/time.h>
//The wytrand true random number generator, passed BigCrush.
static inline uint64_t wytrand(uint64_t *seed){
struct timeval t; gettimeofday(&t,0);
uint64_t teed=(((uint64_t)t.tv_sec)<<32)|t.tv_usec;
teed=_wymix(teed^_wyp[0],*seed^_wyp[1]);
*seed=_wymix(teed^_wyp[0],_wyp[2]);
return _wymix(*seed,*seed^_wyp[3]);
}
#endif
#if(!WYHASH_32BIT_MUM)
//fast range integer random number generation on [0,k) credit to Daniel Lemire. May not work when WYHASH_32BIT_MUM=1. It can be combined with wyrand, wyhash64 or wyhash.
static inline uint64_t wy2u0k(uint64_t r, uint64_t k){ _wymum(&r,&k); return k; }
#endif
// modified from https://github.com/going-digital/Prime64
static inline unsigned long long mul_mod(unsigned long long a, unsigned long long b, unsigned long long m) {
unsigned long long r=0;
while (b) {
if (b & 1) {
unsigned long long r2 = r + a;
if (r2 < r) r2 -= m;
r = r2 % m;
}
b >>= 1;
if (b) {
unsigned long long a2 = a + a;
if (a2 < a) a2 -= m;
a = a2 % m;
}
}
return r;
}
static inline unsigned long long pow_mod(unsigned long long a, unsigned long long b, unsigned long long m) {
unsigned long long r=1;
while (b) {
if (b&1) r=mul_mod(r,a,m);
b>>=1;
if (b) a=mul_mod(a,a,m);
}
return r;
}
static inline unsigned sprp(unsigned long long n, unsigned long long a) {
unsigned long long d=n-1;
unsigned char s=0;
while (!(d & 0xff)) { d>>=8; s+=8; }
if (!(d & 0xf)) { d>>=4; s+=4; }
if (!(d & 0x3)) { d>>=2; s+=2; }
if (!(d & 0x1)) { d>>=1; s+=1; }
unsigned long long b=pow_mod(a,d,n);
if ((b==1) || (b==(n-1))) return 1;
unsigned char r;
for (r=1; r<s; r++) {
b=mul_mod(b,b,n);
if (b<=1) return 0;
if (b==(n-1)) return 1;
}
return 0;
}
static inline unsigned is_prime(unsigned long long n) {
if (n<2||!(n&1)) return 0;
if (n<4) return 1;
if (!sprp(n,2)) return 0;
if (n<2047) return 1;
if (!sprp(n,3)) return 0;
if (!sprp(n,5)) return 0;
if (!sprp(n,7)) return 0;
if (!sprp(n,11)) return 0;
if (!sprp(n,13)) return 0;
if (!sprp(n,17)) return 0;
if (!sprp(n,19)) return 0;
if (!sprp(n,23)) return 0;
if (!sprp(n,29)) return 0;
if (!sprp(n,31)) return 0;
if (!sprp(n,37)) return 0;
return 1;
}
//make your own secret
static inline void make_secret(uint64_t seed, uint64_t *secret){
uint8_t c[] = {15, 23, 27, 29, 30, 39, 43, 45, 46, 51, 53, 54, 57, 58, 60, 71, 75, 77, 78, 83, 85, 86, 89, 90, 92, 99, 101, 102, 105, 106, 108, 113, 114, 116, 120, 135, 139, 141, 142, 147, 149, 150, 153, 154, 156, 163, 165, 166, 169, 170, 172, 177, 178, 180, 184, 195, 197, 198, 201, 202, 204, 209, 210, 212, 216, 225, 226, 228, 232, 240 };
for(size_t i=0;i<5;i++){
for(size_t i=0;i<4;i++){
uint8_t ok;
do{
ok=1; secret[i]=0;
for(size_t j=0;j<64;j+=8) secret[i]|=((uint64_t)c[wyrand(&seed)%sizeof(c)])<<j;
if(secret[i]%2==0){ ok=0; continue; }
for(size_t j=0;j<i;j++)
for(size_t j=0;j<i;j++) {
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__)
if(__builtin_popcountll(secret[j]^secret[i])!=32){ ok=0; break; }
#elif defined(_MSC_VER) && defined(_M_X64)
if(_mm_popcnt_u64(secret[j]^secret[i])!=32){ ok=0; break; }
if(_mm_popcnt_u64(secret[j]^secret[i])!=32){ ok=0; break; }
#else
//manual popcount
uint64_t x = secret[j]^secret[i];
x -= (x >> 1) & 0x5555555555555555;
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f;
x = (x * 0x0101010101010101) >> 56;
if(x!=32){ ok=0; break; }
#endif
if(!ok)continue;
for(uint64_t j=3;j<0x100000000ull;j+=2) if(secret[i]%j==0){ ok=0; break; }
}
if(ok&&!is_prime(secret[i])) ok=0;
}while(!ok);
}
}
#endif
/* The Unlicense
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/

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