mirror of
https://github.com/trapexit/mergerfs.git
synced 2024-04-21 12:31:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b21172c609 | ||
|
|
f0444a1ca9 | ||
|
|
977d04229f | ||
|
|
757ae66327 | ||
|
|
42d0b5734c | ||
|
|
78050ed6b0 | ||
|
|
19ee44afe7 | ||
|
|
a234e9446c | ||
|
|
951eb9540d | ||
|
|
85a936c95e | ||
|
|
8210e63a66 | ||
|
|
facdb66746 | ||
|
|
1916e54a24 | ||
|
|
7fabe515f4 | ||
|
|
0ca5d6aad0 | ||
|
|
0c1aeb411d | ||
|
|
17fdbf7f42 | ||
|
|
27349e41ef | ||
|
|
b3ce95c422 | ||
|
|
cedc9f0f25 | ||
|
|
d68ad9ac01 | ||
|
|
ae6c4f7c25 | ||
|
|
87475098a1 | ||
|
|
d4dc0701d6 | ||
|
|
c1c2f075ee | ||
|
|
96d34f1eab | ||
|
|
f66fb7e2d4 | ||
|
|
edb3368078 | ||
|
|
bd70928268 | ||
|
|
337aade02d | ||
|
|
d6a2f06797 | ||
|
|
7890a4996c | ||
|
|
6aa6452d3e | ||
|
|
7d6c8e9333 | ||
|
|
a7069153bd | ||
|
|
5c71a75fdd | ||
|
|
fd33df04a3 | ||
|
|
a9ae36a3e4 | ||
|
|
569537df9e | ||
|
|
8534ee7ceb | ||
|
|
620cab2948 | ||
|
|
8635456818 | ||
|
|
0888f372aa | ||
|
|
e09b10c55b | ||
|
|
766b923116 |
+56
-41
@@ -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
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
* 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,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
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -89,7 +89,12 @@ start with one of the following option sets.
|
||||
|
||||
#### You need `mmap` (used by rtorrent and many sqlite3 base software)
|
||||
|
||||
`cache.files=partial,dropcacheonclose=true,category.create=mfs`
|
||||
`cache.files=auto-full,dropcacheonclose=true,category.create=mfs`
|
||||
|
||||
or if you are on a Linux kernel >= 6.6.x mergerfs will enable a mode
|
||||
that allows shared mmap when `cache.files=off`. To be sure of the best
|
||||
performance between `cache.files=off` and `cache.files=auto-full`
|
||||
you'll need to do your own benchmarking but often `off` is faster.
|
||||
|
||||
#### You don't need `mmap`
|
||||
|
||||
@@ -97,11 +102,11 @@ start with one of the following option sets.
|
||||
|
||||
### Command Line
|
||||
|
||||
`mergerfs -o cache.files=partial,dropcacheonclose=true,category.create=mfs /mnt/hdd0:/mnt/hdd1 /media`
|
||||
`mergerfs -o cache.files=auto-full,dropcacheonclose=true,category.create=mfs /mnt/hdd0:/mnt/hdd1 /media`
|
||||
|
||||
### /etc/fstab
|
||||
|
||||
`/mnt/hdd0:/mnt/hdd1 /media fuse.mergerfs cache.files=partial,dropcacheonclose=true,category.create=mfs 0 0`
|
||||
`/mnt/hdd0:/mnt/hdd1 /media mergerfs cache.files=auto-full,dropcacheonclose=true,category.create=mfs 0 0`
|
||||
|
||||
### systemd mount
|
||||
|
||||
@@ -116,7 +121,7 @@ Type=simple
|
||||
KillMode=none
|
||||
ExecStart=/usr/bin/mergerfs \
|
||||
-f \
|
||||
-o cache.files=partial \
|
||||
-o cache.files=auto-full \
|
||||
-o dropcacheonclose=true \
|
||||
-o category.create=mfs \
|
||||
/mnt/hdd0:/mnt/hdd1 \
|
||||
@@ -162,6 +167,11 @@ These options are the same regardless of whether you use them with the
|
||||
longer need the data and it can drop its cache. Recommended when
|
||||
**cache.files=partial|full|auto-full|per-process** to limit double
|
||||
caching. (default: false)
|
||||
* **direct-io-allow-mmap=BOOL**: On newer kernels (>= 6.6) it is
|
||||
possible to disable file page caching while still allowing for
|
||||
shared mmap support. mergerfs will enable this feature if available
|
||||
but an option is provided to turn it off for testing and debugging
|
||||
purposes. (default: true)
|
||||
* **symlinkify=BOOL**: When enabled and a file is not writable and its
|
||||
mtime or ctime is older than **symlinkify_timeout** files will be
|
||||
reported as symlinks to the original files. Please read more below
|
||||
@@ -180,6 +190,9 @@ These options are the same regardless of whether you use them with the
|
||||
link to always use the non-path preserving behavior. This means
|
||||
files, when renamed or linked, will stay on the same
|
||||
filesystem. (default: false)
|
||||
* **export-support=BOOL**: Sets a low-level FUSE feature intended to
|
||||
indicate the filesystem can support being exported via
|
||||
NFS. (default: true)
|
||||
* **security_capability=BOOL**: If false return ENOATTR when xattr
|
||||
security.capability is queried. (default: true)
|
||||
* **xattr=passthrough|noattr|nosys**: Runtime control of
|
||||
@@ -248,6 +261,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)
|
||||
@@ -304,8 +320,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.
|
||||
|
||||
@@ -382,8 +398,8 @@ The above line will use all mount points in /mnt prefixed with **hdd** and **ssd
|
||||
To have the pool mounted at boot or otherwise accessible from related tools use **/etc/fstab**.
|
||||
|
||||
```
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/hdd*:/mnt/ssd /media fuse.mergerfs minfreespace=16G 0 0
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0
|
||||
```
|
||||
|
||||
**NOTE:** the globbing is done at mount or when updated using the
|
||||
@@ -412,7 +428,7 @@ or file is to be selected or something changes out of band it becomes
|
||||
unclear what value should be used. Most software does not to care what
|
||||
the values are but those that do often break if a value changes
|
||||
unexpectedly. The tool `find` will abort a directory walk if it sees a
|
||||
directory inode change. NFS will return stale handle errors if the
|
||||
directory inode change. NFS can return stale handle errors if the
|
||||
inode changes out of band. File dedup tools will usually leverage
|
||||
device ids and inodes as a shortcut in searching for duplicate files
|
||||
and would resort to full file comparisons should it find different
|
||||
@@ -667,10 +683,23 @@ 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.
|
||||
|
||||
|
||||
### export-support
|
||||
|
||||
In theory this flag should not be exposed to the end user. It is a
|
||||
low-level FUSE flag which indicates whether or not the kernel can send
|
||||
certain kinds of messages to it for the purposes of using with
|
||||
NFS. mergerfs does support these messages but due bugs and quirks
|
||||
found in the kernel and mergerfs this option is provided just in case
|
||||
it is needed for debugging.
|
||||
|
||||
Given that this flag is set when the FUSE connection is first
|
||||
initiated it is not possible to change during run time.
|
||||
|
||||
|
||||
# FUNCTIONS, CATEGORIES and POLICIES
|
||||
|
||||
The POSIX filesystem API is made up of a number of
|
||||
@@ -826,7 +855,7 @@ policies is not appropriate.
|
||||
|
||||
#### func.readdir
|
||||
|
||||
examples: `fuse.readdir=seq`, `fuse.readdir=cor:4`
|
||||
examples: `func.readdir=seq`, `func.readdir=cor:4`
|
||||
|
||||
`readdir` has policies to control how it manages reading directory
|
||||
content.
|
||||
@@ -834,8 +863,8 @@ 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 `fuse.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 `fuse.readdir=cor:N` where `N` is the number of threads.
|
||||
| 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
|
||||
@@ -926,6 +955,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
|
||||
@@ -1130,8 +1180,8 @@ mount. If you plan on changing settings with the new mount you should
|
||||
```
|
||||
$ sudo mount /mnt/mergerfs
|
||||
$ mount | grep mergerfs
|
||||
media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
```
|
||||
|
||||
A problem with this approach is that the underlying instance will
|
||||
@@ -1264,6 +1314,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
|
||||
@@ -1725,27 +1871,24 @@ on page caching.
|
||||
|
||||
#### NFS clients returning ESTALE / Stale file handle
|
||||
|
||||
NFS does not like out of band changes. That is especially true of
|
||||
inode values.
|
||||
|
||||
Be sure to use the following options:
|
||||
|
||||
* noforget
|
||||
* inodecalc=path-hash
|
||||
NFS generally does not like out of band changes. Take a look at the
|
||||
section on NFS in the [#remote-filesystems](#remote-filesystems) for
|
||||
more details.
|
||||
|
||||
|
||||
#### rtorrent fails with ENODEV (No such device)
|
||||
|
||||
Be sure to set `cache.files=partial|full|auto-full|per-processe` or
|
||||
turn off `direct_io`. rtorrent and some other applications use
|
||||
[mmap](http://linux.die.net/man/2/mmap) to read and write to files and
|
||||
offer no fallback to traditional methods. FUSE does not currently
|
||||
support mmap while using `direct_io`. There may be a performance
|
||||
penalty on writes with `direct_io` off as well as the problem of
|
||||
double caching but it's the only way to get such applications to
|
||||
work. If the performance loss is too high for other apps you can mount
|
||||
mergerfs twice. Once with `direct_io` enabled and one without it. Be
|
||||
sure to set `dropcacheonclose=true` if not using `direct_io`.
|
||||
Be sure to set
|
||||
`cache.files=partial|full|auto-full|per-processe`. rtorrent and some
|
||||
other applications use [mmap](http://linux.die.net/man/2/mmap) to read
|
||||
and write to files and offer no fallback to traditional methods. FUSE
|
||||
does not currently support mmap while using `direct_io`. There may be
|
||||
a performance penalty on writes with `direct_io` off as well as the
|
||||
problem of double caching but it's the only way to get such
|
||||
applications to work. If the performance loss is too high for other
|
||||
apps you can mount mergerfs twice. Once with `direct_io` enabled and
|
||||
one without it. Be sure to set `dropcacheonclose=true` if not using
|
||||
`direct_io`.
|
||||
|
||||
|
||||
#### Plex doesn't work with mergerfs
|
||||
@@ -1881,6 +2024,124 @@ containers or use a standard shared user & groups technology like NIS
|
||||
or LDAP.
|
||||
|
||||
|
||||
# Remote Filesystems
|
||||
|
||||
Many users ask about compatibility with remote filesystems. This
|
||||
section is to describe any known issues or quirks when using mergerfs
|
||||
with common remote filesystems.
|
||||
|
||||
Keep in mind that, like with caching, it is not a good idea to change
|
||||
the contents of the remote filesystem
|
||||
[out-of-band](https://en.wikipedia.org/wiki/Out-of-band). Meaning that
|
||||
you really shouldn't change the contents of the underlying
|
||||
filesystems or mergerfs on the server hosting the remote
|
||||
filesystem. Doing so can lead to weird behavior, inconsistency,
|
||||
errors, and even data corruption should multiple programs try to write
|
||||
or read the same data at the same time. This isn't to say you can't do
|
||||
it or that data corruption is likely but it *could* happen. It is
|
||||
better to always use the remote filesystem. Even on the machine
|
||||
serving it.
|
||||
|
||||
|
||||
## NFS
|
||||
|
||||
[NFS](https://en.wikipedia.org/wiki/Network_File_System) is a common
|
||||
remote filesystem on Unix/POSIX systems. Due to how NFS works there
|
||||
are some settings which need to be set in order for mergerfs to work
|
||||
with it.
|
||||
|
||||
It should be noted that NFS and FUSE (the technology mergerfs uses) do
|
||||
not work perfectly with one another due to certain design choices in
|
||||
FUSE (and mergerfs.) Due to these issues it is generally recommended
|
||||
to use SMB when possible till situations change. That said mergerfs
|
||||
should generally work as an export of NFS and issues discovered should
|
||||
still be reported.
|
||||
|
||||
To ensure compatibility between mergerfs and NFS use the following
|
||||
settings.
|
||||
|
||||
mergerfs settings:
|
||||
* noforget
|
||||
* inodecalc=path-hash
|
||||
|
||||
NFS export settings:
|
||||
* fsid=UUID
|
||||
* no\_root\_squash
|
||||
|
||||
`noforget` is needed because NFS uses the `name_to_handle_at` and
|
||||
`open_by_handle_at` functions which allow a program to keep a
|
||||
reference to a file without technically having it open in the typical
|
||||
sense. The problem is that FUSE has no way to know that NFS has a
|
||||
handle that it will later use to open the file again. As a result it
|
||||
is possible for the kernel to tell mergerfs to forget about the node
|
||||
and should NFS ever ask for that node's details in the future it would
|
||||
have nothing to respond with. Keeping nodes around forever is not
|
||||
ideal but at the moment the only way to manage the situation.
|
||||
|
||||
`inodecalc=path-hash` is needed because NFS is sensitive to
|
||||
out-of-band changes. FUSE doesn't care if a file's inode value changes
|
||||
but NFS, being stateful, does. So if you used the default inode
|
||||
calculation algorithm then it is possible that if you changed a file
|
||||
or updated a directory the file mergerfs will use will be on a
|
||||
different branch and therefore the inode would change. This isn't an
|
||||
ideal solution and others are being considered but it works for most
|
||||
situations.
|
||||
|
||||
`fsid=UUID` is needed because FUSE filesystems don't have different
|
||||
`st_dev` values which can cause issues when exporting. The easiest
|
||||
thing to do is set each mergerfs export `fsid` to some random
|
||||
value. An easy way to generate a random value is to use the command
|
||||
line tool `uuid` or `uuidgen` or through a website such as
|
||||
[uuidgenerator.net](https://www.uuidgenerator.net/).
|
||||
|
||||
`no_root_squash` is not strictly necessary but can lead to confusing
|
||||
permission and ownership issues if root squashing is enabled.
|
||||
|
||||
|
||||
## SMB / CIFS
|
||||
|
||||
[SMB](https://en.wikipedia.org/wiki/Server_Message_Block) is a
|
||||
protocol most used by Microsoft Windows systems to share file shares,
|
||||
printers, etc. However, due to the popularity for Windows, it is also
|
||||
supported on many other platforms including Linux. The most popular
|
||||
way of supporting SMB on Linux is via the software Samba.
|
||||
|
||||
[Samba](https://en.wikipedia.org/wiki/Samba_(software)), and other
|
||||
ways of serving Linux filesystems, via SMB should work fine with
|
||||
mergerfs. The services do not tend to use the same technologies which
|
||||
NFS uses and therefore don't have the same issues. There should not be
|
||||
an special settings required to use mergerfs with Samba. However,
|
||||
[CIFSD](https://en.wikipedia.org/wiki/CIFSD) and other programs have
|
||||
not been extensively tested. If you use mergerfs with CIFSD or other
|
||||
SMB servers please submit your experiences so these docs can be
|
||||
updated.
|
||||
|
||||
|
||||
## SSHFS
|
||||
|
||||
[SSHFS](https://en.wikipedia.org/wiki/SSHFS) is a FUSE filesystem
|
||||
leveraging SSH as the connection and transport layer. While often
|
||||
simpler to setup when compared to NFS or Samba the performance can be
|
||||
lacking and the project is very much in maintenance mode.
|
||||
|
||||
There are no known issues using sshfs with mergerfs. You may want to
|
||||
use the following arguments to improve performance but your millage
|
||||
may vary.
|
||||
|
||||
* `-o Ciphers=arcfour`
|
||||
* `-o Compression=no`
|
||||
|
||||
More info can be found
|
||||
[here](https://ideatrash.net/2016/08/odds-and-ends-optimizing-sshfs-moving.html).
|
||||
|
||||
|
||||
## Other
|
||||
|
||||
There are other remote filesystems but none popularly used to serve
|
||||
mergerfs. If you use something not listed above feel free to reach out
|
||||
and I will add it to the list.
|
||||
|
||||
|
||||
# FAQ
|
||||
|
||||
#### How well does mergerfs scale? Is it "production ready?"
|
||||
@@ -2015,15 +2276,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?
|
||||
@@ -2045,6 +2307,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.
|
||||
@@ -2106,8 +2378,8 @@ values are correct and that directories have their executable bit
|
||||
set. A common mistake by users new to Linux is to `chmod -R 644` when
|
||||
they should have `chmod -R u=rwX,go=rX`.
|
||||
|
||||
If using a network filesystem such as NFS, SMB, CIFS (Samba) be sure
|
||||
to pay close attention to anything regarding permissioning and
|
||||
If using a network filesystem such as NFS or SMB (Samba) be sure to
|
||||
pay close attention to anything regarding permissioning and
|
||||
users. Root squashing and user translation for instance has bitten a
|
||||
few mergerfs users. Some of these also affect the use of mergerfs from
|
||||
container platforms such as Docker.
|
||||
@@ -2163,103 +2435,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
|
||||
@@ -2326,37 +2501,13 @@ it won't show up.
|
||||
You can remove the reserve by running: `tune2fs -m 0 <device>`
|
||||
|
||||
|
||||
#### Can mergerfs mounts be exported over NFS?
|
||||
|
||||
Yes, however if you do anything which may changes files out of band
|
||||
(including for example using the `newest` policy) it will result in
|
||||
"stale file handle" errors unless properly setup.
|
||||
|
||||
Be sure to use the following options:
|
||||
|
||||
* noforget
|
||||
* inodecalc=path-hash
|
||||
|
||||
|
||||
#### Can mergerfs mounts be exported over Samba / SMB?
|
||||
|
||||
Yes. While some users have reported problems it appears to always be
|
||||
related to how Samba is setup in relation to permissions.
|
||||
|
||||
|
||||
#### Can mergerfs mounts be used over SSHFS?
|
||||
|
||||
Yes.
|
||||
|
||||
|
||||
#### I notice massive slowdowns of writes when enabling cache.files.
|
||||
|
||||
When file caching is enabled in any form (`cache.files!=off` or
|
||||
`direct_io=false`) it will issue `getxattr` requests for
|
||||
`security.capability` prior to *every single write*. This will usually
|
||||
result in a performance degradation, especially when using a network
|
||||
filesystem (such as NFS or CIFS/SMB/Samba.) Unfortunately at this
|
||||
moment the kernel is not caching the response.
|
||||
When file caching is enabled in any form (`cache.files!=off`) it will
|
||||
issue `getxattr` requests for `security.capability` prior to *every
|
||||
single write*. This will usually result in a performance degradation,
|
||||
especially when using a network filesystem (such as NFS or SMB.)
|
||||
Unfortunately at this moment the kernel is not caching the response.
|
||||
|
||||
To work around this situation mergerfs offers a few solutions.
|
||||
|
||||
@@ -2411,6 +2562,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
|
||||
|
||||
|
||||
+1
-2
@@ -50,8 +50,7 @@ SRC_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 \
|
||||
|
||||
@@ -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;
|
||||
@@ -106,23 +108,26 @@ struct fuse_file_info_t
|
||||
* FUSE_CAP_IOCTL_DIR: ioctl support on directories
|
||||
* FUSE_CAP_CACHE_SYMLINKS: cache READLINK responses
|
||||
*/
|
||||
#define FUSE_CAP_ASYNC_READ (1 << 0)
|
||||
#define FUSE_CAP_POSIX_LOCKS (1 << 1)
|
||||
#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
|
||||
#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
|
||||
#define FUSE_CAP_BIG_WRITES (1 << 5)
|
||||
#define FUSE_CAP_DONT_MASK (1 << 6)
|
||||
#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
|
||||
#define FUSE_CAP_IOCTL_DIR (1 << 11)
|
||||
#define FUSE_CAP_READDIR_PLUS (1 << 13)
|
||||
#define FUSE_CAP_READDIR_PLUS_AUTO (1 << 14)
|
||||
#define FUSE_CAP_ASYNC_DIO (1 << 15)
|
||||
#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
|
||||
#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
|
||||
#define FUSE_CAP_POSIX_ACL (1 << 19)
|
||||
#define FUSE_CAP_CACHE_SYMLINKS (1 << 20)
|
||||
#define FUSE_CAP_MAX_PAGES (1 << 21)
|
||||
#define FUSE_CAP_SETXATTR_EXT (1 << 22)
|
||||
#define FUSE_CAP_ASYNC_READ (1ULL << 0)
|
||||
#define FUSE_CAP_POSIX_LOCKS (1ULL << 1)
|
||||
#define FUSE_CAP_ATOMIC_O_TRUNC (1ULL << 3)
|
||||
#define FUSE_CAP_EXPORT_SUPPORT (1ULL << 4)
|
||||
#define FUSE_CAP_BIG_WRITES (1ULL << 5)
|
||||
#define FUSE_CAP_DONT_MASK (1ULL << 6)
|
||||
#define FUSE_CAP_FLOCK_LOCKS (1ULL << 10)
|
||||
#define FUSE_CAP_IOCTL_DIR (1ULL << 11)
|
||||
#define FUSE_CAP_READDIR_PLUS (1ULL << 13)
|
||||
#define FUSE_CAP_READDIR_PLUS_AUTO (1ULL << 14)
|
||||
#define FUSE_CAP_ASYNC_DIO (1ULL << 15)
|
||||
#define FUSE_CAP_WRITEBACK_CACHE (1ULL << 16)
|
||||
#define FUSE_CAP_PARALLEL_DIROPS (1ULL << 18)
|
||||
#define FUSE_CAP_POSIX_ACL (1ULL << 19)
|
||||
#define FUSE_CAP_CACHE_SYMLINKS (1ULL << 20)
|
||||
#define FUSE_CAP_MAX_PAGES (1ULL << 21)
|
||||
#define FUSE_CAP_SETXATTR_EXT (1ULL << 22)
|
||||
#define FUSE_CAP_DIRECT_IO_ALLOW_MMAP (1ULL << 23)
|
||||
#define FUSE_CAP_CREATE_SUPP_GROUP (1ULL << 24)
|
||||
|
||||
|
||||
/**
|
||||
* Ioctl flags
|
||||
@@ -148,56 +153,17 @@ struct fuse_file_info_t
|
||||
* indicate the value requested by the filesystem. The requested
|
||||
* value must usually be smaller than the indicated value.
|
||||
*/
|
||||
struct fuse_conn_info {
|
||||
/**
|
||||
* Major version of the protocol (read-only)
|
||||
*/
|
||||
struct fuse_conn_info
|
||||
{
|
||||
unsigned proto_major;
|
||||
|
||||
/**
|
||||
* Minor version of the protocol (read-only)
|
||||
*/
|
||||
unsigned proto_minor;
|
||||
|
||||
/**
|
||||
* Maximum size of the write buffer
|
||||
*/
|
||||
unsigned max_write;
|
||||
|
||||
/**
|
||||
* Maximum readahead
|
||||
*/
|
||||
unsigned max_readahead;
|
||||
|
||||
/**
|
||||
* Capability flags, that the kernel supports
|
||||
*/
|
||||
unsigned capable;
|
||||
|
||||
/**
|
||||
* Capability flags, that the filesystem wants to enable
|
||||
*/
|
||||
unsigned want;
|
||||
|
||||
/**
|
||||
* Maximum number of backgrounded requests
|
||||
*/
|
||||
uint64_t capable;
|
||||
uint64_t want;
|
||||
unsigned max_background;
|
||||
|
||||
/**
|
||||
* Kernel congestion threshold parameter
|
||||
*/
|
||||
unsigned congestion_threshold;
|
||||
|
||||
/**
|
||||
* Max pages
|
||||
*/
|
||||
uint16_t max_pages;
|
||||
|
||||
/**
|
||||
* For future use.
|
||||
*/
|
||||
unsigned reserved[22];
|
||||
};
|
||||
|
||||
struct fuse_session;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+119
-56
@@ -206,6 +206,11 @@
|
||||
* - add extension header
|
||||
* - add FUSE_EXT_GROUPS
|
||||
* - add FUSE_CREATE_SUPP_GROUP
|
||||
* - add FUSE_HAS_EXPIRE_ONLY
|
||||
*
|
||||
* 7.39
|
||||
* - add FUSE_DIRECT_IO_ALLOW_MMAP
|
||||
* - add FUSE_STATX and related structures
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_FUSE_H
|
||||
@@ -241,7 +246,7 @@
|
||||
#define FUSE_KERNEL_VERSION 7
|
||||
|
||||
/** Minor version number of this interface */
|
||||
#define FUSE_KERNEL_MINOR_VERSION 38
|
||||
#define FUSE_KERNEL_MINOR_VERSION 39
|
||||
|
||||
/** The node ID of the root inode */
|
||||
#define FUSE_ROOT_ID 1
|
||||
@@ -268,6 +273,40 @@ struct fuse_attr {
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
/*
|
||||
* The following structures are bit-for-bit compatible with the statx(2) ABI in
|
||||
* Linux.
|
||||
*/
|
||||
struct fuse_sx_time {
|
||||
int64_t tv_sec;
|
||||
uint32_t tv_nsec;
|
||||
int32_t __reserved;
|
||||
};
|
||||
|
||||
struct fuse_statx {
|
||||
uint32_t mask;
|
||||
uint32_t blksize;
|
||||
uint64_t attributes;
|
||||
uint32_t nlink;
|
||||
uint32_t uid;
|
||||
uint32_t gid;
|
||||
uint16_t mode;
|
||||
uint16_t __spare0[1];
|
||||
uint64_t ino;
|
||||
uint64_t size;
|
||||
uint64_t blocks;
|
||||
uint64_t attributes_mask;
|
||||
struct fuse_sx_time atime;
|
||||
struct fuse_sx_time btime;
|
||||
struct fuse_sx_time ctime;
|
||||
struct fuse_sx_time mtime;
|
||||
uint32_t rdev_major;
|
||||
uint32_t rdev_minor;
|
||||
uint32_t dev_major;
|
||||
uint32_t dev_minor;
|
||||
uint64_t __spare2[14];
|
||||
};
|
||||
|
||||
struct fuse_kstatfs {
|
||||
uint64_t blocks;
|
||||
uint64_t bfree;
|
||||
@@ -291,18 +330,18 @@ struct fuse_file_lock {
|
||||
/**
|
||||
* Bitmasks for fuse_setattr_in.valid
|
||||
*/
|
||||
#define FATTR_MODE (1 << 0)
|
||||
#define FATTR_UID (1 << 1)
|
||||
#define FATTR_GID (1 << 2)
|
||||
#define FATTR_SIZE (1 << 3)
|
||||
#define FATTR_ATIME (1 << 4)
|
||||
#define FATTR_MTIME (1 << 5)
|
||||
#define FATTR_FH (1 << 6)
|
||||
#define FATTR_ATIME_NOW (1 << 7)
|
||||
#define FATTR_MTIME_NOW (1 << 8)
|
||||
#define FATTR_LOCKOWNER (1 << 9)
|
||||
#define FATTR_CTIME (1 << 10)
|
||||
#define FATTR_KILL_SUIDGID (1 << 11)
|
||||
#define FATTR_MODE (1 << 0)
|
||||
#define FATTR_UID (1 << 1)
|
||||
#define FATTR_GID (1 << 2)
|
||||
#define FATTR_SIZE (1 << 3)
|
||||
#define FATTR_ATIME (1 << 4)
|
||||
#define FATTR_MTIME (1 << 5)
|
||||
#define FATTR_FH (1 << 6)
|
||||
#define FATTR_ATIME_NOW (1 << 7)
|
||||
#define FATTR_MTIME_NOW (1 << 8)
|
||||
#define FATTR_LOCKOWNER (1 << 9)
|
||||
#define FATTR_CTIME (1 << 10)
|
||||
#define FATTR_KILL_SUIDGID (1 << 11)
|
||||
|
||||
/**
|
||||
* Flags returned by the OPEN request
|
||||
@@ -315,13 +354,13 @@ struct fuse_file_lock {
|
||||
* FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE)
|
||||
* FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode
|
||||
*/
|
||||
#define FOPEN_DIRECT_IO (1 << 0)
|
||||
#define FOPEN_KEEP_CACHE (1 << 1)
|
||||
#define FOPEN_NONSEEKABLE (1 << 2)
|
||||
#define FOPEN_CACHE_DIR (1 << 3)
|
||||
#define FOPEN_STREAM (1 << 4)
|
||||
#define FOPEN_NOFLUSH (1 << 5)
|
||||
#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
|
||||
#define FOPEN_DIRECT_IO (1 << 0)
|
||||
#define FOPEN_KEEP_CACHE (1 << 1)
|
||||
#define FOPEN_NONSEEKABLE (1 << 2)
|
||||
#define FOPEN_CACHE_DIR (1 << 3)
|
||||
#define FOPEN_STREAM (1 << 4)
|
||||
#define FOPEN_NOFLUSH (1 << 5)
|
||||
#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6)
|
||||
|
||||
/**
|
||||
* INIT request/reply flags
|
||||
@@ -369,43 +408,50 @@ struct fuse_file_lock {
|
||||
* FUSE_HAS_INODE_DAX: use per inode DAX
|
||||
* FUSE_CREATE_SUPP_GROUP: add supplementary group info to create, mkdir,
|
||||
* symlink and mknod (single group that matches parent)
|
||||
* FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation
|
||||
* FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode.
|
||||
*/
|
||||
#define FUSE_ASYNC_READ (1 << 0)
|
||||
#define FUSE_POSIX_LOCKS (1 << 1)
|
||||
#define FUSE_FILE_OPS (1 << 2)
|
||||
#define FUSE_ATOMIC_O_TRUNC (1 << 3)
|
||||
#define FUSE_EXPORT_SUPPORT (1 << 4)
|
||||
#define FUSE_BIG_WRITES (1 << 5)
|
||||
#define FUSE_DONT_MASK (1 << 6)
|
||||
#define FUSE_SPLICE_WRITE (1 << 7)
|
||||
#define FUSE_SPLICE_MOVE (1 << 8)
|
||||
#define FUSE_SPLICE_READ (1 << 9)
|
||||
#define FUSE_FLOCK_LOCKS (1 << 10)
|
||||
#define FUSE_HAS_IOCTL_DIR (1 << 11)
|
||||
#define FUSE_AUTO_INVAL_DATA (1 << 12)
|
||||
#define FUSE_DO_READDIRPLUS (1 << 13)
|
||||
#define FUSE_READDIRPLUS_AUTO (1 << 14)
|
||||
#define FUSE_ASYNC_DIO (1 << 15)
|
||||
#define FUSE_WRITEBACK_CACHE (1 << 16)
|
||||
#define FUSE_NO_OPEN_SUPPORT (1 << 17)
|
||||
#define FUSE_PARALLEL_DIROPS (1 << 18)
|
||||
#define FUSE_HANDLE_KILLPRIV (1 << 19)
|
||||
#define FUSE_POSIX_ACL (1 << 20)
|
||||
#define FUSE_ABORT_ERROR (1 << 21)
|
||||
#define FUSE_MAX_PAGES (1 << 22)
|
||||
#define FUSE_CACHE_SYMLINKS (1 << 23)
|
||||
#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
|
||||
#define FUSE_ASYNC_READ (1 << 0)
|
||||
#define FUSE_POSIX_LOCKS (1 << 1)
|
||||
#define FUSE_FILE_OPS (1 << 2)
|
||||
#define FUSE_ATOMIC_O_TRUNC (1 << 3)
|
||||
#define FUSE_EXPORT_SUPPORT (1 << 4)
|
||||
#define FUSE_BIG_WRITES (1 << 5)
|
||||
#define FUSE_DONT_MASK (1 << 6)
|
||||
#define FUSE_SPLICE_WRITE (1 << 7)
|
||||
#define FUSE_SPLICE_MOVE (1 << 8)
|
||||
#define FUSE_SPLICE_READ (1 << 9)
|
||||
#define FUSE_FLOCK_LOCKS (1 << 10)
|
||||
#define FUSE_HAS_IOCTL_DIR (1 << 11)
|
||||
#define FUSE_AUTO_INVAL_DATA (1 << 12)
|
||||
#define FUSE_DO_READDIRPLUS (1 << 13)
|
||||
#define FUSE_READDIRPLUS_AUTO (1 << 14)
|
||||
#define FUSE_ASYNC_DIO (1 << 15)
|
||||
#define FUSE_WRITEBACK_CACHE (1 << 16)
|
||||
#define FUSE_NO_OPEN_SUPPORT (1 << 17)
|
||||
#define FUSE_PARALLEL_DIROPS (1 << 18)
|
||||
#define FUSE_HANDLE_KILLPRIV (1 << 19)
|
||||
#define FUSE_POSIX_ACL (1 << 20)
|
||||
#define FUSE_ABORT_ERROR (1 << 21)
|
||||
#define FUSE_MAX_PAGES (1 << 22)
|
||||
#define FUSE_CACHE_SYMLINKS (1 << 23)
|
||||
#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
|
||||
#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
|
||||
#define FUSE_MAP_ALIGNMENT (1 << 26)
|
||||
#define FUSE_SUBMOUNTS (1 << 27)
|
||||
#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
|
||||
#define FUSE_SETXATTR_EXT (1 << 29)
|
||||
#define FUSE_INIT_EXT (1 << 30)
|
||||
#define FUSE_INIT_RESERVED (1 << 31)
|
||||
#define FUSE_MAP_ALIGNMENT (1 << 26)
|
||||
#define FUSE_SUBMOUNTS (1 << 27)
|
||||
#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28)
|
||||
#define FUSE_SETXATTR_EXT (1 << 29)
|
||||
#define FUSE_INIT_EXT (1 << 30)
|
||||
#define FUSE_INIT_RESERVED (1 << 31)
|
||||
/* bits 32..63 get shifted down 32 bits into the flags2 field */
|
||||
#define FUSE_SECURITY_CTX (1ULL << 32)
|
||||
#define FUSE_HAS_INODE_DAX (1ULL << 33)
|
||||
#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
|
||||
#define FUSE_SECURITY_CTX (1ULL << 32)
|
||||
#define FUSE_HAS_INODE_DAX (1ULL << 33)
|
||||
#define FUSE_CREATE_SUPP_GROUP (1ULL << 34)
|
||||
#define FUSE_HAS_EXPIRE_ONLY (1ULL << 35)
|
||||
#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
|
||||
|
||||
/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
|
||||
#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
|
||||
|
||||
/**
|
||||
* CUSE INIT request/reply flags
|
||||
@@ -417,8 +463,8 @@ struct fuse_file_lock {
|
||||
/**
|
||||
* Release flags
|
||||
*/
|
||||
#define FUSE_RELEASE_FLUSH (1 << 0)
|
||||
#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
|
||||
#define FUSE_RELEASE_FLUSH (1 << 0)
|
||||
#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
|
||||
|
||||
/**
|
||||
* Getattr flags
|
||||
@@ -572,6 +618,7 @@ enum fuse_opcode {
|
||||
FUSE_REMOVEMAPPING = 49,
|
||||
FUSE_SYNCFS = 50,
|
||||
FUSE_TMPFILE = 51,
|
||||
FUSE_STATX = 52,
|
||||
|
||||
/* CUSE specific operations */
|
||||
CUSE_INIT = 4096,
|
||||
@@ -636,6 +683,22 @@ struct fuse_attr_out {
|
||||
struct fuse_attr attr;
|
||||
};
|
||||
|
||||
struct fuse_statx_in {
|
||||
uint32_t getattr_flags;
|
||||
uint32_t reserved;
|
||||
uint64_t fh;
|
||||
uint32_t sx_flags;
|
||||
uint32_t sx_mask;
|
||||
};
|
||||
|
||||
struct fuse_statx_out {
|
||||
uint64_t attr_valid; /* Cache timeout for the attributes */
|
||||
uint32_t attr_valid_nsec;
|
||||
uint32_t flags;
|
||||
uint64_t spare[2];
|
||||
struct fuse_statx stat;
|
||||
};
|
||||
|
||||
#define FUSE_COMPAT_MKNOD_IN_SIZE 8
|
||||
|
||||
struct fuse_mknod_in {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "moodycamel/blockingconcurrentqueue.h"
|
||||
#include "syslog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <syslog.h>
|
||||
|
||||
|
||||
struct ThreadPoolTraits : public moodycamel::ConcurrentQueueDefaultTraits
|
||||
{
|
||||
@@ -29,16 +31,20 @@ private:
|
||||
|
||||
public:
|
||||
explicit
|
||||
ThreadPool(std::size_t const thread_count_ = std::thread::hardware_concurrency(),
|
||||
std::size_t const queue_depth_ = 1,
|
||||
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_,thread_count_,thread_count_),
|
||||
_name(get_thread_name(name_))
|
||||
: _queue(),
|
||||
_queue_depth(0),
|
||||
_max_queue_depth(std::max(thread_count_,max_queue_depth_)),
|
||||
_name(name_)
|
||||
{
|
||||
syslog_debug("threadpool: spawning %zu threads of queue depth %zu named '%s'",
|
||||
thread_count_,
|
||||
queue_depth_,
|
||||
_name.c_str());
|
||||
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;
|
||||
@@ -55,9 +61,11 @@ public:
|
||||
rv = pthread_create(&t,NULL,ThreadPool::start_routine,this);
|
||||
if(rv != 0)
|
||||
{
|
||||
syslog_warning("threadpool: error spawning thread - %d (%s)",
|
||||
rv,
|
||||
strerror(rv));
|
||||
syslog(LOG_WARNING,
|
||||
"threadpool (%s): error spawning thread - %d (%s)",
|
||||
_name.c_str(),
|
||||
rv,
|
||||
strerror(rv));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -75,35 +83,23 @@ public:
|
||||
|
||||
~ThreadPool()
|
||||
{
|
||||
syslog_debug("threadpool: destroying %zu threads named '%s'",
|
||||
_threads.size(),
|
||||
_name.c_str());
|
||||
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);
|
||||
|
||||
Func f;
|
||||
while(_queue.try_dequeue(f))
|
||||
continue;
|
||||
|
||||
for(auto t : _threads)
|
||||
pthread_join(t,NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
static
|
||||
std::string
|
||||
get_thread_name(std::string const name_)
|
||||
{
|
||||
if(!name_.empty())
|
||||
return name_;
|
||||
|
||||
char name[16];
|
||||
pthread_getname_np(pthread_self(),name,sizeof(name));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static
|
||||
void*
|
||||
start_routine(void *arg_)
|
||||
@@ -111,6 +107,7 @@ private:
|
||||
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)
|
||||
@@ -118,6 +115,8 @@ private:
|
||||
q.wait_dequeue(ctok,func);
|
||||
|
||||
func();
|
||||
|
||||
queue_depth.fetch_sub(1,std::memory_order_release);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -142,9 +141,11 @@ public:
|
||||
|
||||
if(rv != 0)
|
||||
{
|
||||
syslog_warning("threadpool: error spawning thread - %d (%s)",
|
||||
rv,
|
||||
strerror(rv));
|
||||
syslog(LOG_WARNING,
|
||||
"threadpool (%s): error spawning thread - %d (%s)",
|
||||
_name.c_str(),
|
||||
rv,
|
||||
strerror(rv));
|
||||
return -rv;
|
||||
}
|
||||
|
||||
@@ -156,9 +157,10 @@ public:
|
||||
_threads.push_back(t);
|
||||
}
|
||||
|
||||
syslog_debug("threadpool: 1 thread added to pool '%s' named '%s'",
|
||||
_name.c_str(),
|
||||
name.c_str());
|
||||
syslog(LOG_DEBUG,
|
||||
"threadpool (%s): 1 thread added named '%s'",
|
||||
_name.c_str(),
|
||||
name.c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -193,11 +195,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
char name[16];
|
||||
pthread_getname_np(t,name,sizeof(name));
|
||||
syslog_debug("threadpool: 1 thread removed from pool '%s' named '%s'",
|
||||
_name.c_str(),
|
||||
name);
|
||||
syslog(LOG_DEBUG,
|
||||
"threadpool (%s): 1 thread removed",
|
||||
_name.c_str());
|
||||
|
||||
pthread_exit(NULL);
|
||||
};
|
||||
@@ -232,28 +232,32 @@ public:
|
||||
enqueue_work(moodycamel::ProducerToken &ptok_,
|
||||
FuncType &&f_)
|
||||
{
|
||||
timespec ts = {0,10};
|
||||
while(true)
|
||||
timespec ts = {0,1000};
|
||||
for(unsigned i = 0; i < 1000000; i++)
|
||||
{
|
||||
if(_queue.try_enqueue(ptok_,f_))
|
||||
return;
|
||||
if(_queue_depth.load(std::memory_order_acquire) < _max_queue_depth)
|
||||
break;
|
||||
::nanosleep(&ts,NULL);
|
||||
ts.tv_nsec += 10;
|
||||
}
|
||||
|
||||
_queue.enqueue(ptok_,f_);
|
||||
_queue_depth.fetch_add(1,std::memory_order_release);
|
||||
}
|
||||
|
||||
template<typename FuncType>
|
||||
void
|
||||
enqueue_work(FuncType &&f_)
|
||||
{
|
||||
timespec ts = {0,10};
|
||||
while(true)
|
||||
timespec ts = {0,1000};
|
||||
for(unsigned i = 0; i < 1000000; i++)
|
||||
{
|
||||
if(_queue.try_enqueue(f_))
|
||||
return;
|
||||
if(_queue_depth.load(std::memory_order_acquire) < _max_queue_depth)
|
||||
break;
|
||||
::nanosleep(&ts,NULL);
|
||||
ts.tv_nsec += 10;
|
||||
}
|
||||
|
||||
_queue.enqueue(f_);
|
||||
_queue_depth.fetch_add(1,std::memory_order_release);
|
||||
}
|
||||
|
||||
template<typename FuncType>
|
||||
@@ -266,21 +270,24 @@ public:
|
||||
|
||||
auto promise = std::make_shared<Promise>();
|
||||
auto future = promise->get_future();
|
||||
auto work = [=]()
|
||||
|
||||
auto work = [=]()
|
||||
{
|
||||
auto rv = f_();
|
||||
promise->set_value(rv);
|
||||
};
|
||||
|
||||
timespec ts = {0,10};
|
||||
while(true)
|
||||
timespec ts = {0,1000};
|
||||
for(unsigned i = 0; i < 1000000; i++)
|
||||
{
|
||||
if(_queue.try_enqueue(work))
|
||||
if(_queue_depth.load(std::memory_order_acquire) < _max_queue_depth)
|
||||
break;
|
||||
::nanosleep(&ts,NULL);
|
||||
ts.tv_nsec += 10;
|
||||
}
|
||||
|
||||
_queue.enqueue(work);
|
||||
_queue_depth.fetch_add(1,std::memory_order_release);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@@ -301,6 +308,8 @@ public:
|
||||
|
||||
private:
|
||||
Queue _queue;
|
||||
std::atomic<unsigned> _queue_depth;
|
||||
unsigned const _max_queue_depth;
|
||||
|
||||
private:
|
||||
std::string const _name;
|
||||
+30
-14
@@ -158,9 +158,9 @@ open_flag_to_str(const uint64_t offset_)
|
||||
static
|
||||
const
|
||||
char*
|
||||
fuse_flag_to_str(const uint32_t offset_)
|
||||
fuse_flag_to_str(const uint64_t offset_)
|
||||
{
|
||||
switch(1 << offset_)
|
||||
switch(1ULL << offset_)
|
||||
{
|
||||
FUSE_INIT_FLAG_CASE(ASYNC_READ);
|
||||
FUSE_INIT_FLAG_CASE(POSIX_LOCKS);
|
||||
@@ -189,6 +189,16 @@ fuse_flag_to_str(const uint32_t offset_)
|
||||
FUSE_INIT_FLAG_CASE(NO_OPENDIR_SUPPORT);
|
||||
FUSE_INIT_FLAG_CASE(EXPLICIT_INVAL_DATA);
|
||||
FUSE_INIT_FLAG_CASE(MAP_ALIGNMENT);
|
||||
FUSE_INIT_FLAG_CASE(SUBMOUNTS);
|
||||
FUSE_INIT_FLAG_CASE(HANDLE_KILLPRIV_V2);
|
||||
FUSE_INIT_FLAG_CASE(SETXATTR_EXT);
|
||||
FUSE_INIT_FLAG_CASE(INIT_EXT);
|
||||
FUSE_INIT_FLAG_CASE(INIT_RESERVED);
|
||||
FUSE_INIT_FLAG_CASE(SECURITY_CTX);
|
||||
FUSE_INIT_FLAG_CASE(HAS_INODE_DAX);
|
||||
FUSE_INIT_FLAG_CASE(CREATE_SUPP_GROUP);
|
||||
FUSE_INIT_FLAG_CASE(HAS_EXPIRE_ONLY);
|
||||
FUSE_INIT_FLAG_CASE(DIRECT_IO_ALLOW_MMAP);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -200,7 +210,7 @@ static
|
||||
void
|
||||
debug_open_flags(const uint32_t flags_)
|
||||
{
|
||||
fprintf(stderr,"%s,",open_accmode_to_str(flags_));
|
||||
fprintf(stderr,"%s, ",open_accmode_to_str(flags_));
|
||||
for(int i = 0; i < (sizeof(flags_) * 8); i++)
|
||||
{
|
||||
const char *str;
|
||||
@@ -212,7 +222,7 @@ debug_open_flags(const uint32_t flags_)
|
||||
if(str == NULL)
|
||||
continue;
|
||||
|
||||
fprintf(stderr,"%s,",str);
|
||||
fprintf(stderr,"%s, ",str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,28 +727,31 @@ debug_fuse_fallocate_in(const void *arg_)
|
||||
void
|
||||
debug_fuse_init_in(const struct fuse_init_in *arg_)
|
||||
{
|
||||
uint64_t flags;
|
||||
|
||||
flags = (((uint64_t)arg_->flags) | ((uint64_t)arg_->flags2) << 32);
|
||||
fprintf(g_OUTPUT,
|
||||
"FUSE_INIT_IN: "
|
||||
" major=%u;"
|
||||
" minor=%u;"
|
||||
" max_readahead=%u;"
|
||||
" flags=0x%08X (",
|
||||
" flags=0x%016lx (",
|
||||
arg_->major,
|
||||
arg_->minor,
|
||||
arg_->max_readahead,
|
||||
arg_->flags);
|
||||
for(uint64_t i = 0; i < (sizeof(arg_->flags)*8); i++)
|
||||
flags);
|
||||
for(uint64_t i = 0; i < (sizeof(flags)*8); i++)
|
||||
{
|
||||
const char *str;
|
||||
|
||||
if(!(arg_->flags & (1ULL << i)))
|
||||
if(!(flags & (1ULL << i)))
|
||||
continue;
|
||||
|
||||
str = fuse_flag_to_str(i);
|
||||
if(str == NULL)
|
||||
continue;
|
||||
|
||||
fprintf(g_OUTPUT,"%s,",str);
|
||||
fprintf(g_OUTPUT,"%s, ",str);
|
||||
}
|
||||
fprintf(g_OUTPUT,")\n");
|
||||
}
|
||||
@@ -748,7 +761,10 @@ debug_fuse_init_out(const uint64_t unique_,
|
||||
const struct fuse_init_out *arg_,
|
||||
const uint64_t argsize_)
|
||||
{
|
||||
uint64_t flags;
|
||||
const struct fuse_init_out *arg = arg_;
|
||||
|
||||
flags = (((uint64_t)arg->flags) | ((uint64_t)arg->flags2) << 32);
|
||||
fprintf(g_OUTPUT,
|
||||
/* "unique=0x%016"PRIx64";" */
|
||||
/* " opcode=RESPONSE;" */
|
||||
@@ -758,27 +774,27 @@ debug_fuse_init_out(const uint64_t unique_,
|
||||
" major=%u;"
|
||||
" minor=%u;"
|
||||
" max_readahead=%u;"
|
||||
" flags=0x%08X ("
|
||||
" flags=0x%016lx ("
|
||||
,
|
||||
/* unique_, */
|
||||
/* sizeof(struct fuse_out_header) + argsize_, */
|
||||
arg->major,
|
||||
arg->minor,
|
||||
arg->max_readahead,
|
||||
arg->flags);
|
||||
flags);
|
||||
|
||||
for(uint64_t i = 0; i < (sizeof(arg->flags)*8); i++)
|
||||
for(uint64_t i = 0; i < (sizeof(flags)*8); i++)
|
||||
{
|
||||
const char *str;
|
||||
|
||||
if(!(arg->flags & (1ULL << i)))
|
||||
if(!(flags & (1ULL << i)))
|
||||
continue;
|
||||
|
||||
str = fuse_flag_to_str(i);
|
||||
if(str == NULL)
|
||||
continue;
|
||||
|
||||
fprintf(g_OUTPUT,"%s,",str);
|
||||
fprintf(g_OUTPUT,"%s, ",str);
|
||||
}
|
||||
|
||||
fprintf(g_OUTPUT,
|
||||
|
||||
+14
-5
@@ -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>
|
||||
|
||||
@@ -1374,7 +1375,7 @@ set_path_info(struct fuse *f,
|
||||
return -ENOMEM;
|
||||
|
||||
e->ino = node->nodeid;
|
||||
e->generation = f->nodeid_gen.generation;
|
||||
e->generation = ((e->ino == FUSE_ROOT_ID) ? 0 : f->nodeid_gen.generation);
|
||||
|
||||
pthread_mutex_lock(&f->lock);
|
||||
update_stat(node,&e->attr);
|
||||
@@ -1385,6 +1386,15 @@ set_path_info(struct fuse *f,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
lookup requests only come in for FUSE_ROOT_ID when a "parent of
|
||||
child of root node" request is made. This can happen when using
|
||||
EXPORT_SUPPORT=true and a file handle is used to keep a reference to
|
||||
a node which has been forgotten. Mostly a NFS concern but not
|
||||
excluslively. Root node always has a nodeid of 1 and generation of
|
||||
0. To ensure this set_path_info() explicitly ensures the root id has
|
||||
a generation of 0.
|
||||
*/
|
||||
static
|
||||
int
|
||||
lookup_path(struct fuse *f,
|
||||
@@ -1522,7 +1532,6 @@ fuse_lib_init(void *data,
|
||||
|
||||
memset(c,0,sizeof(*c));
|
||||
c->ctx.fuse = f;
|
||||
conn->want |= FUSE_CAP_EXPORT_SUPPORT;
|
||||
f->fs->op.init(conn);
|
||||
}
|
||||
|
||||
@@ -3899,7 +3908,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++)
|
||||
@@ -3925,7 +3934,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();
|
||||
@@ -3934,7 +3943,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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+19
-13
@@ -6,7 +6,6 @@
|
||||
#include "fmt/core.h"
|
||||
#include "make_unique.hpp"
|
||||
#include "scope_guard.hpp"
|
||||
#include "syslog.h"
|
||||
#include "thread_pool.hpp"
|
||||
|
||||
#include "fuse_i.h"
|
||||
@@ -26,6 +25,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cassert>
|
||||
@@ -449,7 +449,9 @@ pin_threads(const std::vector<pthread_t> read_threads_,
|
||||
if(type_ == "R1PPSP")
|
||||
return ::pin_threads_R1PPSP(read_threads_,process_threads_);
|
||||
|
||||
syslog_warning("Invalid pin-threads value, ignoring: %s",type_.c_str());
|
||||
syslog(LOG_WARNING,
|
||||
"Invalid pin-threads value, ignoring: %s",
|
||||
type_.c_str());
|
||||
}
|
||||
|
||||
static
|
||||
@@ -488,10 +490,13 @@ fuse_session_loop_mt(struct fuse_session *se_,
|
||||
|
||||
if(process_thread_count > 0)
|
||||
process_tp = std::make_shared<ThreadPool>(process_thread_count,
|
||||
process_thread_queue_depth,
|
||||
(process_thread_count *
|
||||
process_thread_queue_depth),
|
||||
"fuse.process");
|
||||
|
||||
read_tp = std::make_unique<ThreadPool>(read_thread_count,1,"fuse.read");
|
||||
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++)
|
||||
@@ -510,15 +515,16 @@ fuse_session_loop_mt(struct fuse_session *se_,
|
||||
|
||||
::pin_threads(read_threads,process_threads,pin_threads_type_);
|
||||
|
||||
syslog_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());
|
||||
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);
|
||||
|
||||
|
||||
+70
-33
@@ -275,6 +275,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
|
||||
@@ -292,6 +294,11 @@ fuse_reply_entry(fuse_req_t req,
|
||||
|
||||
fill_entry(&arg, e);
|
||||
|
||||
#ifdef NDEBUG
|
||||
// TODO: Add checks for cases where a node could be marked bad by
|
||||
// the kernel.
|
||||
#endif
|
||||
|
||||
return send_reply_ok(req, &arg, size);
|
||||
}
|
||||
|
||||
@@ -1090,6 +1097,11 @@ do_init(fuse_req_t req,
|
||||
struct fuse_init_in *arg = (struct fuse_init_in *) &hdr_[1];
|
||||
struct fuse_ll *f = req->f;
|
||||
size_t bufsize = fuse_chan_bufsize(req->ch);
|
||||
uint64_t inargflags;
|
||||
uint64_t outargflags;
|
||||
|
||||
inargflags = 0;
|
||||
outargflags = 0;
|
||||
|
||||
if(f->debug)
|
||||
debug_fuse_init_in(arg);
|
||||
@@ -1120,40 +1132,49 @@ do_init(fuse_req_t req,
|
||||
|
||||
if(arg->minor >= 6)
|
||||
{
|
||||
inargflags = arg->flags;
|
||||
if(inargflags & FUSE_INIT_EXT)
|
||||
inargflags |= (((uint64_t)arg->flags2) << 32);
|
||||
|
||||
if(arg->max_readahead < f->conn.max_readahead)
|
||||
f->conn.max_readahead = arg->max_readahead;
|
||||
if(arg->flags & FUSE_ASYNC_READ)
|
||||
|
||||
if(inargflags & FUSE_ASYNC_READ)
|
||||
f->conn.capable |= FUSE_CAP_ASYNC_READ;
|
||||
if(arg->flags & FUSE_POSIX_LOCKS)
|
||||
if(inargflags & FUSE_POSIX_LOCKS)
|
||||
f->conn.capable |= FUSE_CAP_POSIX_LOCKS;
|
||||
if(arg->flags & FUSE_ATOMIC_O_TRUNC)
|
||||
if(inargflags & FUSE_ATOMIC_O_TRUNC)
|
||||
f->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC;
|
||||
if(arg->flags & FUSE_EXPORT_SUPPORT)
|
||||
if(inargflags & FUSE_EXPORT_SUPPORT)
|
||||
f->conn.capable |= FUSE_CAP_EXPORT_SUPPORT;
|
||||
if(arg->flags & FUSE_BIG_WRITES)
|
||||
if(inargflags & FUSE_BIG_WRITES)
|
||||
f->conn.capable |= FUSE_CAP_BIG_WRITES;
|
||||
if(arg->flags & FUSE_DONT_MASK)
|
||||
if(inargflags & FUSE_DONT_MASK)
|
||||
f->conn.capable |= FUSE_CAP_DONT_MASK;
|
||||
if(arg->flags & FUSE_FLOCK_LOCKS)
|
||||
if(inargflags & FUSE_FLOCK_LOCKS)
|
||||
f->conn.capable |= FUSE_CAP_FLOCK_LOCKS;
|
||||
if(arg->flags & FUSE_POSIX_ACL)
|
||||
if(inargflags & FUSE_POSIX_ACL)
|
||||
f->conn.capable |= FUSE_CAP_POSIX_ACL;
|
||||
if(arg->flags & FUSE_CACHE_SYMLINKS)
|
||||
if(inargflags & FUSE_CACHE_SYMLINKS)
|
||||
f->conn.capable |= FUSE_CAP_CACHE_SYMLINKS;
|
||||
if(arg->flags & FUSE_ASYNC_DIO)
|
||||
if(inargflags & FUSE_ASYNC_DIO)
|
||||
f->conn.capable |= FUSE_CAP_ASYNC_DIO;
|
||||
if(arg->flags & FUSE_PARALLEL_DIROPS)
|
||||
if(inargflags & FUSE_PARALLEL_DIROPS)
|
||||
f->conn.capable |= FUSE_CAP_PARALLEL_DIROPS;
|
||||
if(arg->flags & FUSE_MAX_PAGES)
|
||||
if(inargflags & FUSE_MAX_PAGES)
|
||||
f->conn.capable |= FUSE_CAP_MAX_PAGES;
|
||||
if(arg->flags & FUSE_WRITEBACK_CACHE)
|
||||
if(inargflags & FUSE_WRITEBACK_CACHE)
|
||||
f->conn.capable |= FUSE_CAP_WRITEBACK_CACHE;
|
||||
if(arg->flags & FUSE_DO_READDIRPLUS)
|
||||
if(inargflags & FUSE_DO_READDIRPLUS)
|
||||
f->conn.capable |= FUSE_CAP_READDIR_PLUS;
|
||||
if(arg->flags & FUSE_READDIRPLUS_AUTO)
|
||||
if(inargflags & FUSE_READDIRPLUS_AUTO)
|
||||
f->conn.capable |= FUSE_CAP_READDIR_PLUS_AUTO;
|
||||
if(arg->flags & FUSE_SETXATTR_EXT)
|
||||
if(inargflags & FUSE_SETXATTR_EXT)
|
||||
f->conn.capable |= FUSE_CAP_SETXATTR_EXT;
|
||||
if(inargflags & FUSE_DIRECT_IO_ALLOW_MMAP)
|
||||
f->conn.capable |= FUSE_CAP_DIRECT_IO_ALLOW_MMAP;
|
||||
if(inargflags & FUSE_CREATE_SUPP_GROUP)
|
||||
f->conn.capable |= FUSE_CAP_CREATE_SUPP_GROUP;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1184,44 +1205,57 @@ do_init(fuse_req_t req,
|
||||
if(f->op.init)
|
||||
f->op.init(f->userdata, &f->conn);
|
||||
|
||||
if((arg->flags & FUSE_MAX_PAGES) && (f->conn.want & FUSE_CAP_MAX_PAGES))
|
||||
outargflags = outarg.flags;
|
||||
if((inargflags & FUSE_MAX_PAGES) && (f->conn.want & FUSE_CAP_MAX_PAGES))
|
||||
{
|
||||
outarg.flags |= FUSE_MAX_PAGES;
|
||||
outargflags |= FUSE_MAX_PAGES;
|
||||
outarg.max_pages = f->conn.max_pages;
|
||||
|
||||
msgbuf_set_bufsize(outarg.max_pages + 1);
|
||||
}
|
||||
|
||||
if(f->conn.want & FUSE_CAP_ASYNC_READ)
|
||||
outarg.flags |= FUSE_ASYNC_READ;
|
||||
outargflags |= FUSE_ASYNC_READ;
|
||||
if(f->conn.want & FUSE_CAP_POSIX_LOCKS)
|
||||
outarg.flags |= FUSE_POSIX_LOCKS;
|
||||
outargflags |= FUSE_POSIX_LOCKS;
|
||||
if(f->conn.want & FUSE_CAP_ATOMIC_O_TRUNC)
|
||||
outarg.flags |= FUSE_ATOMIC_O_TRUNC;
|
||||
outargflags |= FUSE_ATOMIC_O_TRUNC;
|
||||
if(f->conn.want & FUSE_CAP_EXPORT_SUPPORT)
|
||||
outarg.flags |= FUSE_EXPORT_SUPPORT;
|
||||
outargflags |= FUSE_EXPORT_SUPPORT;
|
||||
if(f->conn.want & FUSE_CAP_BIG_WRITES)
|
||||
outarg.flags |= FUSE_BIG_WRITES;
|
||||
outargflags |= FUSE_BIG_WRITES;
|
||||
if(f->conn.want & FUSE_CAP_DONT_MASK)
|
||||
outarg.flags |= FUSE_DONT_MASK;
|
||||
outargflags |= FUSE_DONT_MASK;
|
||||
if(f->conn.want & FUSE_CAP_FLOCK_LOCKS)
|
||||
outarg.flags |= FUSE_FLOCK_LOCKS;
|
||||
outargflags |= FUSE_FLOCK_LOCKS;
|
||||
if(f->conn.want & FUSE_CAP_POSIX_ACL)
|
||||
outarg.flags |= FUSE_POSIX_ACL;
|
||||
outargflags |= FUSE_POSIX_ACL;
|
||||
if(f->conn.want & FUSE_CAP_CACHE_SYMLINKS)
|
||||
outarg.flags |= FUSE_CACHE_SYMLINKS;
|
||||
outargflags |= FUSE_CACHE_SYMLINKS;
|
||||
if(f->conn.want & FUSE_CAP_ASYNC_DIO)
|
||||
outarg.flags |= FUSE_ASYNC_DIO;
|
||||
outargflags |= FUSE_ASYNC_DIO;
|
||||
if(f->conn.want & FUSE_CAP_PARALLEL_DIROPS)
|
||||
outarg.flags |= FUSE_PARALLEL_DIROPS;
|
||||
outargflags |= FUSE_PARALLEL_DIROPS;
|
||||
if(f->conn.want & FUSE_CAP_WRITEBACK_CACHE)
|
||||
outarg.flags |= FUSE_WRITEBACK_CACHE;
|
||||
outargflags |= FUSE_WRITEBACK_CACHE;
|
||||
if(f->conn.want & FUSE_CAP_READDIR_PLUS)
|
||||
outarg.flags |= FUSE_DO_READDIRPLUS;
|
||||
outargflags |= FUSE_DO_READDIRPLUS;
|
||||
if(f->conn.want & FUSE_CAP_READDIR_PLUS_AUTO)
|
||||
outarg.flags |= FUSE_READDIRPLUS_AUTO;
|
||||
outargflags |= FUSE_READDIRPLUS_AUTO;
|
||||
if(f->conn.want & FUSE_CAP_SETXATTR_EXT)
|
||||
outarg.flags |= FUSE_SETXATTR_EXT;
|
||||
outargflags |= FUSE_SETXATTR_EXT;
|
||||
if(f->conn.want & FUSE_CAP_CREATE_SUPP_GROUP)
|
||||
outargflags |= FUSE_CREATE_SUPP_GROUP;
|
||||
if(f->conn.want & FUSE_CAP_DIRECT_IO_ALLOW_MMAP)
|
||||
outargflags |= FUSE_DIRECT_IO_ALLOW_MMAP;
|
||||
|
||||
if(inargflags & FUSE_INIT_EXT)
|
||||
{
|
||||
outargflags |= FUSE_INIT_EXT;
|
||||
outarg.flags2 = (outargflags >> 32);
|
||||
}
|
||||
|
||||
outarg.flags = outargflags;
|
||||
|
||||
outarg.max_readahead = f->conn.max_readahead;
|
||||
outarg.max_write = f->conn.max_write;
|
||||
@@ -1240,6 +1274,9 @@ do_init(fuse_req_t req,
|
||||
outarg.congestion_threshold = f->conn.congestion_threshold;
|
||||
}
|
||||
|
||||
if(f->conn.proto_minor >= 23)
|
||||
outarg.time_gran = 1;
|
||||
|
||||
size_t outargsize;
|
||||
if(arg->minor < 5)
|
||||
outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
|
||||
|
||||
+11
-10
@@ -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;
|
||||
|
||||
@@ -1,114 +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_debug(const char *format_,
|
||||
...)
|
||||
{
|
||||
va_list valist;
|
||||
|
||||
va_start(valist,format_);
|
||||
syslog_vlog(LOG_DEBUG,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);
|
||||
}
|
||||
+413
-139
@@ -106,16 +106,22 @@ If you don\[cq]t already know that you have a special use case then just
|
||||
start with one of the following option sets.
|
||||
.SS You need \f[C]mmap\f[R] (used by rtorrent and many sqlite3 base software)
|
||||
.PP
|
||||
\f[C]cache.files=partial,dropcacheonclose=true,category.create=mfs\f[R]
|
||||
\f[C]cache.files=auto-full,dropcacheonclose=true,category.create=mfs\f[R]
|
||||
.PP
|
||||
or if you are on a Linux kernel >= 6.6.x mergerfs will enable a mode
|
||||
that allows shared mmap when \f[C]cache.files=off\f[R].
|
||||
To be sure of the best performance between \f[C]cache.files=off\f[R] and
|
||||
\f[C]cache.files=auto-full\f[R] you\[cq]ll need to do your own
|
||||
benchmarking but often \f[C]off\f[R] is faster.
|
||||
.SS You don\[cq]t need \f[C]mmap\f[R]
|
||||
.PP
|
||||
\f[C]cache.files=off,dropcacheonclose=true,category.create=mfs\f[R]
|
||||
.SS Command Line
|
||||
.PP
|
||||
\f[C]mergerfs -o cache.files=partial,dropcacheonclose=true,category.create=mfs /mnt/hdd0:/mnt/hdd1 /media\f[R]
|
||||
\f[C]mergerfs -o cache.files=auto-full,dropcacheonclose=true,category.create=mfs /mnt/hdd0:/mnt/hdd1 /media\f[R]
|
||||
.SS /etc/fstab
|
||||
.PP
|
||||
\f[C]/mnt/hdd0:/mnt/hdd1 /media fuse.mergerfs cache.files=partial,dropcacheonclose=true,category.create=mfs 0 0\f[R]
|
||||
\f[C]/mnt/hdd0:/mnt/hdd1 /media mergerfs cache.files=auto-full,dropcacheonclose=true,category.create=mfs 0 0\f[R]
|
||||
.SS systemd mount
|
||||
.PP
|
||||
https://github.com/trapexit/mergerfs/wiki/systemd
|
||||
@@ -130,7 +136,7 @@ Type=simple
|
||||
KillMode=none
|
||||
ExecStart=/usr/bin/mergerfs \[rs]
|
||||
-f \[rs]
|
||||
-o cache.files=partial \[rs]
|
||||
-o cache.files=auto-full \[rs]
|
||||
-o dropcacheonclose=true \[rs]
|
||||
-o category.create=mfs \[rs]
|
||||
/mnt/hdd0:/mnt/hdd1 \[rs]
|
||||
@@ -185,6 +191,13 @@ Recommended when
|
||||
caching.
|
||||
(default: false)
|
||||
.IP \[bu] 2
|
||||
\f[B]direct-io-allow-mmap=BOOL\f[R]: On newer kernels (>= 6.6) it is
|
||||
possible to disable file page caching while still allowing for shared
|
||||
mmap support.
|
||||
mergerfs will enable this feature if available but an option is provided
|
||||
to turn it off for testing and debugging purposes.
|
||||
(default: true)
|
||||
.IP \[bu] 2
|
||||
\f[B]symlinkify=BOOL\f[R]: When enabled and a file is not writable and
|
||||
its mtime or ctime is older than \f[B]symlinkify_timeout\f[R] files will
|
||||
be reported as symlinks to the original files.
|
||||
@@ -214,6 +227,10 @@ This means files, when renamed or linked, will stay on the same
|
||||
filesystem.
|
||||
(default: false)
|
||||
.IP \[bu] 2
|
||||
\f[B]export-support=BOOL\f[R]: Sets a low-level FUSE feature intended to
|
||||
indicate the filesystem can support being exported via NFS.
|
||||
(default: true)
|
||||
.IP \[bu] 2
|
||||
\f[B]security_capability=BOOL\f[R]: If false return ENOATTR when xattr
|
||||
security.capability is queried.
|
||||
(default: true)
|
||||
@@ -321,6 +338,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.
|
||||
@@ -421,8 +443,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.
|
||||
@@ -507,8 +529,8 @@ tools use \f[B]/etc/fstab\f[R].
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/hdd*:/mnt/ssd /media fuse.mergerfs minfreespace=16G 0 0
|
||||
# <file system> <mount point> <type> <options> <dump> <pass>
|
||||
/mnt/hdd*:/mnt/ssd /media mergerfs minfreespace=16G 0 0
|
||||
\f[R]
|
||||
.fi
|
||||
.PP
|
||||
@@ -542,7 +564,7 @@ Most software does not to care what the values are but those that do
|
||||
often break if a value changes unexpectedly.
|
||||
The tool \f[C]find\f[R] will abort a directory walk if it sees a
|
||||
directory inode change.
|
||||
NFS will return stale handle errors if the inode changes out of band.
|
||||
NFS can return stale handle errors if the inode changes out of band.
|
||||
File dedup tools will usually leverage device ids and inodes as a
|
||||
shortcut in searching for duplicate files and would resort to full file
|
||||
comparisons should it find different inode values.
|
||||
@@ -830,8 +852,20 @@ 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.
|
||||
.SS export-support
|
||||
.PP
|
||||
In theory this flag should not be exposed to the end user.
|
||||
It is a low-level FUSE flag which indicates whether or not the kernel
|
||||
can send certain kinds of messages to it for the purposes of using with
|
||||
NFS.
|
||||
mergerfs does support these messages but due bugs and quirks found in
|
||||
the kernel and mergerfs this option is provided just in case it is
|
||||
needed for debugging.
|
||||
.PP
|
||||
Given that this flag is set when the FUSE connection is first initiated
|
||||
it is not possible to change during run time.
|
||||
.SH FUNCTIONS, CATEGORIES and POLICIES
|
||||
.PP
|
||||
The POSIX filesystem API is made up of a number of functions.
|
||||
@@ -1140,7 +1174,7 @@ T}
|
||||
.TE
|
||||
.SS func.readdir
|
||||
.PP
|
||||
examples: \f[C]fuse.readdir=seq\f[R], \f[C]fuse.readdir=cor:4\f[R]
|
||||
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.
|
||||
@@ -1169,7 +1203,7 @@ 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]fuse.readdir=cosr:N\f[R] where
|
||||
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{
|
||||
@@ -1185,7 +1219,7 @@ 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]fuse.readdir=cor:N\f[R] where
|
||||
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
|
||||
@@ -1321,6 +1355,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
|
||||
@@ -1530,8 +1582,8 @@ apply those before mounting the new version.
|
||||
\f[C]
|
||||
$ sudo mount /mnt/mergerfs
|
||||
$ mount | grep mergerfs
|
||||
media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type fuse.mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
media on /mnt/mergerfs type mergerfs (rw,relatime,user_id=0,group_id=0,default_permissions,allow_other)
|
||||
\f[R]
|
||||
.fi
|
||||
.PP
|
||||
@@ -1699,6 +1751,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
|
||||
@@ -2250,18 +2404,13 @@ Use \f[C]cache.files=off\f[R] and/or \f[C]dropcacheonclose=true\f[R].
|
||||
See the section on page caching.
|
||||
.SS NFS clients returning ESTALE / Stale file handle
|
||||
.PP
|
||||
NFS does not like out of band changes.
|
||||
That is especially true of inode values.
|
||||
.PP
|
||||
Be sure to use the following options:
|
||||
.IP \[bu] 2
|
||||
noforget
|
||||
.IP \[bu] 2
|
||||
inodecalc=path-hash
|
||||
NFS generally does not like out of band changes.
|
||||
Take a look at the section on NFS in the #remote-filesystems for more
|
||||
details.
|
||||
.SS rtorrent fails with ENODEV (No such device)
|
||||
.PP
|
||||
Be sure to set \f[C]cache.files=partial|full|auto-full|per-processe\f[R]
|
||||
or turn off \f[C]direct_io\f[R].
|
||||
Be sure to set
|
||||
\f[C]cache.files=partial|full|auto-full|per-processe\f[R].
|
||||
rtorrent and some other applications use
|
||||
mmap (http://linux.die.net/man/2/mmap) to read and write to files and
|
||||
offer no fallback to traditional methods.
|
||||
@@ -2414,6 +2563,123 @@ There might be a hack to work around this (make mergerfs read the
|
||||
would be limited to Linux and the /etc/group DB.
|
||||
Preferably users would mount in the host group file into the containers
|
||||
or use a standard shared user & groups technology like NIS or LDAP.
|
||||
.SH Remote Filesystems
|
||||
.PP
|
||||
Many users ask about compatibility with remote filesystems.
|
||||
This section is to describe any known issues or quirks when using
|
||||
mergerfs with common remote filesystems.
|
||||
.PP
|
||||
Keep in mind that, like with caching, it is not a good idea to change
|
||||
the contents of the remote filesystem
|
||||
out-of-band (https://en.wikipedia.org/wiki/Out-of-band).
|
||||
Meaning that you really shouldn\[cq]t change the contents of the
|
||||
underlying filesystems or mergerfs on the server hosting the remote
|
||||
filesystem.
|
||||
Doing so can lead to weird behavior, inconsistency, errors, and even
|
||||
data corruption should multiple programs try to write or read the same
|
||||
data at the same time.
|
||||
This isn\[cq]t to say you can\[cq]t do it or that data corruption is
|
||||
likely but it \f[I]could\f[R] happen.
|
||||
It is better to always use the remote filesystem.
|
||||
Even on the machine serving it.
|
||||
.SS NFS
|
||||
.PP
|
||||
NFS (https://en.wikipedia.org/wiki/Network_File_System) is a common
|
||||
remote filesystem on Unix/POSIX systems.
|
||||
Due to how NFS works there are some settings which need to be set in
|
||||
order for mergerfs to work with it.
|
||||
.PP
|
||||
It should be noted that NFS and FUSE (the technology mergerfs uses) do
|
||||
not work perfectly with one another due to certain design choices in
|
||||
FUSE (and mergerfs.) Due to these issues it is generally recommended to
|
||||
use SMB when possible till situations change.
|
||||
That said mergerfs should generally work as an export of NFS and issues
|
||||
discovered should still be reported.
|
||||
.PP
|
||||
To ensure compatibility between mergerfs and NFS use the following
|
||||
settings.
|
||||
.PP
|
||||
mergerfs settings: * noforget * inodecalc=path-hash
|
||||
.PP
|
||||
NFS export settings: * fsid=UUID * no_root_squash
|
||||
.PP
|
||||
\f[C]noforget\f[R] is needed because NFS uses the
|
||||
\f[C]name_to_handle_at\f[R] and \f[C]open_by_handle_at\f[R] functions
|
||||
which allow a program to keep a reference to a file without technically
|
||||
having it open in the typical sense.
|
||||
The problem is that FUSE has no way to know that NFS has a handle that
|
||||
it will later use to open the file again.
|
||||
As a result it is possible for the kernel to tell mergerfs to forget
|
||||
about the node and should NFS ever ask for that node\[cq]s details in
|
||||
the future it would have nothing to respond with.
|
||||
Keeping nodes around forever is not ideal but at the moment the only way
|
||||
to manage the situation.
|
||||
.PP
|
||||
\f[C]inodecalc=path-hash\f[R] is needed because NFS is sensitive to
|
||||
out-of-band changes.
|
||||
FUSE doesn\[cq]t care if a file\[cq]s inode value changes but NFS, being
|
||||
stateful, does.
|
||||
So if you used the default inode calculation algorithm then it is
|
||||
possible that if you changed a file or updated a directory the file
|
||||
mergerfs will use will be on a different branch and therefore the inode
|
||||
would change.
|
||||
This isn\[cq]t an ideal solution and others are being considered but it
|
||||
works for most situations.
|
||||
.PP
|
||||
\f[C]fsid=UUID\f[R] is needed because FUSE filesystems don\[cq]t have
|
||||
different \f[C]st_dev\f[R] values which can cause issues when exporting.
|
||||
The easiest thing to do is set each mergerfs export \f[C]fsid\f[R] to
|
||||
some random value.
|
||||
An easy way to generate a random value is to use the command line tool
|
||||
\f[C]uuid\f[R] or \f[C]uuidgen\f[R] or through a website such as
|
||||
uuidgenerator.net (https://www.uuidgenerator.net/).
|
||||
.PP
|
||||
\f[C]no_root_squash\f[R] is not strictly necessary but can lead to
|
||||
confusing permission and ownership issues if root squashing is enabled.
|
||||
.SS SMB / CIFS
|
||||
.PP
|
||||
SMB (https://en.wikipedia.org/wiki/Server_Message_Block) is a protocol
|
||||
most used by Microsoft Windows systems to share file shares, printers,
|
||||
etc.
|
||||
However, due to the popularity for Windows, it is also supported on many
|
||||
other platforms including Linux.
|
||||
The most popular way of supporting SMB on Linux is via the software
|
||||
Samba.
|
||||
.PP
|
||||
Samba (https://en.wikipedia.org/wiki/Samba_(software)), and other ways
|
||||
of serving Linux filesystems, via SMB should work fine with mergerfs.
|
||||
The services do not tend to use the same technologies which NFS uses and
|
||||
therefore don\[cq]t have the same issues.
|
||||
There should not be an special settings required to use mergerfs with
|
||||
Samba.
|
||||
However, CIFSD (https://en.wikipedia.org/wiki/CIFSD) and other programs
|
||||
have not been extensively tested.
|
||||
If you use mergerfs with CIFSD or other SMB servers please submit your
|
||||
experiences so these docs can be updated.
|
||||
.SS SSHFS
|
||||
.PP
|
||||
SSHFS (https://en.wikipedia.org/wiki/SSHFS) is a FUSE filesystem
|
||||
leveraging SSH as the connection and transport layer.
|
||||
While often simpler to setup when compared to NFS or Samba the
|
||||
performance can be lacking and the project is very much in maintenance
|
||||
mode.
|
||||
.PP
|
||||
There are no known issues using sshfs with mergerfs.
|
||||
You may want to use the following arguments to improve performance but
|
||||
your millage may vary.
|
||||
.IP \[bu] 2
|
||||
\f[C]-o Ciphers=arcfour\f[R]
|
||||
.IP \[bu] 2
|
||||
\f[C]-o Compression=no\f[R]
|
||||
.PP
|
||||
More info can be found
|
||||
here (https://ideatrash.net/2016/08/odds-and-ends-optimizing-sshfs-moving.html).
|
||||
.SS Other
|
||||
.PP
|
||||
There are other remote filesystems but none popularly used to serve
|
||||
mergerfs.
|
||||
If you use something not listed above feel free to reach out and I will
|
||||
add it to the list.
|
||||
.SH FAQ
|
||||
.SS How well does mergerfs scale? Is it \[lq]production ready?\[rq]
|
||||
.PP
|
||||
@@ -2550,7 +2816,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.
|
||||
@@ -2580,6 +2848,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.
|
||||
@@ -2643,8 +2921,8 @@ executable bit set.
|
||||
A common mistake by users new to Linux is to \f[C]chmod -R 644\f[R] when
|
||||
they should have \f[C]chmod -R u=rwX,go=rX\f[R].
|
||||
.PP
|
||||
If using a network filesystem such as NFS, SMB, CIFS (Samba) be sure to
|
||||
pay close attention to anything regarding permissioning and users.
|
||||
If using a network filesystem such as NFS or SMB (Samba) be sure to pay
|
||||
close attention to anything regarding permissioning and users.
|
||||
Root squashing and user translation for instance has bitten a few
|
||||
mergerfs users.
|
||||
Some of these also affect the use of mergerfs from container platforms
|
||||
@@ -2703,93 +2981,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
|
||||
@@ -2856,33 +3047,14 @@ mergerfs uses available space for statfs calculations.
|
||||
If you\[cq]ve reserved space for root then it won\[cq]t show up.
|
||||
.PP
|
||||
You can remove the reserve by running: \f[C]tune2fs -m 0 <device>\f[R]
|
||||
.SS Can mergerfs mounts be exported over NFS?
|
||||
.PP
|
||||
Yes, however if you do anything which may changes files out of band
|
||||
(including for example using the \f[C]newest\f[R] policy) it will result
|
||||
in \[lq]stale file handle\[rq] errors unless properly setup.
|
||||
.PP
|
||||
Be sure to use the following options:
|
||||
.IP \[bu] 2
|
||||
noforget
|
||||
.IP \[bu] 2
|
||||
inodecalc=path-hash
|
||||
.SS Can mergerfs mounts be exported over Samba / SMB?
|
||||
.PP
|
||||
Yes.
|
||||
While some users have reported problems it appears to always be related
|
||||
to how Samba is setup in relation to permissions.
|
||||
.SS Can mergerfs mounts be used over SSHFS?
|
||||
.PP
|
||||
Yes.
|
||||
.SS I notice massive slowdowns of writes when enabling cache.files.
|
||||
.PP
|
||||
When file caching is enabled in any form (\f[C]cache.files!=off\f[R] or
|
||||
\f[C]direct_io=false\f[R]) it will issue \f[C]getxattr\f[R] requests for
|
||||
\f[C]security.capability\f[R] prior to \f[I]every single write\f[R].
|
||||
When file caching is enabled in any form (\f[C]cache.files!=off\f[R]) it
|
||||
will issue \f[C]getxattr\f[R] requests for \f[C]security.capability\f[R]
|
||||
prior to \f[I]every single write\f[R].
|
||||
This will usually result in a performance degradation, especially when
|
||||
using a network filesystem (such as NFS or CIFS/SMB/Samba.)
|
||||
Unfortunately at this moment the kernel is not caching the response.
|
||||
using a network filesystem (such as NFS or SMB.) Unfortunately at this
|
||||
moment the kernel is not caching the response.
|
||||
.PP
|
||||
To work around this situation mergerfs offers a few solutions.
|
||||
.IP "1." 3
|
||||
@@ -2945,6 +3117,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+9
-2
@@ -58,6 +58,8 @@ namespace l
|
||||
IFERT("branches-mount-timeout");
|
||||
IFERT("cache.symlinks");
|
||||
IFERT("cache.writeback");
|
||||
IFERT("direct-io-allow-mmap");
|
||||
IFERT("export-support");
|
||||
IFERT("fsname");
|
||||
IFERT("fuse_msg_size");
|
||||
IFERT("mount");
|
||||
@@ -93,9 +95,12 @@ Config::Config()
|
||||
cache_symlinks(false),
|
||||
category(func),
|
||||
direct_io(false),
|
||||
direct_io_allow_mmap(true),
|
||||
dropcacheonclose(false),
|
||||
fsname(),
|
||||
export_support(true),
|
||||
flushonclose(FlushOnClose::ENUM::OPENED_FOR_WRITE),
|
||||
follow_symlinks(FollowSymlinks::ENUM::NEVER),
|
||||
fsname(),
|
||||
func(),
|
||||
fuse_msg_size(FUSE_MAX_MAX_PAGES),
|
||||
ignorepponrename(false),
|
||||
@@ -109,7 +114,6 @@ Config::Config()
|
||||
nfsopenhack(NFSOpenHack::ENUM::OFF),
|
||||
nullrw(false),
|
||||
parallel_direct_writes(false),
|
||||
pid(::getpid()),
|
||||
posix_acl(false),
|
||||
readahead(0),
|
||||
readdir("seq"),
|
||||
@@ -148,7 +152,10 @@ Config::Config()
|
||||
_map["category.create"] = &category.create;
|
||||
_map["category.search"] = &category.search;
|
||||
_map["direct_io"] = &direct_io;
|
||||
_map["direct-io-allow-mmap"] = &direct_io_allow_mmap;
|
||||
_map["dropcacheonclose"] = &dropcacheonclose;
|
||||
_map["export-support"] = &export_support;
|
||||
_map["flush-on-close"] = &flushonclose;
|
||||
_map["follow-symlinks"] = &follow_symlinks;
|
||||
_map["fsname"] = &fsname;
|
||||
_map["func.access"] = &func.access;
|
||||
|
||||
+7
-2
@@ -19,7 +19,9 @@
|
||||
#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"
|
||||
@@ -115,9 +117,12 @@ public:
|
||||
ConfigBOOL cache_symlinks;
|
||||
Categories category;
|
||||
ConfigBOOL direct_io;
|
||||
ConfigBOOL direct_io_allow_mmap;
|
||||
ConfigBOOL dropcacheonclose;
|
||||
ConfigSTR fsname;
|
||||
ConfigBOOL export_support;
|
||||
FlushOnClose flushonclose;
|
||||
FollowSymlinks follow_symlinks;
|
||||
ConfigSTR fsname;
|
||||
Funcs func;
|
||||
ConfigUINT64 fuse_msg_size;
|
||||
ConfigBOOL ignorepponrename;
|
||||
@@ -132,7 +137,7 @@ public:
|
||||
NFSOpenHack nfsopenhack;
|
||||
ConfigBOOL nullrw;
|
||||
ConfigBOOL parallel_direct_writes;
|
||||
ConfigUINT64 pid;
|
||||
ConfigGetPid pid;
|
||||
ConfigBOOL posix_acl;
|
||||
ConfigUINT64 readahead;
|
||||
FUSE::ReadDir readdir;
|
||||
|
||||
@@ -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,21 +16,39 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "config_flushonclose.hpp"
|
||||
#include "ef.hpp"
|
||||
#include "errno.hpp"
|
||||
|
||||
#include "branches.hpp"
|
||||
|
||||
#include "fuse.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace FUSE
|
||||
template<>
|
||||
std::string
|
||||
FlushOnClose::to_string() const
|
||||
{
|
||||
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);
|
||||
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
|
||||
FlushOnClose::from_string(const std::string &s_)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -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,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "branches.hpp"
|
||||
|
||||
#include "fuse.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include "enum.hpp"
|
||||
|
||||
|
||||
namespace FUSE
|
||||
{
|
||||
int
|
||||
readdir_linux(const Branches::CPtr &branches,
|
||||
const char *dirname,
|
||||
fuse_dirents_t *buf);
|
||||
}
|
||||
enum class FlushOnCloseEnum
|
||||
{
|
||||
NEVER,
|
||||
OPENED_FOR_WRITE,
|
||||
ALWAYS
|
||||
};
|
||||
|
||||
typedef Enum<FlushOnCloseEnum> FlushOnClose;
|
||||
@@ -18,14 +18,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <syslog.h>
|
||||
#include "tofrom_string.hpp"
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
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, ...);
|
||||
void syslog_error(const char *format, ...);
|
||||
void syslog_close();
|
||||
class ConfigGetPid : public ToFromString
|
||||
{
|
||||
public:
|
||||
std::string
|
||||
to_string() const final
|
||||
{
|
||||
return fmt::format("{}",::getpid());
|
||||
}
|
||||
|
||||
int
|
||||
from_string(const std::string &) final
|
||||
{
|
||||
return -EROFS;
|
||||
}
|
||||
};
|
||||
+7
-6
@@ -1,4 +1,4 @@
|
||||
// Formatting library for C++ - dynamic format arguments
|
||||
// Formatting library for C++ - dynamic argument lists
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
@@ -22,8 +22,9 @@ template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||
|
||||
template <typename T> const T& unwrap(const T& v) { return v; }
|
||||
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
|
||||
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
|
||||
template <typename T>
|
||||
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
|
||||
return static_cast<const T&>(v);
|
||||
}
|
||||
|
||||
@@ -50,7 +51,7 @@ class dynamic_arg_list {
|
||||
std::unique_ptr<node<>> head_;
|
||||
|
||||
public:
|
||||
template <typename T, typename Arg> const T& push(const Arg& arg) {
|
||||
template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
|
||||
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||
auto& value = new_node->value;
|
||||
new_node->next = std::move(head_);
|
||||
@@ -110,14 +111,14 @@ class dynamic_format_arg_store
|
||||
|
||||
friend class basic_format_args<Context>;
|
||||
|
||||
unsigned long long get_types() const {
|
||||
auto get_types() const -> unsigned long long {
|
||||
return detail::is_unpacked_bit | data_.size() |
|
||||
(named_info_.empty()
|
||||
? 0ULL
|
||||
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||
}
|
||||
|
||||
const basic_format_arg<Context>* data() const {
|
||||
auto data() const -> const basic_format_arg<Context>* {
|
||||
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||
}
|
||||
|
||||
|
||||
+230
-257
@@ -18,7 +18,7 @@
|
||||
#include <ostream>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
#include "ostream.h" // formatbuf
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
@@ -72,7 +72,8 @@ template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed ==
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
|
||||
-> To {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
@@ -101,7 +102,8 @@ template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed !=
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
|
||||
-> To {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
@@ -133,7 +135,8 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)
|
||||
-> To {
|
||||
ec = 0;
|
||||
return from;
|
||||
} // function
|
||||
@@ -154,7 +157,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
// clang-format on
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {
|
||||
ec = 0;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
@@ -176,7 +179,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {
|
||||
ec = 0;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
return from;
|
||||
@@ -188,8 +191,8 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) -> To {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
@@ -240,8 +243,8 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) -> To {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
if (std::isnan(from.count())) {
|
||||
@@ -321,12 +324,12 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
|
||||
namespace detail {
|
||||
template <typename T = void> struct null {};
|
||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||
inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
|
||||
inline auto localtime_s(...) -> null<> { return null<>(); }
|
||||
inline auto gmtime_r(...) -> null<> { return null<>(); }
|
||||
inline auto gmtime_s(...) -> null<> { return null<>(); }
|
||||
|
||||
inline const std::locale& get_classic_locale() {
|
||||
inline auto get_classic_locale() -> const std::locale& {
|
||||
static const auto& locale = std::locale::classic();
|
||||
return locale;
|
||||
}
|
||||
@@ -336,8 +339,6 @@ template <typename CodeUnit> struct codecvt_result {
|
||||
CodeUnit buf[max_size];
|
||||
CodeUnit* end;
|
||||
};
|
||||
template <typename CodeUnit>
|
||||
constexpr const size_t codecvt_result<CodeUnit>::max_size;
|
||||
|
||||
template <typename CodeUnit>
|
||||
void write_codecvt(codecvt_result<CodeUnit>& out, string_view in_buf,
|
||||
@@ -377,8 +378,8 @@ auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)
|
||||
unit_t unit;
|
||||
write_codecvt(unit, in, loc);
|
||||
// In UTF-8 is used one to four one-byte code units.
|
||||
unicode_to_utf8<code_unit, basic_memory_buffer<char, unit_t::max_size * 4>>
|
||||
u;
|
||||
auto u =
|
||||
to_utf8<code_unit, basic_memory_buffer<char, unit_t::max_size * 4>>();
|
||||
if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)}))
|
||||
FMT_THROW(format_error("failed to format time"));
|
||||
return copy_str<char>(u.c_str(), u.c_str() + u.size(), out);
|
||||
@@ -408,8 +409,7 @@ inline void do_write(buffer<Char>& buf, const std::tm& time,
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& os = std::basic_ostream<Char>(&format_buf);
|
||||
os.imbue(loc);
|
||||
using iterator = std::ostreambuf_iterator<Char>;
|
||||
const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc);
|
||||
const auto& facet = std::use_facet<std::time_put<Char>>(loc);
|
||||
auto end = facet.put(os, os, Char(' '), &time, format, modifier);
|
||||
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
||||
}
|
||||
@@ -432,6 +432,51 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
|
||||
}
|
||||
|
||||
template <typename Rep1, typename Rep2>
|
||||
struct is_same_arithmetic_type
|
||||
: public std::integral_constant<bool,
|
||||
(std::is_integral<Rep1>::value &&
|
||||
std::is_integral<Rep2>::value) ||
|
||||
(std::is_floating_point<Rep1>::value &&
|
||||
std::is_floating_point<Rep2>::value)> {
|
||||
};
|
||||
|
||||
template <
|
||||
typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(is_same_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
auto fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
// Throwing version of safe_duration_cast is only available for
|
||||
// integer to integer or float to float casts.
|
||||
int ec;
|
||||
To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
|
||||
if (ec) FMT_THROW(format_error("cannot format duration"));
|
||||
return to;
|
||||
#else
|
||||
// Standard duration cast, may overflow.
|
||||
return std::chrono::duration_cast<To>(from);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <
|
||||
typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
|
||||
auto fmt_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
|
||||
// Mixed integer <-> float cast is not supported by safe_duration_cast.
|
||||
return std::chrono::duration_cast<To>(from);
|
||||
}
|
||||
|
||||
template <typename Duration>
|
||||
auto to_time_t(
|
||||
std::chrono::time_point<std::chrono::system_clock, Duration> time_point)
|
||||
-> std::time_t {
|
||||
// Cannot use std::chrono::system_clock::to_time_t since this would first
|
||||
// require a cast to std::chrono::system_clock::time_point, which could
|
||||
// overflow.
|
||||
return fmt_duration_cast<std::chrono::duration<std::time_t>>(
|
||||
time_point.time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
@@ -441,29 +486,29 @@ FMT_BEGIN_EXPORT
|
||||
expressed in local time. Unlike ``std::localtime``, this function is
|
||||
thread-safe on most platforms.
|
||||
*/
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
inline auto localtime(std::time_t time) -> std::tm {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t) : time_(t) {}
|
||||
|
||||
bool run() {
|
||||
auto run() -> bool {
|
||||
using namespace fmt::detail;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm* tm) { return tm != nullptr; }
|
||||
auto handle(std::tm* tm) -> bool { return tm != nullptr; }
|
||||
|
||||
bool handle(detail::null<>) {
|
||||
auto handle(detail::null<>) -> bool {
|
||||
using namespace fmt::detail;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
auto fallback(int res) -> bool { return res == 0; }
|
||||
|
||||
#if !FMT_MSC_VERSION
|
||||
bool fallback(detail::null<>) {
|
||||
auto fallback(detail::null<>) -> bool {
|
||||
using namespace fmt::detail;
|
||||
std::tm* tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
@@ -480,8 +525,8 @@ inline std::tm localtime(std::time_t time) {
|
||||
#if FMT_USE_LOCAL_TIME
|
||||
template <typename Duration>
|
||||
inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
|
||||
return localtime(std::chrono::system_clock::to_time_t(
|
||||
std::chrono::current_zone()->to_sys(time)));
|
||||
return localtime(
|
||||
detail::to_time_t(std::chrono::current_zone()->to_sys(time)));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -490,90 +535,49 @@ inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
|
||||
expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
|
||||
function is thread-safe on most platforms.
|
||||
*/
|
||||
inline std::tm gmtime(std::time_t time) {
|
||||
inline auto gmtime(std::time_t time) -> std::tm {
|
||||
struct dispatcher {
|
||||
std::time_t time_;
|
||||
std::tm tm_;
|
||||
|
||||
dispatcher(std::time_t t) : time_(t) {}
|
||||
|
||||
bool run() {
|
||||
auto run() -> bool {
|
||||
using namespace fmt::detail;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm* tm) { return tm != nullptr; }
|
||||
auto handle(std::tm* tm) -> bool { return tm != nullptr; }
|
||||
|
||||
bool handle(detail::null<>) {
|
||||
auto handle(detail::null<>) -> bool {
|
||||
using namespace fmt::detail;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
auto fallback(int res) -> bool { return res == 0; }
|
||||
|
||||
#if !FMT_MSC_VERSION
|
||||
bool fallback(detail::null<>) {
|
||||
auto fallback(detail::null<>) -> bool {
|
||||
std::tm* tm = std::gmtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != nullptr;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
dispatcher gt(time);
|
||||
auto gt = dispatcher(time);
|
||||
// Too big time values may be unsupported.
|
||||
if (!gt.run()) FMT_THROW(format_error("time_t value out of range"));
|
||||
return gt.tm_;
|
||||
}
|
||||
|
||||
inline std::tm gmtime(
|
||||
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||
return gmtime(std::chrono::system_clock::to_time_t(time_point));
|
||||
template <typename Duration>
|
||||
inline auto gmtime(
|
||||
std::chrono::time_point<std::chrono::system_clock, Duration> time_point)
|
||||
-> std::tm {
|
||||
return gmtime(detail::to_time_t(time_point));
|
||||
}
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
// DEPRECATED!
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end,
|
||||
format_specs<Char>& specs) -> const Char* {
|
||||
FMT_ASSERT(begin != end, "");
|
||||
auto align = align::none;
|
||||
auto p = begin + code_point_length(begin);
|
||||
if (end - p <= 0) p = begin;
|
||||
for (;;) {
|
||||
switch (to_ascii(*p)) {
|
||||
case '<':
|
||||
align = align::left;
|
||||
break;
|
||||
case '>':
|
||||
align = align::right;
|
||||
break;
|
||||
case '^':
|
||||
align = align::center;
|
||||
break;
|
||||
}
|
||||
if (align != align::none) {
|
||||
if (p != begin) {
|
||||
auto c = *begin;
|
||||
if (c == '}') return begin;
|
||||
if (c == '{') {
|
||||
throw_format_error("invalid fill character '{'");
|
||||
return begin;
|
||||
}
|
||||
specs.fill = {begin, to_unsigned(p - begin)};
|
||||
begin = p + 1;
|
||||
} else {
|
||||
++begin;
|
||||
}
|
||||
break;
|
||||
} else if (p == begin) {
|
||||
break;
|
||||
}
|
||||
p = begin;
|
||||
}
|
||||
specs.align = align;
|
||||
return begin;
|
||||
}
|
||||
namespace detail {
|
||||
|
||||
// Writes two-digit numbers a, b and c separated by sep to buf.
|
||||
// The method by Pavel Novikov based on
|
||||
@@ -609,7 +613,8 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
|
||||
template <typename Period>
|
||||
FMT_CONSTEXPR inline auto get_units() -> const char* {
|
||||
if (std::is_same<Period, std::atto>::value) return "as";
|
||||
if (std::is_same<Period, std::femto>::value) return "fs";
|
||||
if (std::is_same<Period, std::pico>::value) return "ps";
|
||||
@@ -627,8 +632,9 @@ template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
|
||||
if (std::is_same<Period, std::tera>::value) return "Ts";
|
||||
if (std::is_same<Period, std::peta>::value) return "Ps";
|
||||
if (std::is_same<Period, std::exa>::value) return "Es";
|
||||
if (std::is_same<Period, std::ratio<60>>::value) return "m";
|
||||
if (std::is_same<Period, std::ratio<60>>::value) return "min";
|
||||
if (std::is_same<Period, std::ratio<3600>>::value) return "h";
|
||||
if (std::is_same<Period, std::ratio<86400>>::value) return "d";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -664,9 +670,8 @@ auto write_padding(OutputIt out, pad_type pad) -> OutputIt {
|
||||
|
||||
// Parses a put_time-like format string and invokes handler actions.
|
||||
template <typename Char, typename Handler>
|
||||
FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
|
||||
const Char* end,
|
||||
Handler&& handler) {
|
||||
FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end,
|
||||
Handler&& handler) -> const Char* {
|
||||
if (begin == end || *begin == '}') return begin;
|
||||
if (*begin != '%') FMT_THROW(format_error("invalid format"));
|
||||
auto ptr = begin;
|
||||
@@ -997,25 +1002,25 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
|
||||
FMT_CONSTEXPR void on_tz_name() {}
|
||||
};
|
||||
|
||||
inline const char* tm_wday_full_name(int wday) {
|
||||
inline auto tm_wday_full_name(int wday) -> const char* {
|
||||
static constexpr const char* full_name_list[] = {
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday",
|
||||
"Thursday", "Friday", "Saturday"};
|
||||
return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?";
|
||||
}
|
||||
inline const char* tm_wday_short_name(int wday) {
|
||||
inline auto tm_wday_short_name(int wday) -> const char* {
|
||||
static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed",
|
||||
"Thu", "Fri", "Sat"};
|
||||
return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???";
|
||||
}
|
||||
|
||||
inline const char* tm_mon_full_name(int mon) {
|
||||
inline auto tm_mon_full_name(int mon) -> const char* {
|
||||
static constexpr const char* full_name_list[] = {
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"};
|
||||
return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?";
|
||||
}
|
||||
inline const char* tm_mon_short_name(int mon) {
|
||||
inline auto tm_mon_short_name(int mon) -> const char* {
|
||||
static constexpr const char* short_name_list[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||||
@@ -1047,21 +1052,21 @@ inline void tzset_once() {
|
||||
|
||||
// Converts value to Int and checks that it's in the range [0, upper).
|
||||
template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
inline Int to_nonnegative_int(T value, Int upper) {
|
||||
FMT_ASSERT(std::is_unsigned<Int>::value ||
|
||||
(value >= 0 && to_unsigned(value) <= to_unsigned(upper)),
|
||||
"invalid value");
|
||||
(void)upper;
|
||||
inline auto to_nonnegative_int(T value, Int upper) -> Int {
|
||||
if (!std::is_unsigned<Int>::value &&
|
||||
(value < 0 || to_unsigned(value) > to_unsigned(upper))) {
|
||||
FMT_THROW(fmt::format_error("chrono value is out of range"));
|
||||
}
|
||||
return static_cast<Int>(value);
|
||||
}
|
||||
template <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
inline Int to_nonnegative_int(T value, Int upper) {
|
||||
inline auto to_nonnegative_int(T value, Int upper) -> Int {
|
||||
if (value < 0 || value > static_cast<T>(upper))
|
||||
FMT_THROW(format_error("invalid value"));
|
||||
return static_cast<Int>(value);
|
||||
}
|
||||
|
||||
constexpr long long pow10(std::uint32_t n) {
|
||||
constexpr auto pow10(std::uint32_t n) -> long long {
|
||||
return n == 0 ? 1 : 10 * pow10(n - 1);
|
||||
}
|
||||
|
||||
@@ -1095,13 +1100,12 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
|
||||
std::chrono::seconds::rep>::type,
|
||||
std::ratio<1, detail::pow10(num_fractional_digits)>>;
|
||||
|
||||
const auto fractional =
|
||||
d - std::chrono::duration_cast<std::chrono::seconds>(d);
|
||||
const auto fractional = d - fmt_duration_cast<std::chrono::seconds>(d);
|
||||
const auto subseconds =
|
||||
std::chrono::treat_as_floating_point<
|
||||
typename subsecond_precision::rep>::value
|
||||
? fractional.count()
|
||||
: std::chrono::duration_cast<subsecond_precision>(fractional).count();
|
||||
: fmt_duration_cast<subsecond_precision>(fractional).count();
|
||||
auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds);
|
||||
const int num_digits = detail::count_digits(n);
|
||||
|
||||
@@ -1152,11 +1156,11 @@ void write_floating_seconds(memory_buffer& buf, Duration duration,
|
||||
num_fractional_digits = 6;
|
||||
}
|
||||
|
||||
format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"),
|
||||
std::fmod(val * static_cast<rep>(Duration::period::num) /
|
||||
static_cast<rep>(Duration::period::den),
|
||||
static_cast<rep>(60)),
|
||||
num_fractional_digits);
|
||||
fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"),
|
||||
std::fmod(val * static_cast<rep>(Duration::period::num) /
|
||||
static_cast<rep>(Duration::period::den),
|
||||
static_cast<rep>(60)),
|
||||
num_fractional_digits);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char,
|
||||
@@ -1217,8 +1221,7 @@ class tm_writer {
|
||||
return static_cast<int>(l);
|
||||
}
|
||||
|
||||
// Algorithm:
|
||||
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date
|
||||
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
|
||||
auto iso_year_weeks(long long curr_year) const noexcept -> int {
|
||||
const auto prev_year = curr_year - 1;
|
||||
const auto curr_p =
|
||||
@@ -1358,7 +1361,7 @@ class tm_writer {
|
||||
subsecs_(subsecs),
|
||||
tm_(tm) {}
|
||||
|
||||
OutputIt out() const { return out_; }
|
||||
auto out() const -> OutputIt { return out_; }
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
out_ = copy_str<Char>(begin, end, out_);
|
||||
@@ -1622,6 +1625,7 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
||||
FMT_CONSTEXPR void on_day_of_year() {}
|
||||
FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}
|
||||
FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}
|
||||
FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}
|
||||
@@ -1640,16 +1644,16 @@ struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
||||
|
||||
template <typename T,
|
||||
FMT_ENABLE_IF(std::is_integral<T>::value&& has_isfinite<T>::value)>
|
||||
inline bool isfinite(T) {
|
||||
inline auto isfinite(T) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
inline T mod(T x, int y) {
|
||||
inline auto mod(T x, int y) -> T {
|
||||
return x % static_cast<T>(y);
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
inline T mod(T x, int y) {
|
||||
inline auto mod(T x, int y) -> T {
|
||||
return std::fmod(x, static_cast<T>(y));
|
||||
}
|
||||
|
||||
@@ -1664,49 +1668,38 @@ template <typename T> struct make_unsigned_or_unchanged<T, true> {
|
||||
using type = typename std::make_unsigned<T>::type;
|
||||
};
|
||||
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
// throwing version of safe_duration_cast
|
||||
template <typename To, typename FromRep, typename FromPeriod>
|
||||
To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) {
|
||||
int ec;
|
||||
To to = safe_duration_cast::safe_duration_cast<To>(from, ec);
|
||||
if (ec) FMT_THROW(format_error("cannot format duration"));
|
||||
return to;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename Rep, typename Period,
|
||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||
inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
||||
std::chrono::duration<Rep, Period> d) {
|
||||
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
|
||||
-> std::chrono::duration<Rep, std::milli> {
|
||||
// this may overflow and/or the result may not fit in the
|
||||
// target type.
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
using CommonSecondsType =
|
||||
typename std::common_type<decltype(d), std::chrono::seconds>::type;
|
||||
const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d);
|
||||
const auto d_as_common = fmt_duration_cast<CommonSecondsType>(d);
|
||||
const auto d_as_whole_seconds =
|
||||
fmt_safe_duration_cast<std::chrono::seconds>(d_as_common);
|
||||
fmt_duration_cast<std::chrono::seconds>(d_as_common);
|
||||
// this conversion should be nonproblematic
|
||||
const auto diff = d_as_common - d_as_whole_seconds;
|
||||
const auto ms =
|
||||
fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
||||
fmt_duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
|
||||
return ms;
|
||||
#else
|
||||
auto s = std::chrono::duration_cast<std::chrono::seconds>(d);
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(d - s);
|
||||
auto s = fmt_duration_cast<std::chrono::seconds>(d);
|
||||
return fmt_duration_cast<std::chrono::milliseconds>(d - s);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename Char, typename Rep, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||
OutputIt format_duration_value(OutputIt out, Rep val, int) {
|
||||
auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt {
|
||||
return write<Char>(out, val);
|
||||
}
|
||||
|
||||
template <typename Char, typename Rep, typename OutputIt,
|
||||
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
|
||||
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||
auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt {
|
||||
auto specs = format_specs<Char>();
|
||||
specs.precision = precision;
|
||||
specs.type = precision >= 0 ? presentation_type::fixed_lower
|
||||
@@ -1715,12 +1708,12 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
||||
auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt {
|
||||
return std::copy(unit.begin(), unit.end(), out);
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
|
||||
auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt {
|
||||
// This works when wchar_t is UTF-32 because units only contain characters
|
||||
// that have the same representation in UTF-16 and UTF-32.
|
||||
utf8_to_utf16 u(unit);
|
||||
@@ -1728,7 +1721,7 @@ OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
|
||||
}
|
||||
|
||||
template <typename Char, typename Period, typename OutputIt>
|
||||
OutputIt format_duration_unit(OutputIt out) {
|
||||
auto format_duration_unit(OutputIt out) -> OutputIt {
|
||||
if (const char* unit = get_units<Period>())
|
||||
return copy_unit(string_view(unit), out, Char());
|
||||
*out++ = '[';
|
||||
@@ -1795,18 +1788,12 @@ struct chrono_formatter {
|
||||
|
||||
// this may overflow and/or the result may not fit in the
|
||||
// target type.
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
// might need checked conversion (rep!=Rep)
|
||||
auto tmpval = std::chrono::duration<rep, Period>(val);
|
||||
s = fmt_safe_duration_cast<seconds>(tmpval);
|
||||
#else
|
||||
s = std::chrono::duration_cast<seconds>(
|
||||
std::chrono::duration<rep, Period>(val));
|
||||
#endif
|
||||
s = fmt_duration_cast<seconds>(std::chrono::duration<rep, Period>(val));
|
||||
}
|
||||
|
||||
// returns true if nan or inf, writes to out.
|
||||
bool handle_nan_inf() {
|
||||
auto handle_nan_inf() -> bool {
|
||||
if (isfinite(val)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1823,17 +1810,22 @@ struct chrono_formatter {
|
||||
return true;
|
||||
}
|
||||
|
||||
Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); }
|
||||
auto days() const -> Rep { return static_cast<Rep>(s.count() / 86400); }
|
||||
auto hour() const -> Rep {
|
||||
return static_cast<Rep>(mod((s.count() / 3600), 24));
|
||||
}
|
||||
|
||||
Rep hour12() const {
|
||||
auto hour12() const -> Rep {
|
||||
Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12));
|
||||
return hour <= 0 ? 12 : hour;
|
||||
}
|
||||
|
||||
Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); }
|
||||
Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); }
|
||||
auto minute() const -> Rep {
|
||||
return static_cast<Rep>(mod((s.count() / 60), 60));
|
||||
}
|
||||
auto second() const -> Rep { return static_cast<Rep>(mod(s.count(), 60)); }
|
||||
|
||||
std::tm time() const {
|
||||
auto time() const -> std::tm {
|
||||
auto time = std::tm();
|
||||
time.tm_hour = to_nonnegative_int(hour(), 24);
|
||||
time.tm_min = to_nonnegative_int(minute(), 60);
|
||||
@@ -1901,10 +1893,14 @@ struct chrono_formatter {
|
||||
void on_dec0_week_of_year(numeric_system) {}
|
||||
void on_dec1_week_of_year(numeric_system) {}
|
||||
void on_iso_week_of_year(numeric_system) {}
|
||||
void on_day_of_year() {}
|
||||
void on_day_of_month(numeric_system) {}
|
||||
void on_day_of_month_space(numeric_system) {}
|
||||
|
||||
void on_day_of_year() {
|
||||
if (handle_nan_inf()) return;
|
||||
write(days(), 0);
|
||||
}
|
||||
|
||||
void on_24_hour(numeric_system ns, pad_type pad) {
|
||||
if (handle_nan_inf()) return;
|
||||
|
||||
@@ -1997,7 +1993,7 @@ struct chrono_formatter {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
|
||||
using weekday = std::chrono::weekday;
|
||||
@@ -2011,7 +2007,7 @@ class weekday {
|
||||
weekday() = default;
|
||||
explicit constexpr weekday(unsigned wd) noexcept
|
||||
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
|
||||
constexpr unsigned c_encoding() const noexcept { return value; }
|
||||
constexpr auto c_encoding() const noexcept -> unsigned { return value; }
|
||||
};
|
||||
|
||||
class year_month_day {};
|
||||
@@ -2047,80 +2043,67 @@ template <typename Char> struct formatter<weekday, Char> {
|
||||
template <typename Rep, typename Period, typename Char>
|
||||
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
private:
|
||||
format_specs<Char> specs;
|
||||
int precision = -1;
|
||||
using arg_ref_type = detail::arg_ref<Char>;
|
||||
arg_ref_type width_ref;
|
||||
arg_ref_type precision_ref;
|
||||
bool localized = false;
|
||||
basic_string_view<Char> format_str;
|
||||
using duration = std::chrono::duration<Rep, Period>;
|
||||
|
||||
using iterator = typename basic_format_parse_context<Char>::iterator;
|
||||
struct parse_range {
|
||||
iterator begin;
|
||||
iterator end;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) {
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if (begin == end || *begin == '}') return {begin, begin};
|
||||
|
||||
begin = detail::parse_align(begin, end, specs);
|
||||
if (begin == end) return {begin, begin};
|
||||
|
||||
begin = detail::parse_dynamic_spec(begin, end, specs.width, width_ref, ctx);
|
||||
if (begin == end) return {begin, begin};
|
||||
|
||||
auto checker = detail::chrono_format_checker();
|
||||
if (*begin == '.') {
|
||||
checker.has_precision_integral = !std::is_floating_point<Rep>::value;
|
||||
begin =
|
||||
detail::parse_precision(begin, end, precision, precision_ref, ctx);
|
||||
}
|
||||
if (begin != end && *begin == 'L') {
|
||||
++begin;
|
||||
localized = true;
|
||||
}
|
||||
end = detail::parse_chrono_format(begin, end, checker);
|
||||
return {begin, end};
|
||||
}
|
||||
format_specs<Char> specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
detail::arg_ref<Char> precision_ref_;
|
||||
bool localized_ = false;
|
||||
basic_string_view<Char> format_str_;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto range = do_parse(ctx);
|
||||
format_str = basic_string_view<Char>(
|
||||
&*range.begin, detail::to_unsigned(range.end - range.begin));
|
||||
return range.end;
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
||||
if (it == end) return it;
|
||||
|
||||
auto checker = detail::chrono_format_checker();
|
||||
if (*it == '.') {
|
||||
checker.has_precision_integral = !std::is_floating_point<Rep>::value;
|
||||
it = detail::parse_precision(it, end, specs_.precision, precision_ref_,
|
||||
ctx);
|
||||
}
|
||||
if (it != end && *it == 'L') {
|
||||
localized_ = true;
|
||||
++it;
|
||||
}
|
||||
end = detail::parse_chrono_format(it, end, checker);
|
||||
format_str_ = {it, detail::to_unsigned(end - it)};
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const duration& d, FormatContext& ctx) const
|
||||
auto format(std::chrono::duration<Rep, Period> d, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto specs_copy = specs;
|
||||
auto precision_copy = precision;
|
||||
auto begin = format_str.begin(), end = format_str.end();
|
||||
auto specs = specs_;
|
||||
auto precision = specs.precision;
|
||||
specs.precision = -1;
|
||||
auto begin = format_str_.begin(), end = format_str_.end();
|
||||
// As a possible future optimization, we could avoid extra copying if width
|
||||
// is not specified.
|
||||
basic_memory_buffer<Char> buf;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto out = std::back_inserter(buf);
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
|
||||
width_ref, ctx);
|
||||
detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
|
||||
precision_ref, ctx);
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
||||
ctx);
|
||||
detail::handle_dynamic_spec<detail::precision_checker>(precision,
|
||||
precision_ref_, ctx);
|
||||
if (begin == end || *begin == '}') {
|
||||
out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
|
||||
out = detail::format_duration_value<Char>(out, d.count(), precision);
|
||||
detail::format_duration_unit<Char, Period>(out);
|
||||
} else {
|
||||
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
||||
ctx, out, d);
|
||||
f.precision = precision_copy;
|
||||
f.localized = localized;
|
||||
using chrono_formatter =
|
||||
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;
|
||||
auto f = chrono_formatter(ctx, out, d);
|
||||
f.precision = precision;
|
||||
f.localized = localized_;
|
||||
detail::parse_chrono_format(begin, end, f);
|
||||
}
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2128,34 +2111,33 @@ template <typename Char, typename Duration>
|
||||
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||
Char> : formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR formatter() {
|
||||
this->format_str = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
||||
this->format_str_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(std::chrono::time_point<std::chrono::system_clock, Duration> val,
|
||||
FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
using period = typename Duration::period;
|
||||
if (period::num != 1 || period::den != 1 ||
|
||||
std::is_floating_point<typename Duration::rep>::value) {
|
||||
if (detail::const_check(
|
||||
period::num != 1 || period::den != 1 ||
|
||||
std::is_floating_point<typename Duration::rep>::value)) {
|
||||
const auto epoch = val.time_since_epoch();
|
||||
auto subsecs = std::chrono::duration_cast<Duration>(
|
||||
epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
|
||||
auto subsecs = detail::fmt_duration_cast<Duration>(
|
||||
epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch));
|
||||
|
||||
if (subsecs.count() < 0) {
|
||||
auto second = std::chrono::seconds(1);
|
||||
auto second =
|
||||
detail::fmt_duration_cast<Duration>(std::chrono::seconds(1));
|
||||
if (epoch.count() < ((Duration::min)() + second).count())
|
||||
FMT_THROW(format_error("duration is too small"));
|
||||
subsecs += second;
|
||||
val -= second;
|
||||
}
|
||||
|
||||
return formatter<std::tm, Char>::do_format(
|
||||
gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx,
|
||||
&subsecs);
|
||||
return formatter<std::tm, Char>::do_format(gmtime(val), ctx, &subsecs);
|
||||
}
|
||||
|
||||
return formatter<std::tm, Char>::format(
|
||||
gmtime(std::chrono::time_point_cast<std::chrono::seconds>(val)), ctx);
|
||||
return formatter<std::tm, Char>::format(gmtime(val), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2164,7 +2146,7 @@ template <typename Char, typename Duration>
|
||||
struct formatter<std::chrono::local_time<Duration>, Char>
|
||||
: formatter<std::tm, Char> {
|
||||
FMT_CONSTEXPR formatter() {
|
||||
this->format_str = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
||||
this->format_str_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>{};
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
@@ -2174,17 +2156,13 @@ struct formatter<std::chrono::local_time<Duration>, Char>
|
||||
if (period::num != 1 || period::den != 1 ||
|
||||
std::is_floating_point<typename Duration::rep>::value) {
|
||||
const auto epoch = val.time_since_epoch();
|
||||
const auto subsecs = std::chrono::duration_cast<Duration>(
|
||||
epoch - std::chrono::duration_cast<std::chrono::seconds>(epoch));
|
||||
const auto subsecs = detail::fmt_duration_cast<Duration>(
|
||||
epoch - detail::fmt_duration_cast<std::chrono::seconds>(epoch));
|
||||
|
||||
return formatter<std::tm, Char>::do_format(
|
||||
localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
|
||||
ctx, &subsecs);
|
||||
return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
|
||||
}
|
||||
|
||||
return formatter<std::tm, Char>::format(
|
||||
localtime(std::chrono::time_point_cast<std::chrono::seconds>(val)),
|
||||
ctx);
|
||||
return formatter<std::tm, Char>::format(localtime(val), ctx);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
@@ -2207,51 +2185,46 @@ struct formatter<std::chrono::time_point<std::chrono::utc_clock, Duration>,
|
||||
|
||||
template <typename Char> struct formatter<std::tm, Char> {
|
||||
private:
|
||||
format_specs<Char> specs;
|
||||
detail::arg_ref<Char> width_ref;
|
||||
format_specs<Char> specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
|
||||
protected:
|
||||
basic_string_view<Char> format_str;
|
||||
|
||||
FMT_CONSTEXPR auto do_parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if (begin == end || *begin == '}') return begin;
|
||||
|
||||
begin = detail::parse_align(begin, end, specs);
|
||||
if (begin == end) return end;
|
||||
|
||||
begin = detail::parse_dynamic_spec(begin, end, specs.width, width_ref, ctx);
|
||||
if (begin == end) return end;
|
||||
|
||||
end = detail::parse_chrono_format(begin, end, detail::tm_format_checker());
|
||||
// Replace default format_str only if the new spec is not empty.
|
||||
if (end != begin) format_str = {begin, detail::to_unsigned(end - begin)};
|
||||
return end;
|
||||
}
|
||||
basic_string_view<Char> format_str_;
|
||||
|
||||
template <typename FormatContext, typename Duration>
|
||||
auto do_format(const std::tm& tm, FormatContext& ctx,
|
||||
const Duration* subsecs) const -> decltype(ctx.out()) {
|
||||
auto specs_copy = specs;
|
||||
basic_memory_buffer<Char> buf;
|
||||
auto specs = specs_;
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
auto out = std::back_inserter(buf);
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
|
||||
width_ref, ctx);
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
||||
ctx);
|
||||
|
||||
const auto loc_ref = ctx.locale();
|
||||
auto loc_ref = ctx.locale();
|
||||
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
|
||||
auto w =
|
||||
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
|
||||
detail::parse_chrono_format(format_str.begin(), format_str.end(), w);
|
||||
detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w);
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return this->do_parse(ctx);
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
||||
if (it == end) return it;
|
||||
|
||||
end = detail::parse_chrono_format(it, end, detail::tm_format_checker());
|
||||
// Replace the default format_str only if the new spec is not empty.
|
||||
if (end != it) format_str_ = {it, detail::to_unsigned(end - it)};
|
||||
return end;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
|
||||
+48
-38
@@ -203,7 +203,7 @@ struct rgb {
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
@@ -225,8 +225,7 @@ struct color_type {
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
/** A text style consisting of foreground and background colors and emphasis. */
|
||||
class text_style {
|
||||
@@ -234,7 +233,7 @@ class text_style {
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
|
||||
: set_foreground_color(), set_background_color(), ems(em) {}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
|
||||
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
@@ -258,29 +257,29 @@ class text_style {
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
|
||||
-> text_style {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const noexcept {
|
||||
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
|
||||
return set_foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_background() const noexcept {
|
||||
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
|
||||
return set_background_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_emphasis() const noexcept {
|
||||
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_foreground() const noexcept {
|
||||
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_background() const noexcept {
|
||||
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const noexcept {
|
||||
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
@@ -298,9 +297,11 @@ class text_style {
|
||||
}
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept;
|
||||
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style;
|
||||
|
||||
friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept;
|
||||
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
|
||||
-> text_style;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
@@ -310,20 +311,23 @@ class text_style {
|
||||
};
|
||||
|
||||
/** Creates a text style from the foreground (text) color. */
|
||||
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept {
|
||||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style {
|
||||
return text_style(true, foreground);
|
||||
}
|
||||
|
||||
/** Creates a text style from the background color. */
|
||||
FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept {
|
||||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
|
||||
-> text_style {
|
||||
return text_style(false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept {
|
||||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
||||
-> text_style {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
@@ -385,8 +389,8 @@ template <typename Char> struct ansi_color_escape {
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const noexcept { return buffer; }
|
||||
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept {
|
||||
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
|
||||
FMT_CONSTEXPR_CHAR_TRAITS auto end() const noexcept -> const Char* {
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
@@ -401,25 +405,27 @@ template <typename Char> struct ansi_color_escape {
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept {
|
||||
static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
|
||||
-> bool {
|
||||
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
detail::color_type foreground) noexcept {
|
||||
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
detail::color_type background) noexcept {
|
||||
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept {
|
||||
FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
@@ -428,9 +434,10 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
||||
buffer.append(reset_color.begin(), reset_color.end());
|
||||
}
|
||||
|
||||
template <typename T> struct styled_arg {
|
||||
template <typename T> struct styled_arg : detail::view {
|
||||
const T& value;
|
||||
text_style style;
|
||||
styled_arg(const T& v, text_style s) : value(v), style(s) {}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
@@ -457,7 +464,7 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
inline void vprint(std::FILE* f, const text_style& ts, string_view fmt,
|
||||
format_args args) {
|
||||
@@ -511,9 +518,10 @@ void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
inline auto vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, detail::to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
@@ -532,8 +540,8 @@ inline std::basic_string<Char> vformat(
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
inline auto format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) -> std::basic_string<Char> {
|
||||
return fmt::vformat(ts, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
@@ -543,9 +551,10 @@ inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
*/
|
||||
template <typename OutputIt, typename Char,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
OutputIt vformat_to(
|
||||
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto vformat_to(OutputIt out, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, ts, format_str, args);
|
||||
return detail::get_iterator(buf, out);
|
||||
@@ -563,9 +572,10 @@ OutputIt vformat_to(
|
||||
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||
detail::is_string<S>::value>
|
||||
template <
|
||||
typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value &&
|
||||
detail::is_string<S>::value>
|
||||
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
|
||||
+27
-99
@@ -14,89 +14,11 @@ FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char, typename InputIt>
|
||||
FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||
counting_iterator it) {
|
||||
FMT_CONSTEXPR inline auto copy_str(InputIt begin, InputIt end,
|
||||
counting_iterator it) -> counting_iterator {
|
||||
return it + (end - begin);
|
||||
}
|
||||
|
||||
template <typename OutputIt> class truncating_iterator_base {
|
||||
protected:
|
||||
OutputIt out_;
|
||||
size_t limit_;
|
||||
size_t count_ = 0;
|
||||
|
||||
truncating_iterator_base() : out_(), limit_(0) {}
|
||||
|
||||
truncating_iterator_base(OutputIt out, size_t limit)
|
||||
: out_(out), limit_(limit) {}
|
||||
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = typename std::iterator_traits<OutputIt>::value_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
FMT_UNCHECKED_ITERATOR(truncating_iterator_base);
|
||||
|
||||
OutputIt base() const { return out_; }
|
||||
size_t count() const { return count_; }
|
||||
};
|
||||
|
||||
// An output iterator that truncates the output and counts the number of objects
|
||||
// written to it.
|
||||
template <typename OutputIt,
|
||||
typename Enable = typename std::is_void<
|
||||
typename std::iterator_traits<OutputIt>::value_type>::type>
|
||||
class truncating_iterator;
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::false_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
|
||||
|
||||
public:
|
||||
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
|
||||
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
truncating_iterator& operator++() {
|
||||
if (this->count_++ < this->limit_) ++this->out_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type& operator*() const {
|
||||
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::true_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
public:
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
template <typename T> truncating_iterator& operator=(T val) {
|
||||
if (this->count_++ < this->limit_) *this->out_++ = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator& operator++() { return *this; }
|
||||
truncating_iterator& operator++(int) { return *this; }
|
||||
truncating_iterator& operator*() { return *this; }
|
||||
};
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
|
||||
@@ -135,7 +57,7 @@ struct udl_compiled_string : compiled_string {
|
||||
#endif
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
const T& first(const T& value, const Tail&...) {
|
||||
auto first(const T& value, const Tail&...) -> const T& {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -196,7 +118,8 @@ template <typename Char> struct code_unit {
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, value);
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -220,7 +143,12 @@ template <typename Char, typename T, int N> struct field {
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
return write<Char>(out, get_arg_checked<T, N>(args...));
|
||||
const T& arg = get_arg_checked<T, N>(args...);
|
||||
if constexpr (std::is_convertible_v<T, basic_string_view<Char>>) {
|
||||
auto s = basic_string_view<Char>(arg);
|
||||
return copy_str<Char>(s.begin(), s.end(), out);
|
||||
}
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -448,20 +376,18 @@ constexpr auto compile_format_string(S format_str) {
|
||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||
constexpr auto arg_index =
|
||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||
if constexpr (arg_index != invalid_arg_index) {
|
||||
if constexpr (arg_index >= 0) {
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<
|
||||
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||
arg_index, next_id>(format_str);
|
||||
} else {
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
} else if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,17 +488,19 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const S& format_str, Args&&... args) {
|
||||
auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n),
|
||||
format_str, std::forward<Args>(args)...);
|
||||
return {it.base(), it.count()};
|
||||
auto format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||
fmt::format_to(std::back_inserter(buf), format_str,
|
||||
std::forward<Args>(args)...);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR20 size_t formatted_size(const S& format_str,
|
||||
const Args&... args) {
|
||||
FMT_CONSTEXPR20 auto formatted_size(const S& format_str, const Args&... args)
|
||||
-> size_t {
|
||||
return fmt::format_to(detail::counting_iterator(), format_str, args...)
|
||||
.count();
|
||||
}
|
||||
|
||||
+328
-310
File diff suppressed because it is too large
Load Diff
+93
-96
@@ -18,7 +18,7 @@
|
||||
# include <locale>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32) && !defined(FMT_WINDOWS_NO_WCHAR)
|
||||
# include <io.h> // _isatty
|
||||
#endif
|
||||
|
||||
@@ -58,8 +58,8 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
|
||||
error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
|
||||
auto it = buffer_appender<char>(out);
|
||||
if (message.size() <= inline_buffer_size - error_code_size)
|
||||
format_to(it, FMT_STRING("{}{}"), message, SEP);
|
||||
format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
|
||||
fmt::format_to(it, FMT_STRING("{}{}"), message, SEP);
|
||||
fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code);
|
||||
FMT_ASSERT(out.size() <= inline_buffer_size, "");
|
||||
}
|
||||
|
||||
@@ -73,9 +73,8 @@ FMT_FUNC void report_error(format_func func, int error_code,
|
||||
}
|
||||
|
||||
// A wrapper around fwrite that throws on error.
|
||||
inline void fwrite_fully(const void* ptr, size_t size, size_t count,
|
||||
FILE* stream) {
|
||||
size_t written = std::fwrite(ptr, size, count, stream);
|
||||
inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) {
|
||||
size_t written = std::fwrite(ptr, 1, count, stream);
|
||||
if (written < count)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
@@ -86,7 +85,7 @@ locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
|
||||
static_assert(std::is_same<Locale, std::locale>::value, "");
|
||||
}
|
||||
|
||||
template <typename Locale> Locale locale_ref::get() const {
|
||||
template <typename Locale> auto locale_ref::get() const -> Locale {
|
||||
static_assert(std::is_same<Locale, std::locale>::value, "");
|
||||
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
|
||||
}
|
||||
@@ -98,7 +97,8 @@ FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
|
||||
auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
|
||||
return {std::move(grouping), thousands_sep};
|
||||
}
|
||||
template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
|
||||
template <typename Char>
|
||||
FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {
|
||||
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
|
||||
.decimal_point();
|
||||
}
|
||||
@@ -144,24 +144,25 @@ FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt,
|
||||
format_args args) {
|
||||
FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args)
|
||||
-> std::system_error {
|
||||
auto ec = std::error_code(error_code, std::generic_category());
|
||||
return std::system_error(ec, vformat(fmt, args));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename F> inline bool operator==(basic_fp<F> x, basic_fp<F> y) {
|
||||
template <typename F>
|
||||
inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
|
||||
return x.f == y.f && x.e == y.e;
|
||||
}
|
||||
|
||||
// Compilers should be able to optimize this into the ror instruction.
|
||||
FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept {
|
||||
FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
|
||||
r &= 31;
|
||||
return (n >> r) | (n << (32 - r));
|
||||
}
|
||||
FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
|
||||
FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
|
||||
r &= 63;
|
||||
return (n >> r) | (n << (64 - r));
|
||||
}
|
||||
@@ -170,14 +171,14 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept {
|
||||
namespace dragonbox {
|
||||
// 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 {
|
||||
inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t {
|
||||
return umul128_upper64(static_cast<uint64_t>(x) << 32, y);
|
||||
}
|
||||
|
||||
// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a
|
||||
// 128-bit unsigned integer.
|
||||
inline uint128_fallback umul192_lower128(uint64_t x,
|
||||
uint128_fallback y) noexcept {
|
||||
inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept
|
||||
-> uint128_fallback {
|
||||
uint64_t high = x * y.high();
|
||||
uint128_fallback high_low = umul128(x, y.low());
|
||||
return {high + high_low.high(), high_low.low()};
|
||||
@@ -185,12 +186,12 @@ inline uint128_fallback umul192_lower128(uint64_t x,
|
||||
|
||||
// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a
|
||||
// 64-bit unsigned integer.
|
||||
inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept {
|
||||
inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t {
|
||||
return x * y;
|
||||
}
|
||||
|
||||
// Various fast log computations.
|
||||
inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept {
|
||||
inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
|
||||
FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent");
|
||||
return (e * 631305 - 261663) >> 21;
|
||||
}
|
||||
@@ -204,7 +205,7 @@ FMT_INLINE_VARIABLE constexpr struct {
|
||||
// divisible by pow(10, N).
|
||||
// Precondition: n <= pow(10, N + 1).
|
||||
template <int N>
|
||||
bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept {
|
||||
auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool {
|
||||
// The numbers below are chosen such that:
|
||||
// 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100,
|
||||
// 2. nm mod 2^k < m if and only if n is divisible by d,
|
||||
@@ -229,7 +230,7 @@ bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept {
|
||||
|
||||
// Computes floor(n / pow(10, N)) for small n and N.
|
||||
// Precondition: n <= pow(10, N + 1).
|
||||
template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept {
|
||||
template <int N> auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t {
|
||||
constexpr auto info = div_small_pow10_infos[N - 1];
|
||||
FMT_ASSERT(n <= info.divisor * 10, "n is too large");
|
||||
constexpr uint32_t magic_number =
|
||||
@@ -238,12 +239,12 @@ template <int N> uint32_t small_division_by_pow10(uint32_t n) noexcept {
|
||||
}
|
||||
|
||||
// Computes floor(n / 10^(kappa + 1)) (float)
|
||||
inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept {
|
||||
inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t {
|
||||
// 1374389535 = ceil(2^37/100)
|
||||
return static_cast<uint32_t>((static_cast<uint64_t>(n) * 1374389535) >> 37);
|
||||
}
|
||||
// Computes floor(n / 10^(kappa + 1)) (double)
|
||||
inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept {
|
||||
inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t {
|
||||
// 2361183241434822607 = ceil(2^(64+7)/1000)
|
||||
return umul128_upper64(n, 2361183241434822607ull) >> 7;
|
||||
}
|
||||
@@ -255,7 +256,7 @@ template <> struct cache_accessor<float> {
|
||||
using carrier_uint = float_info<float>::carrier_uint;
|
||||
using cache_entry_type = uint64_t;
|
||||
|
||||
static uint64_t get_cached_power(int k) noexcept {
|
||||
static auto get_cached_power(int k) noexcept -> uint64_t {
|
||||
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
|
||||
"k is out of range");
|
||||
static constexpr const uint64_t pow10_significands[] = {
|
||||
@@ -297,20 +298,23 @@ template <> struct cache_accessor<float> {
|
||||
bool is_integer;
|
||||
};
|
||||
|
||||
static compute_mul_result compute_mul(
|
||||
carrier_uint u, const cache_entry_type& cache) noexcept {
|
||||
static auto compute_mul(carrier_uint u,
|
||||
const cache_entry_type& cache) noexcept
|
||||
-> compute_mul_result {
|
||||
auto r = umul96_upper64(u, cache);
|
||||
return {static_cast<carrier_uint>(r >> 32),
|
||||
static_cast<carrier_uint>(r) == 0};
|
||||
}
|
||||
|
||||
static uint32_t compute_delta(const cache_entry_type& cache,
|
||||
int beta) noexcept {
|
||||
static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
|
||||
-> uint32_t {
|
||||
return static_cast<uint32_t>(cache >> (64 - 1 - beta));
|
||||
}
|
||||
|
||||
static compute_mul_parity_result compute_mul_parity(
|
||||
carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_mul_parity(carrier_uint two_f,
|
||||
const cache_entry_type& cache,
|
||||
int beta) noexcept
|
||||
-> compute_mul_parity_result {
|
||||
FMT_ASSERT(beta >= 1, "");
|
||||
FMT_ASSERT(beta < 64, "");
|
||||
|
||||
@@ -319,22 +323,22 @@ template <> struct cache_accessor<float> {
|
||||
static_cast<uint32_t>(r >> (32 - beta)) == 0};
|
||||
}
|
||||
|
||||
static carrier_uint compute_left_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_left_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
|
||||
return static_cast<carrier_uint>(
|
||||
(cache - (cache >> (num_significand_bits<float>() + 2))) >>
|
||||
(64 - num_significand_bits<float>() - 1 - beta));
|
||||
}
|
||||
|
||||
static carrier_uint compute_right_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_right_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
|
||||
return static_cast<carrier_uint>(
|
||||
(cache + (cache >> (num_significand_bits<float>() + 1))) >>
|
||||
(64 - num_significand_bits<float>() - 1 - beta));
|
||||
}
|
||||
|
||||
static carrier_uint compute_round_up_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_round_up_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
|
||||
return (static_cast<carrier_uint>(
|
||||
cache >> (64 - num_significand_bits<float>() - 2 - beta)) +
|
||||
1) /
|
||||
@@ -346,7 +350,7 @@ template <> struct cache_accessor<double> {
|
||||
using carrier_uint = float_info<double>::carrier_uint;
|
||||
using cache_entry_type = uint128_fallback;
|
||||
|
||||
static uint128_fallback get_cached_power(int k) noexcept {
|
||||
static auto get_cached_power(int k) noexcept -> uint128_fallback {
|
||||
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
|
||||
"k is out of range");
|
||||
|
||||
@@ -985,8 +989,7 @@ template <> struct cache_accessor<double> {
|
||||
{0xe0accfa875af45a7, 0x93eb1b80a33b8606},
|
||||
{0x8c6c01c9498d8b88, 0xbc72f130660533c4},
|
||||
{0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},
|
||||
{ 0xdb68c2ca82ed2a05,
|
||||
0xa67398db9f6820e2 }
|
||||
{0xdb68c2ca82ed2a05, 0xa67398db9f6820e2},
|
||||
#else
|
||||
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
|
||||
{0xce5d73ff402d98e3, 0xfb0a3d212dc81290},
|
||||
@@ -1071,19 +1074,22 @@ template <> struct cache_accessor<double> {
|
||||
bool is_integer;
|
||||
};
|
||||
|
||||
static compute_mul_result compute_mul(
|
||||
carrier_uint u, const cache_entry_type& cache) noexcept {
|
||||
static auto compute_mul(carrier_uint u,
|
||||
const cache_entry_type& cache) noexcept
|
||||
-> compute_mul_result {
|
||||
auto r = umul192_upper128(u, cache);
|
||||
return {r.high(), r.low() == 0};
|
||||
}
|
||||
|
||||
static uint32_t compute_delta(cache_entry_type const& cache,
|
||||
int beta) noexcept {
|
||||
static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
|
||||
-> uint32_t {
|
||||
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
|
||||
}
|
||||
|
||||
static compute_mul_parity_result compute_mul_parity(
|
||||
carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_mul_parity(carrier_uint two_f,
|
||||
const cache_entry_type& cache,
|
||||
int beta) noexcept
|
||||
-> compute_mul_parity_result {
|
||||
FMT_ASSERT(beta >= 1, "");
|
||||
FMT_ASSERT(beta < 64, "");
|
||||
|
||||
@@ -1092,35 +1098,35 @@ template <> struct cache_accessor<double> {
|
||||
((r.high() << beta) | (r.low() >> (64 - beta))) == 0};
|
||||
}
|
||||
|
||||
static carrier_uint compute_left_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_left_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
|
||||
return (cache.high() -
|
||||
(cache.high() >> (num_significand_bits<double>() + 2))) >>
|
||||
(64 - num_significand_bits<double>() - 1 - beta);
|
||||
}
|
||||
|
||||
static carrier_uint compute_right_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_right_endpoint_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
|
||||
return (cache.high() +
|
||||
(cache.high() >> (num_significand_bits<double>() + 1))) >>
|
||||
(64 - num_significand_bits<double>() - 1 - beta);
|
||||
}
|
||||
|
||||
static carrier_uint compute_round_up_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept {
|
||||
static auto compute_round_up_for_shorter_interval_case(
|
||||
const cache_entry_type& cache, int beta) noexcept -> carrier_uint {
|
||||
return ((cache.high() >> (64 - num_significand_bits<double>() - 2 - beta)) +
|
||||
1) /
|
||||
2;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_FUNC uint128_fallback get_cached_power(int k) noexcept {
|
||||
FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback {
|
||||
return cache_accessor<double>::get_cached_power(k);
|
||||
}
|
||||
|
||||
// Various integer checks
|
||||
template <typename T>
|
||||
bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept {
|
||||
auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
|
||||
const int case_shorter_interval_left_endpoint_lower_threshold = 2;
|
||||
const int case_shorter_interval_left_endpoint_upper_threshold = 3;
|
||||
return exponent >= case_shorter_interval_left_endpoint_lower_threshold &&
|
||||
@@ -1128,16 +1134,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_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) 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;
|
||||
// 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);
|
||||
constexpr uint32_t mod_inv_5 = 0xcccccccd;
|
||||
constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5
|
||||
|
||||
int s = 0;
|
||||
while (true) {
|
||||
auto q = rotr(n * mod_inv_25, 2);
|
||||
if (q > max_value<uint32_t>() / 100) break;
|
||||
@@ -1162,32 +1164,17 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
|
||||
|
||||
// Is n is divisible by 10^8?
|
||||
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
|
||||
// If yes, work with the quotient.
|
||||
// If yes, work with the quotient...
|
||||
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
|
||||
|
||||
const uint32_t mod_inv_5 = 0xcccccccd;
|
||||
const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
||||
|
||||
int s = 8;
|
||||
while (true) {
|
||||
auto q = rotr(n32 * mod_inv_25, 2);
|
||||
if (q > max_value<uint32_t>() / 100) break;
|
||||
n32 = q;
|
||||
s += 2;
|
||||
}
|
||||
auto q = rotr(n32 * mod_inv_5, 1);
|
||||
if (q <= max_value<uint32_t>() / 10) {
|
||||
n32 = q;
|
||||
s |= 1;
|
||||
}
|
||||
|
||||
// ... and use the 32 bit variant of the function
|
||||
int s = remove_trailing_zeros(n32, 8);
|
||||
n = n32;
|
||||
return s;
|
||||
}
|
||||
|
||||
// If n is not divisible by 10^8, work with n itself.
|
||||
const uint64_t mod_inv_5 = 0xcccccccccccccccd;
|
||||
const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5;
|
||||
constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;
|
||||
constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5
|
||||
|
||||
int s = 0;
|
||||
while (true) {
|
||||
@@ -1253,7 +1240,7 @@ FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
|
||||
return ret_value;
|
||||
}
|
||||
|
||||
template <typename T> decimal_fp<T> to_decimal(T x) noexcept {
|
||||
template <typename T> auto to_decimal(T x) noexcept -> decimal_fp<T> {
|
||||
// Step 1: integer promotion & Schubfach multiplier calculation.
|
||||
|
||||
using carrier_uint = typename float_info<T>::carrier_uint;
|
||||
@@ -1392,15 +1379,15 @@ template <> struct formatter<detail::bigint> {
|
||||
for (auto i = n.bigits_.size(); i > 0; --i) {
|
||||
auto value = n.bigits_[i - 1u];
|
||||
if (first) {
|
||||
out = format_to(out, FMT_STRING("{:x}"), value);
|
||||
out = fmt::format_to(out, FMT_STRING("{:x}"), value);
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
out = format_to(out, FMT_STRING("{:08x}"), value);
|
||||
out = fmt::format_to(out, FMT_STRING("{:08x}"), value);
|
||||
}
|
||||
if (n.exp_ > 0)
|
||||
out = format_to(out, FMT_STRING("p{}"),
|
||||
n.exp_ * detail::bigint::bigit_bits);
|
||||
out = fmt::format_to(out, FMT_STRING("p{}"),
|
||||
n.exp_ * detail::bigint::bigit_bits);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
@@ -1436,7 +1423,7 @@ FMT_FUNC void report_system_error(int error_code,
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
FMT_FUNC std::string vformat(string_view fmt, format_args args) {
|
||||
FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
|
||||
// Don't optimize the "{}" case to keep the binary size small and because it
|
||||
// can be better optimized in fmt::format anyway.
|
||||
auto buffer = memory_buffer();
|
||||
@@ -1445,33 +1432,43 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) {
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
#ifndef _WIN32
|
||||
FMT_FUNC bool write_console(std::FILE*, string_view) { return false; }
|
||||
#if !defined(_WIN32) || defined(FMT_WINDOWS_NO_WCHAR)
|
||||
FMT_FUNC auto write_console(int, string_view) -> bool { return false; }
|
||||
FMT_FUNC auto write_console(std::FILE*, string_view) -> bool { 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)) return false;
|
||||
FMT_FUNC bool write_console(int fd, string_view text) {
|
||||
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);
|
||||
static_cast<dword>(u16.size()), nullptr, nullptr) != 0;
|
||||
}
|
||||
|
||||
FMT_FUNC auto write_console(std::FILE* f, string_view text) -> bool {
|
||||
return write_console(_fileno(f), text);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// Print assuming legacy (non-Unicode) encoding.
|
||||
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);
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
fwrite_fully(buffer.data(), buffer.size(), f);
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_FUNC void print(std::FILE* f, string_view text) {
|
||||
if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f);
|
||||
#ifdef _WIN32
|
||||
int fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
std::fflush(f);
|
||||
if (write_console(fd, text)) return;
|
||||
}
|
||||
#endif
|
||||
fwrite_fully(text.data(), text.size(), f);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
|
||||
+424
-624
File diff suppressed because it is too large
Load Diff
+29
-25
@@ -13,12 +13,14 @@
|
||||
#include <cstdio>
|
||||
#include <system_error> // std::system_error
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# if FMT_HAS_INCLUDE(<xlocale.h>)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_USE_FCNTL
|
||||
// UWP doesn't provide _pipe.
|
||||
# if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
@@ -46,6 +48,7 @@
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_HAS_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
@@ -114,7 +117,7 @@ template <typename Char> class basic_cstring_view {
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
auto c_str() const -> const Char* { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
@@ -123,10 +126,10 @@ using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
#ifdef _WIN32
|
||||
FMT_API const std::error_category& system_category() noexcept;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
const char* message) noexcept;
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
}
|
||||
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||
format_args args);
|
||||
@@ -169,7 +172,7 @@ std::system_error windows_error(int error_code, string_view message,
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code, const char* message) noexcept;
|
||||
#else
|
||||
inline const std::error_category& system_category() noexcept {
|
||||
inline auto system_category() noexcept -> const std::error_category& {
|
||||
return std::system_category();
|
||||
}
|
||||
#endif // _WIN32
|
||||
@@ -206,7 +209,7 @@ class buffered_file {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
auto operator=(buffered_file&& other) -> buffered_file& {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
@@ -220,9 +223,9 @@ class buffered_file {
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const noexcept { return file_; }
|
||||
auto get() const noexcept -> FILE* { return file_; }
|
||||
|
||||
FMT_API int descriptor() const;
|
||||
FMT_API auto descriptor() const -> int;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
@@ -272,7 +275,7 @@ class FMT_API file {
|
||||
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
// Move assignment is not noexcept because close may throw.
|
||||
file& operator=(file&& other) {
|
||||
auto operator=(file&& other) -> file& {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
@@ -283,24 +286,24 @@ class FMT_API file {
|
||||
~file() noexcept;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const noexcept { return fd_; }
|
||||
auto descriptor() const noexcept -> int { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
long long size() const;
|
||||
auto size() const -> long long;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
size_t read(void* buffer, size_t count);
|
||||
auto read(void* buffer, size_t count) -> size_t;
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
size_t write(const void* buffer, size_t count);
|
||||
auto write(const void* buffer, size_t count) -> size_t;
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
static file dup(int fd);
|
||||
static auto dup(int fd) -> file;
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
@@ -312,11 +315,12 @@ class FMT_API file {
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
// DEPRECATED! Taking files as out parameters is deprecated.
|
||||
static void pipe(file& read_end, file& write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
buffered_file fdopen(const char* mode);
|
||||
auto fdopen(const char* mode) -> buffered_file;
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Opens a file and constructs a file object representing this file by
|
||||
@@ -326,14 +330,14 @@ class FMT_API file {
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
auto getpagesize() -> long;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
struct buffer_size {
|
||||
buffer_size() = default;
|
||||
size_t value = 0;
|
||||
buffer_size operator=(size_t val) const {
|
||||
auto operator=(size_t val) const -> buffer_size {
|
||||
auto bs = buffer_size();
|
||||
bs.value = val;
|
||||
return bs;
|
||||
@@ -387,7 +391,7 @@ class file_buffer final : public buffer<char> {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
// Added {} below to work around default constructor error known to
|
||||
// occur in Xcode versions 7.2.1 and 8.2.1.
|
||||
@@ -410,7 +414,7 @@ class FMT_API ostream {
|
||||
void flush() { buffer_.flush(); }
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
friend auto output_file(cstring_view path, T... params) -> ostream;
|
||||
|
||||
void close() { buffer_.close(); }
|
||||
|
||||
@@ -419,7 +423,7 @@ class FMT_API ostream {
|
||||
output to the file.
|
||||
*/
|
||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||
vformat_to(detail::buffer_appender<char>(buffer_), fmt,
|
||||
vformat_to(std::back_inserter(buffer_), fmt,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
@@ -440,7 +444,7 @@ class FMT_API ostream {
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline ostream output_file(cstring_view path, T... params) {
|
||||
inline auto output_file(cstring_view path, T... params) -> ostream {
|
||||
return {path, detail::ostream_params(params...)};
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
+69
-33
@@ -10,19 +10,50 @@
|
||||
|
||||
#include <fstream> // std::filebuf
|
||||
|
||||
#if defined(_WIN32) && defined(__GLIBCXX__)
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
# include <__std_stream>
|
||||
#ifdef _WIN32
|
||||
# ifdef __GLIBCXX__
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
# endif
|
||||
# include <io.h>
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Streambuf> class formatbuf : public Streambuf {
|
||||
private:
|
||||
using char_type = typename Streambuf::char_type;
|
||||
using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
|
||||
using int_type = typename Streambuf::int_type;
|
||||
using traits_type = typename Streambuf::traits_type;
|
||||
|
||||
buffer<char_type>& buffer_;
|
||||
|
||||
public:
|
||||
explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put area is always empty. This makes the implementation simpler and has
|
||||
// the advantage that the streambuf and the buffer are always in sync and
|
||||
// sputc never writes into uninitialized memory. A disadvantage is that each
|
||||
// call to sputc always results in a (virtual) call to overflow. There is no
|
||||
// disadvantage here for sputn since this always results in a call to xsputn.
|
||||
|
||||
auto overflow(int_type ch) -> int_type override {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<char_type>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
auto xsputn(const char_type* s, streamsize count) -> streamsize override {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
// Generate a unique explicit instantion in every translation unit using a tag
|
||||
// type in an anonymous namespace.
|
||||
namespace {
|
||||
@@ -37,36 +68,40 @@ class file_access {
|
||||
template class file_access<file_access_tag, std::filebuf,
|
||||
&std::filebuf::_Myfile>;
|
||||
auto get_file(std::filebuf&) -> FILE*;
|
||||
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
template class file_access<file_access_tag, std::__stdoutbuf<char>,
|
||||
&std::__stdoutbuf<char>::__file_>;
|
||||
auto get_file(std::__stdoutbuf<char>&) -> FILE*;
|
||||
#endif
|
||||
|
||||
inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) {
|
||||
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
|
||||
-> bool {
|
||||
FILE* f = nullptr;
|
||||
#if FMT_MSC_VERSION
|
||||
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
||||
if (FILE* f = get_file(*buf)) return write_console(f, data);
|
||||
f = get_file(*buf);
|
||||
else
|
||||
return false;
|
||||
#elif defined(_WIN32) && defined(__GLIBCXX__)
|
||||
auto* rdbuf = os.rdbuf();
|
||||
FILE* c_file;
|
||||
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||
c_file = sfbuf->file();
|
||||
f = sfbuf->file();
|
||||
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||
c_file = fbuf->file();
|
||||
f = fbuf->file();
|
||||
else
|
||||
return false;
|
||||
if (c_file) return write_console(c_file, data);
|
||||
#elif defined(_WIN32) && defined(_LIBCPP_VERSION)
|
||||
if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf()))
|
||||
if (FILE* f = get_file(*buf)) return write_console(f, data);
|
||||
#else
|
||||
ignore_unused(os, data);
|
||||
ignore_unused(os, data, f);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
if (f) {
|
||||
int fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
os.flush();
|
||||
return write_console(fd, data);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
inline bool write_ostream_unicode(std::wostream&,
|
||||
fmt::basic_string_view<wchar_t>) {
|
||||
inline auto write_ostream_unicode(std::wostream&,
|
||||
fmt::basic_string_view<wchar_t>) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -87,18 +122,19 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
void format_value(buffer<Char>& buf, const T& value) {
|
||||
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
|
||||
auto&& output = std::basic_ostream<Char>(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
output.imbue(std::locale::classic()); // The default is always unlocalized.
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
}
|
||||
|
||||
template <typename T> struct streamed_view { const T& value; };
|
||||
template <typename T> struct streamed_view {
|
||||
const T& value;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -111,7 +147,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>();
|
||||
detail::format_value(buffer, value, ctx.locale());
|
||||
detail::format_value(buffer, value);
|
||||
return formatter<basic_string_view<Char>, Char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
@@ -140,7 +176,7 @@ struct formatter<detail::streamed_view<T>, Char>
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
auto streamed(const T& value) -> detail::streamed_view<T> {
|
||||
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
|
||||
return {value};
|
||||
}
|
||||
|
||||
@@ -155,7 +191,7 @@ inline void vprint_directly(std::ostream& os, string_view format_str,
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT template <typename Char>
|
||||
FMT_EXPORT template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os,
|
||||
basic_string_view<type_identity_t<Char>> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
@@ -174,7 +210,7 @@ void vprint(std::basic_ostream<Char>& os,
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_MODULE_EXPORT template <typename... T>
|
||||
FMT_EXPORT template <typename... T>
|
||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
const auto& vargs = fmt::make_format_args(args...);
|
||||
if (detail::is_utf8())
|
||||
@@ -183,7 +219,7 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
detail::vprint_directly(os, fmt, vargs);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
FMT_EXPORT
|
||||
template <typename... Args>
|
||||
void print(std::wostream& os,
|
||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||
@@ -191,12 +227,12 @@ void print(std::wostream& os,
|
||||
vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...));
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT template <typename... T>
|
||||
FMT_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
|
||||
FMT_EXPORT
|
||||
template <typename... Args>
|
||||
void println(std::wostream& os,
|
||||
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
|
||||
|
||||
+131
-135
@@ -16,22 +16,22 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
template <typename T> struct printf_formatter {
|
||||
printf_formatter() = delete;
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
template <typename Char> class basic_printf_context {
|
||||
private:
|
||||
OutputIt out_;
|
||||
detail::buffer_appender<Char> out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
|
||||
static_assert(std::is_same<Char, char>::value ||
|
||||
std::is_same<Char, wchar_t>::value,
|
||||
"Unsupported code unit type.");
|
||||
|
||||
public:
|
||||
using char_type = Char;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
using parse_context_type = basic_format_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
/**
|
||||
@@ -40,75 +40,77 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out,
|
||||
basic_printf_context(detail::buffer_appender<Char> out,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
auto out() -> detail::buffer_appender<Char> { return out_; }
|
||||
void advance_to(detail::buffer_appender<Char>) {}
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
auto locale() -> detail::locale_ref { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
||||
return args_.get(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
detail::error_handler().on_error(message);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
static auto fits_in_int(bool) -> bool { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
static auto fits_in_int(int) -> bool { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
struct printf_precision_handler {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
auto operator()(T value) -> int {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
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) {
|
||||
auto operator()(T) -> int {
|
||||
throw_format_error("precision is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
struct is_zero_int {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
auto operator()(T value) -> bool {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
auto operator()(T) -> bool {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
|
||||
template <> struct make_unsigned_or_bool<bool> {
|
||||
using type = bool;
|
||||
};
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
@@ -132,22 +134,23 @@ template <typename T, typename Context> class arg_converter {
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
auto n = static_cast<int>(static_cast<target_type>(value));
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
auto n = static_cast<long long>(value);
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
} else {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
||||
arg_ = detail::make_arg<Context>(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,8 +178,8 @@ template <typename Context> class char_converter {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
auto c = static_cast<typename Context::char_type>(value);
|
||||
arg_ = detail::make_arg<Context>(c);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
@@ -186,8 +189,8 @@ template <typename Context> class char_converter {
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
|
||||
auto operator()(const Char* s) -> const Char* { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
@@ -200,7 +203,7 @@ template <typename Char> class printf_width_handler {
|
||||
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) {
|
||||
auto operator()(T value) -> unsigned {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
@@ -212,7 +215,7 @@ template <typename Char> class printf_width_handler {
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
auto operator()(T) -> unsigned {
|
||||
throw_format_error("width is not integer");
|
||||
return 0;
|
||||
}
|
||||
@@ -227,80 +230,85 @@ auto make_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s)
|
||||
}
|
||||
|
||||
// The ``printf`` argument formatter.
|
||||
template <typename OutputIt, typename Char>
|
||||
template <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 context_type = basic_printf_context<Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
OutputIt write_null_pointer(bool is_string = false) {
|
||||
void write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = presentation_type::none;
|
||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
public:
|
||||
printf_arg_formatter(OutputIt iter, format_specs<Char>& s, context_type& ctx)
|
||||
printf_arg_formatter(buffer_appender<Char> iter, format_specs<Char>& s,
|
||||
context_type& ctx)
|
||||
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
||||
|
||||
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||
void operator()(monostate value) { base::operator()(value); }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
void operator()(T value) {
|
||||
// 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<Char> fmt_specs = this->specs;
|
||||
if (fmt_specs.type != presentation_type::none &&
|
||||
fmt_specs.type != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
if (!std::is_same<T, Char>::value) {
|
||||
base::operator()(value);
|
||||
return;
|
||||
}
|
||||
return base::operator()(value);
|
||||
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));
|
||||
}
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
return base::operator()(value);
|
||||
void operator()(T value) {
|
||||
base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
OutputIt operator()(const char* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
void operator()(const char* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
OutputIt operator()(const wchar_t* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
void operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type != presentation_type::pointer);
|
||||
}
|
||||
|
||||
OutputIt operator()(basic_string_view<Char> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
void operator()(basic_string_view<Char> value) { base::operator()(value); }
|
||||
|
||||
/** Formats a pointer. */
|
||||
OutputIt operator()(const void* value) {
|
||||
return value ? base::operator()(value) : write_null_pointer();
|
||||
void operator()(const void* value) {
|
||||
if (value)
|
||||
base::operator()(value);
|
||||
else
|
||||
write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx =
|
||||
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
||||
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx = basic_format_parse_context<Char>({});
|
||||
handle.format(parse_ctx, context_);
|
||||
return this->out;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -318,9 +326,7 @@ void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
if (specs.sign != sign::plus) specs.sign = sign::space;
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
@@ -332,8 +338,8 @@ void parse_flags(format_specs<Char>& specs, const Char*& it, const Char* end) {
|
||||
}
|
||||
|
||||
template <typename Char, typename GetArg>
|
||||
int parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
|
||||
GetArg get_arg) {
|
||||
auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
|
||||
GetArg get_arg) -> int {
|
||||
int arg_index = -1;
|
||||
Char c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
@@ -414,8 +420,8 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> 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);
|
||||
auto context = basic_printf_context<Char>(out, args);
|
||||
auto parse_ctx = basic_format_parse_context<Char>(format);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
@@ -437,12 +443,11 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
}
|
||||
Char c = *it++;
|
||||
if (it != end && *it == c) {
|
||||
out = write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out =
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||
|
||||
auto specs = format_specs<Char>();
|
||||
specs.align = align::right;
|
||||
@@ -469,16 +474,17 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
auto arg = get_arg(arg_index);
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
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.is_integral()) {
|
||||
// Ignore '0' for non-numeric types or if '-' present.
|
||||
specs.fill[0] = ' ';
|
||||
}
|
||||
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 = make_arg<basic_printf_context<iterator, Char>>(
|
||||
basic_string_view<Char>(
|
||||
str, to_unsigned(nul != str_end ? nul - str : specs.precision)));
|
||||
auto sv = basic_string_view<Char>(
|
||||
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
||||
arg = make_arg<basic_printf_context<Char>>(sv);
|
||||
}
|
||||
if (specs.alt && visit_format_arg(is_zero_int(), arg)) specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
@@ -540,8 +546,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(
|
||||
char_converter<basic_printf_context<iterator, Char>>(arg), arg);
|
||||
visit_format_arg(char_converter<basic_printf_context<Char>>(arg), arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -552,19 +557,14 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(
|
||||
printf_arg_formatter<iterator, Char>(out, specs, context), arg);
|
||||
visit_format_arg(printf_arg_formatter<Char>(out, specs, context), arg);
|
||||
}
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
}
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
using printf_context = basic_printf_context<char>;
|
||||
using wprintf_context = basic_printf_context<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
@@ -581,25 +581,20 @@ inline auto make_printf_args(const T&... args)
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
// DEPRECATED!
|
||||
template <typename... T>
|
||||
inline auto make_wprintf_args(const T&... args)
|
||||
-> format_arg_store<wprintf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
template <typename Char>
|
||||
inline auto vsprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
basic_string_view<Char> fmt,
|
||||
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, detail::to_string_view(fmt), args);
|
||||
detail::vprintf(buf, fmt, args);
|
||||
return to_string(buf);
|
||||
}
|
||||
|
||||
@@ -615,18 +610,17 @@ inline auto vsprintf(
|
||||
template <typename S, typename... T,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(detail::to_string_view(fmt),
|
||||
fmt::make_format_args<context>(args...));
|
||||
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
template <typename Char>
|
||||
inline auto vfprintf(
|
||||
std::FILE* f, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
std::FILE* f, basic_string_view<Char> fmt,
|
||||
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, detail::to_string_view(fmt), args);
|
||||
detail::vprintf(buf, fmt, args);
|
||||
size_t size = buf.size();
|
||||
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
@@ -644,17 +638,16 @@ inline auto vfprintf(
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<context>(args...));
|
||||
fmt::make_format_args<basic_printf_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
template <typename Char>
|
||||
FMT_DEPRECATED inline auto vprintf(
|
||||
basic_string_view<Char> fmt,
|
||||
basic_format_args<basic_printf_context<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
return vfprintf(stdout, detail::to_string_view(fmt), args);
|
||||
return vfprintf(stdout, fmt, args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -666,11 +659,14 @@ inline auto vprintf(
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||
return vprintf(
|
||||
detail::to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||
template <typename... T>
|
||||
inline auto printf(string_view fmt, const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_printf_args(args...));
|
||||
}
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_wprintf_args(args...));
|
||||
}
|
||||
|
||||
FMT_END_EXPORT
|
||||
|
||||
+23
-17
@@ -1,13 +1,9 @@
|
||||
// Formatting library for C++ - experimental range support
|
||||
// Formatting library for C++ - range and tuple support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
@@ -187,7 +183,7 @@ template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
@@ -211,15 +207,15 @@ class is_tuple_formattable_ {
|
||||
};
|
||||
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
|
||||
template <std::size_t... Is>
|
||||
static std::true_type check2(index_sequence<Is...>,
|
||||
integer_sequence<bool, (Is == Is)...>);
|
||||
static std::false_type check2(...);
|
||||
static auto check2(index_sequence<Is...>,
|
||||
integer_sequence<bool, (Is == Is)...>) -> std::true_type;
|
||||
static auto check2(...) -> std::false_type;
|
||||
template <std::size_t... Is>
|
||||
static decltype(check2(
|
||||
static auto check(index_sequence<Is...>) -> decltype(check2(
|
||||
index_sequence<Is...>{},
|
||||
integer_sequence<
|
||||
bool, (is_formattable<typename std::tuple_element<Is, T>::type,
|
||||
C>::value)...>{})) check(index_sequence<Is...>);
|
||||
integer_sequence<bool,
|
||||
(is_formattable<typename std::tuple_element<Is, T>::type,
|
||||
C>::value)...>{}));
|
||||
|
||||
public:
|
||||
static constexpr const bool value =
|
||||
@@ -421,6 +417,12 @@ struct is_formattable_delayed
|
||||
#endif
|
||||
} // namespace detail
|
||||
|
||||
template <typename...> struct conjunction : std::true_type {};
|
||||
template <typename P> struct conjunction<P> : P {};
|
||||
template <typename P1, typename... Pn>
|
||||
struct conjunction<P1, Pn...>
|
||||
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
|
||||
|
||||
template <typename T, typename Char, typename Enable = void>
|
||||
struct range_formatter;
|
||||
|
||||
@@ -486,7 +488,8 @@ struct range_formatter<
|
||||
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);
|
||||
auto&& item = *it;
|
||||
out = underlying_.format(mapper.map(item), ctx);
|
||||
++i;
|
||||
}
|
||||
out = detail::copy_str<Char>(closing_bracket_, out);
|
||||
@@ -668,8 +671,11 @@ template <typename Container> struct all {
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<T, Char,
|
||||
enable_if_t<detail::is_container_adaptor_like<T>::value>>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<conjunction<detail::is_container_adaptor_like<T>,
|
||||
bool_constant<range_format_kind<T, Char>::value ==
|
||||
range_format::disabled>>::value>>
|
||||
: formatter<detail::all<typename T::container_type>, Char> {
|
||||
using all = detail::all<typename T::container_type>;
|
||||
template <typename FormatContext>
|
||||
|
||||
+251
-63
@@ -8,6 +8,8 @@
|
||||
#ifndef FMT_STD_H_
|
||||
#define FMT_STD_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
@@ -15,7 +17,9 @@
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "format.h"
|
||||
#include "ostream.h"
|
||||
|
||||
#if FMT_HAS_INCLUDE(<version>)
|
||||
@@ -34,6 +38,10 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
|
||||
# include <source_location>
|
||||
#endif
|
||||
|
||||
// GCC 4 does not support FMT_HAS_INCLUDE.
|
||||
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
|
||||
# include <cxxabi.h>
|
||||
@@ -44,67 +52,155 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cpp_lib_filesystem
|
||||
// Check if typeid is available.
|
||||
#ifndef FMT_USE_TYPEID
|
||||
// __RTTI is for EDG compilers. In MSVC typeid is available without RTTI.
|
||||
# if defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || FMT_MSC_VERSION || \
|
||||
defined(__INTEL_RTTI__) || defined(__RTTI)
|
||||
# define FMT_USE_TYPEID 1
|
||||
# else
|
||||
# define FMT_USE_TYPEID 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
|
||||
#ifndef FMT_CPP_LIB_FILESYSTEM
|
||||
# ifdef __cpp_lib_filesystem
|
||||
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
|
||||
# else
|
||||
# define FMT_CPP_LIB_FILESYSTEM 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_CPP_LIB_VARIANT
|
||||
# ifdef __cpp_lib_variant
|
||||
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
|
||||
# else
|
||||
# define FMT_CPP_LIB_VARIANT 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename Char>
|
||||
template <typename Char, typename PathChar>
|
||||
auto get_path_string(const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
|
||||
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
|
||||
else
|
||||
return p.string<Char>();
|
||||
}
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||
const std::filesystem::path& p) {
|
||||
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
||||
}
|
||||
# ifdef _WIN32
|
||||
template <>
|
||||
inline void write_escaped_path<char>(memory_buffer& quoted,
|
||||
const std::filesystem::path& p) {
|
||||
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 <>
|
||||
inline void write_escaped_path<std::filesystem::path::value_type>(
|
||||
basic_memory_buffer<std::filesystem::path::value_type>& quoted,
|
||||
const std::filesystem::path& p) {
|
||||
write_escaped_string<std::filesystem::path::value_type>(
|
||||
std::back_inserter(quoted), p.native());
|
||||
const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> &&
|
||||
std::is_same_v<PathChar, wchar_t>) {
|
||||
auto buf = basic_memory_buffer<wchar_t>();
|
||||
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
|
||||
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
|
||||
FMT_ASSERT(valid, "invalid utf16");
|
||||
} else if constexpr (std::is_same_v<Char, PathChar>) {
|
||||
write_escaped_string<std::filesystem::path::value_type>(
|
||||
std::back_inserter(quoted), native);
|
||||
} else {
|
||||
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::filesystem::path, Char>
|
||||
: formatter<basic_string_view<Char>> {
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
private:
|
||||
format_specs<Char> specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
bool debug_ = false;
|
||||
char path_type_ = 0;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||
|
||||
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;
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
|
||||
if (it != end && *it == '?') {
|
||||
debug_ = true;
|
||||
++it;
|
||||
}
|
||||
if (it != end && (*it == 'g')) path_type_ = *it++;
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const ->
|
||||
typename FormatContext::iterator {
|
||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
|
||||
auto specs = specs_;
|
||||
# ifdef _WIN32
|
||||
auto path_string = !path_type_ ? p.native() : p.generic_wstring();
|
||||
# else
|
||||
auto path_string = !path_type_ ? p.native() : p.generic_string();
|
||||
# endif
|
||||
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
|
||||
ctx);
|
||||
if (!debug_) {
|
||||
auto s = detail::get_path_string<Char>(p, path_string);
|
||||
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
||||
}
|
||||
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);
|
||||
detail::write_escaped_path(quoted, p, path_string);
|
||||
return detail::write(ctx.out(),
|
||||
basic_string_view<Char>(quoted.data(), quoted.size()),
|
||||
specs);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT
|
||||
FMT_EXPORT
|
||||
template <std::size_t N, typename Char>
|
||||
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
|
||||
private:
|
||||
// Functor because C++11 doesn't support generic lambdas.
|
||||
struct writer {
|
||||
const std::bitset<N>& bs;
|
||||
|
||||
template <typename OutputIt>
|
||||
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
|
||||
for (auto pos = N; pos > 0; --pos) {
|
||||
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename FormatContext>
|
||||
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return write_padded(ctx, writer{bs});
|
||||
}
|
||||
};
|
||||
|
||||
FMT_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
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::optional<T>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||
@@ -132,7 +228,7 @@ struct formatter<std::optional<T>, Char,
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(std::optional<T> const& opt, FormatContext& ctx) const
|
||||
auto format(const std::optional<T>& opt, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
if (!opt) return detail::write<Char>(ctx.out(), none);
|
||||
|
||||
@@ -146,24 +242,33 @@ struct formatter<std::optional<T>, Char,
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_optional
|
||||
|
||||
#ifdef __cpp_lib_variant
|
||||
#ifdef __cpp_lib_source_location
|
||||
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()) {
|
||||
FMT_EXPORT
|
||||
template <> struct formatter<std::source_location> {
|
||||
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::monostate&, FormatContext& ctx) const
|
||||
auto format(const std::source_location& loc, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write<Char>(out, "monostate");
|
||||
out = detail::write(out, loc.file_name());
|
||||
out = detail::write(out, ':');
|
||||
out = detail::write<char>(out, loc.line());
|
||||
out = detail::write(out, ':');
|
||||
out = detail::write<char>(out, loc.column());
|
||||
out = detail::write(out, ": ");
|
||||
out = detail::write(out, loc.function_name());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
@@ -197,6 +302,7 @@ 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;
|
||||
};
|
||||
@@ -206,7 +312,21 @@ template <typename T, typename C> struct is_variant_formattable {
|
||||
detail::is_variant_formattable_<T, C>::value;
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::monostate, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::monostate&, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return detail::write<Char>(ctx.out(), "monostate");
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename Variant, typename Char>
|
||||
struct formatter<
|
||||
Variant, Char,
|
||||
@@ -223,13 +343,14 @@ struct formatter<
|
||||
auto out = ctx.out();
|
||||
|
||||
out = detail::write<Char>(out, "variant(");
|
||||
try {
|
||||
FMT_TRY {
|
||||
std::visit(
|
||||
[&](const auto& v) {
|
||||
out = detail::write_variant_alternative<Char>(out, v);
|
||||
},
|
||||
value);
|
||||
} catch (const std::bad_variant_access&) {
|
||||
}
|
||||
FMT_CATCH(const std::bad_variant_access&) {
|
||||
detail::write<Char>(out, "valueless by exception");
|
||||
}
|
||||
*out++ = ')';
|
||||
@@ -237,10 +358,10 @@ struct formatter<
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
#endif // __cpp_lib_variant
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT
|
||||
FMT_EXPORT
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
@@ -258,10 +379,10 @@ template <typename Char> struct formatter<std::error_code, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
T, Char, // DEPRECATED! Mixing code unit types.
|
||||
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||
private:
|
||||
bool with_typename_ = false;
|
||||
@@ -274,7 +395,7 @@ struct formatter<
|
||||
if (it == end || *it == '}') return it;
|
||||
if (*it == 't') {
|
||||
++it;
|
||||
with_typename_ = true;
|
||||
with_typename_ = FMT_USE_TYPEID != 0;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
@@ -287,11 +408,12 @@ struct formatter<
|
||||
if (!with_typename_)
|
||||
return detail::write_bytes(out, string_view(ex.what()), spec);
|
||||
|
||||
#if FMT_USE_TYPEID
|
||||
const std::type_info& ti = typeid(ex);
|
||||
#ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
std::size_t size = 0;
|
||||
std::unique_ptr<char, decltype(&std::free)> demangled_name_ptr(
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
@@ -327,23 +449,89 @@ struct formatter<
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
out = detail::write_bytes(out, demangled_name_view, spec);
|
||||
#elif FMT_MSC_VERSION
|
||||
# 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
|
||||
# else
|
||||
out = detail::write_bytes(out, string_view(ti.name()), spec);
|
||||
# endif
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
return detail::write_bytes(out, string_view(ex.what()), 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
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_flip : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct is_bit_reference_like {
|
||||
static constexpr const bool value =
|
||||
std::is_convertible<T, bool>::value &&
|
||||
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
|
||||
};
|
||||
|
||||
#ifdef _LIBCPP_VERSION
|
||||
|
||||
// Workaround for libc++ incompatibility with C++ standard.
|
||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||
template <typename C>
|
||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||
static constexpr const bool value = true;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// We can't use std::vector<bool, Allocator>::reference and
|
||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||
// in partial specialization.
|
||||
FMT_EXPORT
|
||||
template <typename BitRef, typename Char>
|
||||
struct formatter<BitRef, Char,
|
||||
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||
: formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_EXPORT
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::atomic<T>, Char,
|
||||
enable_if_t<is_formattable<T, Char>::value>>
|
||||
: formatter<T, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic<T>& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<T, Char>::format(v.load(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
FMT_EXPORT
|
||||
template <typename Char>
|
||||
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic_flag& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v.test(), ctx);
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
#endif // FMT_STD_H_
|
||||
|
||||
+32
-32
@@ -62,15 +62,16 @@ template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||
template <> struct is_char<char16_t> : std::true_type {};
|
||||
template <> struct is_char<char32_t> : std::true_type {};
|
||||
|
||||
template <typename... Args>
|
||||
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||
const Args&... args) {
|
||||
template <typename... T>
|
||||
constexpr auto make_wformat_args(const T&... args)
|
||||
-> format_arg_store<wformat_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
inline namespace literals {
|
||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
|
||||
constexpr auto operator""_a(const wchar_t* s, size_t)
|
||||
-> detail::udl_arg<wchar_t> {
|
||||
return {s};
|
||||
}
|
||||
#endif
|
||||
@@ -99,9 +100,9 @@ template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto vformat(basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
return to_string(buffer);
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return to_string(buf);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
@@ -111,10 +112,10 @@ auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
template <typename S, typename... T, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
|
||||
return vformat(detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
@@ -129,11 +130,10 @@ inline auto vformat(
|
||||
return detail::vformat(loc, detail::to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
template <typename Locale, typename S, typename... T, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
||||
inline auto format(const Locale& loc, const S& format_str, T&&... args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
@@ -150,11 +150,11 @@ auto vformat_to(OutputIt out, const S& format_str,
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
|
||||
return vformat_to(out, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
@@ -173,13 +173,13 @@ inline auto vformat_to(
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||
template <typename OutputIt, typename Locale, typename S, typename... T,
|
||||
typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
|
||||
detail::is_locale<Locale>::value &&
|
||||
detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
Args&&... args) ->
|
||||
T&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, loc, detail::to_string_view(format_str),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
@@ -192,36 +192,36 @@ inline auto vformat_to_n(
|
||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||
n);
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
return vformat_to_n(out, n, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
template <typename S, typename... T, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||
detail::counting_buffer<Char> buf;
|
||||
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||
auto buf = detail::counting_buffer<Char>();
|
||||
detail::vformat_to(buf, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffer_context<Char>>(args...));
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
buffer.push_back(L'\0');
|
||||
if (std::fputws(buffer.data(), f) == -1)
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, fmt, args);
|
||||
buf.push_back(L'\0');
|
||||
if (std::fputws(buf.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to 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,
|
||||
|
||||
+4
-2
@@ -144,13 +144,15 @@ namespace FUSE
|
||||
l::want_if_capable(conn_,FUSE_CAP_ATOMIC_O_TRUNC);
|
||||
l::want_if_capable(conn_,FUSE_CAP_BIG_WRITES);
|
||||
l::want_if_capable(conn_,FUSE_CAP_CACHE_SYMLINKS,&cfg->cache_symlinks);
|
||||
l::want_if_capable(conn_,FUSE_CAP_DIRECT_IO_ALLOW_MMAP,&cfg->direct_io_allow_mmap);
|
||||
l::want_if_capable(conn_,FUSE_CAP_DONT_MASK);
|
||||
l::want_if_capable(conn_,FUSE_CAP_EXPORT_SUPPORT,&cfg->export_support);
|
||||
l::want_if_capable(conn_,FUSE_CAP_IOCTL_DIR);
|
||||
l::want_if_capable(conn_,FUSE_CAP_PARALLEL_DIROPS);
|
||||
l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS,&cfg->readdirplus);
|
||||
//l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS_AUTO);
|
||||
l::want_if_capable(conn_,FUSE_CAP_POSIX_ACL,&cfg->posix_acl);
|
||||
l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS,&cfg->readdirplus);
|
||||
l::want_if_capable(conn_,FUSE_CAP_WRITEBACK_CACHE,&cfg->writeback_cache);
|
||||
// l::want_if_capable(conn_,FUSE_CAP_READDIR_PLUS_AUTO);
|
||||
l::want_if_capable_max_pages(conn_,cfg);
|
||||
conn_->want &= ~FUSE_CAP_POSIX_LOCKS;
|
||||
conn_->want &= ~FUSE_CAP_FLOCK_LOCKS;
|
||||
|
||||
@@ -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_,
|
||||
|
||||
@@ -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;
|
||||
|
||||
+80
-77
@@ -19,20 +19,24 @@
|
||||
#include "config.hpp"
|
||||
#include "dirinfo.hpp"
|
||||
#include "errno.hpp"
|
||||
#include "fs_closedir.hpp"
|
||||
#include "fs_close.hpp"
|
||||
#include "fs_devid.hpp"
|
||||
#include "fs_getdents64.hpp"
|
||||
#include "fs_inode.hpp"
|
||||
#include "fs_opendir.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_)
|
||||
: _tp(concurrency_,concurrency_,"readdir.cor")
|
||||
FUSE::ReadDirCOR::ReadDirCOR(unsigned concurrency_,
|
||||
unsigned max_queue_depth_)
|
||||
: _tp(concurrency_,max_queue_depth_,"readdir.cor")
|
||||
{
|
||||
|
||||
}
|
||||
@@ -44,79 +48,98 @@ FUSE::ReadDirCOR::~ReadDirCOR()
|
||||
|
||||
namespace l
|
||||
{
|
||||
static
|
||||
inline
|
||||
uint64_t
|
||||
dirent_exact_namelen(const struct dirent *d_)
|
||||
struct Error
|
||||
{
|
||||
#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
|
||||
}
|
||||
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_,
|
||||
std::mutex &names_mutex_,
|
||||
fuse_dirents_t *buf_,
|
||||
std::mutex &dirents_mutex_)
|
||||
std::mutex &mutex_)
|
||||
{
|
||||
int rv;
|
||||
int err;
|
||||
DIR *dh;
|
||||
int dfd;
|
||||
dev_t dev;
|
||||
std::string filepath;
|
||||
|
||||
dh = fs::opendir(basepath_);
|
||||
if(dh == NULL)
|
||||
return -errno;
|
||||
dfd = fs::open_dir_ro(basepath_);
|
||||
if(dfd == -1)
|
||||
return errno;
|
||||
|
||||
dev = fs::devid(dh);
|
||||
DEFER{ fs::close(dfd); };
|
||||
|
||||
dev = fs::devid(dfd);
|
||||
|
||||
rv = 0;
|
||||
err = 0;
|
||||
for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh))
|
||||
for(;;)
|
||||
{
|
||||
std::uint64_t namelen;
|
||||
long nread;
|
||||
char buf[32 * 1024];
|
||||
|
||||
namelen = l::dirent_exact_namelen(de);
|
||||
nread = fs::getdents_64(dfd,buf,sizeof(buf));
|
||||
if(nread == -1)
|
||||
return errno;
|
||||
if(nread == 0)
|
||||
break;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(names_mutex_);
|
||||
rv = names_.put(de->d_name,namelen);
|
||||
if(rv == 0)
|
||||
continue;
|
||||
}
|
||||
linux_dirent64_t *d;
|
||||
std::lock_guard<std::mutex> lk(mutex_);
|
||||
for(long pos = 0; pos < nread; pos += d->reclen)
|
||||
{
|
||||
std::uint64_t namelen;
|
||||
|
||||
filepath = fs::path::make(basepath_,de->d_name);
|
||||
de->d_ino = fs::inode::calc(filepath,
|
||||
DTTOIF(de->d_type),
|
||||
dev,
|
||||
de->d_ino);
|
||||
d = (linux_dirent64_t*)&buf[pos];
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(dirents_mutex_);
|
||||
rv = fuse_dirents_add(buf_,de,namelen);
|
||||
if(rv == 0)
|
||||
continue;
|
||||
}
|
||||
namelen = DIRENT_NAMELEN(d);
|
||||
|
||||
err = -ENOMEM;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
fs::closedir(dh);
|
||||
|
||||
return err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
std::vector<int>
|
||||
int
|
||||
concurrent_readdir(ThreadPool &tp_,
|
||||
const Branches::CPtr &branches_,
|
||||
const char *dirname_,
|
||||
@@ -125,11 +148,10 @@ namespace l
|
||||
gid_t const gid_)
|
||||
{
|
||||
HashSet names;
|
||||
std::mutex names_mutex;
|
||||
std::mutex dirents_mutex;
|
||||
std::vector<int> rv;
|
||||
std::mutex mutex;
|
||||
std::vector<std::future<int>> futures;
|
||||
|
||||
futures.reserve(branches_->size());
|
||||
for(auto const &branch : *branches_)
|
||||
{
|
||||
auto func = [&,dirname_,buf_,uid_,gid_]()
|
||||
@@ -139,7 +161,7 @@ namespace l
|
||||
|
||||
basepath = fs::path::make(branch.path,dirname_);
|
||||
|
||||
return l::readdir(basepath,names,names_mutex,buf_,dirents_mutex);
|
||||
return l::readdir(basepath,names,buf_,mutex);
|
||||
};
|
||||
|
||||
auto rv = tp_.enqueue_task(func);
|
||||
@@ -147,26 +169,11 @@ namespace l
|
||||
futures.emplace_back(std::move(rv));
|
||||
}
|
||||
|
||||
Error error;
|
||||
for(auto &future : futures)
|
||||
rv.push_back(future.get());
|
||||
error = future.get();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
calc_rv(std::vector<int> rvs_)
|
||||
{
|
||||
for(auto rv : rvs_)
|
||||
{
|
||||
if(rv == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(rvs_.empty())
|
||||
return -ENOENT;
|
||||
|
||||
return rvs_[0];
|
||||
return -error;
|
||||
}
|
||||
|
||||
static
|
||||
@@ -178,13 +185,9 @@ namespace l
|
||||
uid_t const uid_,
|
||||
gid_t const gid_)
|
||||
{
|
||||
std::vector<int> rvs;
|
||||
|
||||
fuse_dirents_reset(buf_);
|
||||
|
||||
rvs = l::concurrent_readdir(tp_,branches_,dirname_,buf_,uid_,gid_);
|
||||
|
||||
return l::calc_rv(rvs);
|
||||
return l::concurrent_readdir(tp_,branches_,dirname_,buf_,uid_,gid_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ namespace FUSE
|
||||
class ReadDirCOR final : public FUSE::ReadDirBase
|
||||
{
|
||||
public:
|
||||
ReadDirCOR(unsigned concurrency);
|
||||
ReadDirCOR(unsigned concurrency,
|
||||
unsigned max_queue_depth);
|
||||
~ReadDirCOR();
|
||||
|
||||
int operator()(fuse_file_info_t const *ffi,
|
||||
|
||||
+63
-23
@@ -28,13 +28,15 @@
|
||||
#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_)
|
||||
: _tp(concurrency_,concurrency_,"readdir.cosr")
|
||||
FUSE::ReadDirCOSR::ReadDirCOSR(unsigned concurrency_,
|
||||
unsigned max_queue_depth_)
|
||||
: _tp(concurrency_,max_queue_depth_,"readdir.cosr")
|
||||
{
|
||||
|
||||
}
|
||||
@@ -46,8 +48,40 @@ 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
|
||||
inline
|
||||
uint64_t
|
||||
dirent_exact_namelen(const struct dirent *d_)
|
||||
{
|
||||
@@ -62,25 +96,31 @@ namespace l
|
||||
|
||||
static
|
||||
inline
|
||||
std::vector<std::future<DIR*>>
|
||||
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<DIR*>> futures;
|
||||
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_);
|
||||
|
||||
return fs::opendir(basepath);
|
||||
errno = 0;
|
||||
rv.dir = fs::opendir(basepath);
|
||||
rv.err = errno;
|
||||
|
||||
return rv;
|
||||
};
|
||||
|
||||
auto rv = tp_.enqueue_task(func);
|
||||
@@ -94,29 +134,31 @@ namespace l
|
||||
static
|
||||
inline
|
||||
int
|
||||
readdir(std::vector<std::future<DIR*>> &dh_futures_,
|
||||
char const *dirname_,
|
||||
fuse_dirents_t *buf_)
|
||||
readdir(std::vector<std::future<DirRV>> &dh_futures_,
|
||||
char const *dirname_,
|
||||
fuse_dirents_t *buf_)
|
||||
{
|
||||
int err;
|
||||
Error error;
|
||||
HashSet names;
|
||||
std::string fullpath;
|
||||
|
||||
err = 0;
|
||||
for(auto &dh_future : dh_futures_)
|
||||
{
|
||||
int rv;
|
||||
DIR *dh;
|
||||
dev_t dev;
|
||||
DirRV dirrv;
|
||||
|
||||
dh = dh_future.get();
|
||||
if(dh == NULL)
|
||||
dirrv = dh_future.get();
|
||||
error = dirrv.err;
|
||||
if(dirrv.dir == NULL)
|
||||
continue;
|
||||
|
||||
dev = fs::devid(dh);
|
||||
DEFER { fs::closedir(dirrv.dir); };
|
||||
|
||||
dev = fs::devid(dirrv.dir);
|
||||
|
||||
rv = 0;
|
||||
for(struct dirent *de = fs::readdir(dh); de && !rv; de = fs::readdir(dh))
|
||||
for(dirent *de = fs::readdir(dirrv.dir); de && !rv; de = fs::readdir(dirrv.dir))
|
||||
{
|
||||
std::uint64_t namelen;
|
||||
|
||||
@@ -136,13 +178,11 @@ namespace l
|
||||
if(rv == 0)
|
||||
continue;
|
||||
|
||||
err = -ENOMEM;
|
||||
error = ENOMEM;
|
||||
}
|
||||
|
||||
fs::closedir(dh);
|
||||
}
|
||||
|
||||
return err;
|
||||
return -error;
|
||||
}
|
||||
|
||||
static
|
||||
@@ -156,12 +196,12 @@ namespace l
|
||||
gid_t const gid_)
|
||||
{
|
||||
int rv;
|
||||
std::vector<std::future<DIR*>> dh_futures;
|
||||
std::vector<std::future<DirRV>> futures;
|
||||
|
||||
fuse_dirents_reset(buf_);
|
||||
|
||||
dh_futures = l::opendir(tp_,branches_,dirname_,uid_,gid_);
|
||||
rv = l::readdir(dh_futures,dirname_,buf_);
|
||||
futures = l::opendir(tp_,branches_,dirname_,uid_,gid_);
|
||||
rv = l::readdir(futures,dirname_,buf_);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ namespace FUSE
|
||||
class ReadDirCOSR final : public FUSE::ReadDirBase
|
||||
{
|
||||
public:
|
||||
ReadDirCOSR(unsigned concurrency);
|
||||
ReadDirCOSR(unsigned concurrency,
|
||||
unsigned max_queue_depth);
|
||||
~ReadDirCOSR();
|
||||
|
||||
int operator()(fuse_file_info_t const *ffi,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <cstdlib>
|
||||
#include <set>
|
||||
|
||||
#define DEFAULT_MAX_QUEUE_DEPTH 3
|
||||
|
||||
namespace l
|
||||
{
|
||||
@@ -35,13 +36,20 @@ namespace l
|
||||
void
|
||||
read_cfg(std::string const str_,
|
||||
std::string &type_,
|
||||
int &concurrency_)
|
||||
unsigned &concurrency_,
|
||||
unsigned &max_queue_depth_)
|
||||
{
|
||||
char type[16];
|
||||
int concurrency;
|
||||
int max_queue_depth;
|
||||
|
||||
concurrency = 0;
|
||||
std::sscanf(str_.c_str(),"%15[a-z]:%d",type,&concurrency);
|
||||
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();
|
||||
@@ -51,22 +59,29 @@ namespace l
|
||||
if(concurrency == 0)
|
||||
concurrency = 1;
|
||||
|
||||
type_ = type;
|
||||
concurrency_ = concurrency;
|
||||
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_)
|
||||
{
|
||||
int concurrency;
|
||||
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);
|
||||
l::read_cfg(str_,type,concurrency,max_queue_depth);
|
||||
|
||||
if(types.find(type) == types.end())
|
||||
return false;
|
||||
@@ -79,20 +94,21 @@ FUSE::ReadDirFactory::valid(std::string const str_)
|
||||
std::shared_ptr<FUSE::ReadDirBase>
|
||||
FUSE::ReadDirFactory::make(std::string const str_)
|
||||
{
|
||||
int concurrency;
|
||||
unsigned concurrency;
|
||||
unsigned max_queue_depth;
|
||||
std::string type;
|
||||
|
||||
if(!valid(str_))
|
||||
return {};
|
||||
|
||||
l::read_cfg(str_,type,concurrency);
|
||||
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);
|
||||
return std::make_shared<FUSE::ReadDirCOSR>(concurrency,max_queue_depth);
|
||||
if(type == "cor")
|
||||
return std::make_shared<FUSE::ReadDirCOR>(concurrency);
|
||||
return std::make_shared<FUSE::ReadDirCOR>(concurrency,max_queue_depth);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
@@ -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,17 +16,14 @@
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "errno.hpp"
|
||||
#include "fuse_readdir_plus.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_)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2020, Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "branches.hpp"
|
||||
|
||||
#include "fuse.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
@@ -31,17 +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>
|
||||
|
||||
|
||||
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,13 +88,14 @@ namespace l
|
||||
const char *dirname_,
|
||||
fuse_dirents_t *buf_)
|
||||
{
|
||||
Error error;
|
||||
HashSet names;
|
||||
std::string basepath;
|
||||
std::string fullpath;
|
||||
|
||||
fuse_dirents_reset(buf_);
|
||||
|
||||
for(const auto &branch : *branches_)
|
||||
for(auto const &branch : *branches_)
|
||||
{
|
||||
int rv;
|
||||
DIR *dh;
|
||||
@@ -75,14 +103,18 @@ namespace l
|
||||
|
||||
basepath = fs::path::make(branch.path,dirname_);
|
||||
|
||||
errno = 0;
|
||||
dh = fs::opendir(basepath);
|
||||
error = errno;
|
||||
if(!dh)
|
||||
continue;
|
||||
|
||||
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;
|
||||
|
||||
@@ -100,13 +132,11 @@ namespace l
|
||||
|
||||
rv = fuse_dirents_add(buf_,de,namelen);
|
||||
if(rv)
|
||||
return (fs::closedir(dh),-ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
fs::closedir(dh);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return -error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-31
@@ -25,23 +25,6 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
//---------------------------------------------------------------------------------------
|
||||
//
|
||||
// To dynamically select std::filesystem where available on most platforms,
|
||||
// you could use:
|
||||
//
|
||||
// #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
|
||||
// #if __has_include(<filesystem>) && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
|
||||
// #define GHC_USE_STD_FS
|
||||
// #include <filesystem>
|
||||
// namespace fs = std::filesystem;
|
||||
// #endif
|
||||
// #endif
|
||||
// #ifndef GHC_USE_STD_FS
|
||||
// #include <ghc/filesystem.hpp>
|
||||
// namespace fs = ghc::filesystem;
|
||||
// #endif
|
||||
//
|
||||
//---------------------------------------------------------------------------------------
|
||||
#ifndef GHC_FILESYSTEM_H
|
||||
#define GHC_FILESYSTEM_H
|
||||
|
||||
@@ -53,7 +36,7 @@
|
||||
|
||||
#ifndef GHC_OS_DETECTED
|
||||
#if defined(__APPLE__) && defined(__MACH__)
|
||||
#define GHC_OS_MACOS
|
||||
#define GHC_OS_APPLE
|
||||
#elif defined(__linux__)
|
||||
#define GHC_OS_LINUX
|
||||
#if defined(__ANDROID__)
|
||||
@@ -181,7 +164,7 @@
|
||||
#include <langinfo.h>
|
||||
#endif
|
||||
#endif
|
||||
#ifdef GHC_OS_MACOS
|
||||
#ifdef GHC_OS_APPLE
|
||||
#include <Availability.h>
|
||||
#endif
|
||||
|
||||
@@ -308,7 +291,7 @@
|
||||
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch)
|
||||
#define GHC_FILESYSTEM_VERSION 10514L
|
||||
#define GHC_FILESYSTEM_VERSION 10515L
|
||||
|
||||
#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND))
|
||||
#define GHC_WITH_EXCEPTIONS
|
||||
@@ -1159,6 +1142,10 @@ GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std:
|
||||
GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept;
|
||||
#endif
|
||||
|
||||
#if defined(GHC_OS_WINDOWS) && (!defined(__GLIBCXX__) || (defined(_GLIBCXX_HAVE__WFOPEN) && defined(_GLIBCXX_USE_WCHAR_T)))
|
||||
#define GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
|
||||
#endif
|
||||
|
||||
// Non-C++17 add-on std::fstream wrappers with path
|
||||
template <class charT, class traits = std::char_traits<charT>>
|
||||
class basic_filebuf : public std::basic_filebuf<charT, traits>
|
||||
@@ -1170,7 +1157,7 @@ public:
|
||||
const basic_filebuf& operator=(const basic_filebuf&) = delete;
|
||||
basic_filebuf<charT, traits>* open(const path& p, std::ios_base::openmode mode)
|
||||
{
|
||||
#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
|
||||
#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
|
||||
return std::basic_filebuf<charT, traits>::open(p.wstring().c_str(), mode) ? this : 0;
|
||||
#else
|
||||
return std::basic_filebuf<charT, traits>::open(p.string().c_str(), mode) ? this : 0;
|
||||
@@ -1183,7 +1170,7 @@ class basic_ifstream : public std::basic_ifstream<charT, traits>
|
||||
{
|
||||
public:
|
||||
basic_ifstream() {}
|
||||
#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
|
||||
#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
|
||||
explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in)
|
||||
: std::basic_ifstream<charT, traits>(p.wstring().c_str(), mode)
|
||||
{
|
||||
@@ -1206,7 +1193,7 @@ class basic_ofstream : public std::basic_ofstream<charT, traits>
|
||||
{
|
||||
public:
|
||||
basic_ofstream() {}
|
||||
#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
|
||||
#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
|
||||
explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out)
|
||||
: std::basic_ofstream<charT, traits>(p.wstring().c_str(), mode)
|
||||
{
|
||||
@@ -1229,7 +1216,7 @@ class basic_fstream : public std::basic_fstream<charT, traits>
|
||||
{
|
||||
public:
|
||||
basic_fstream() {}
|
||||
#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__)
|
||||
#ifdef GHC_HAS_FSTREAM_OPEN_WITH_WCHAR
|
||||
explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out)
|
||||
: std::basic_fstream<charT, traits>(p.wstring().c_str(), mode)
|
||||
{
|
||||
@@ -3170,7 +3157,7 @@ GHC_INLINE path path::extension() const
|
||||
auto iter = end();
|
||||
const auto& fn = *--iter;
|
||||
impl_string_type::size_type pos = fn._path.rfind('.');
|
||||
if (pos != std::string::npos && pos > 0) {
|
||||
if (pos != std::string::npos && pos > 0 && fn._path != "..") {
|
||||
return path(fn._path.substr(pos), native_format);
|
||||
}
|
||||
}
|
||||
@@ -3987,7 +3974,7 @@ GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options
|
||||
}
|
||||
ssize_t br, bw;
|
||||
while (true) {
|
||||
do { br = ::read(in, buffer.data(), buffer.size()); } while(errno == EINTR);
|
||||
do { br = ::read(in, buffer.data(), buffer.size()); } while(errno == EINTR && !br);
|
||||
if(!br) {
|
||||
break;
|
||||
}
|
||||
@@ -4650,9 +4637,11 @@ 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) && \
|
||||
(__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)
|
||||
#elif defined(GHC_OS_APPLE) && \
|
||||
(__MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 \
|
||||
|| __IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < 110000 \
|
||||
|| __TV_OS_VERSION_MIN_REQUIRED && __TVOS_VERSION_MIN_REQUIRED < 110000 \
|
||||
|| __WATCH_OS_VERSION_MIN_REQUIRED && __WATCHOS_VERSION_MIN_REQUIRED < 40000)
|
||||
struct ::stat fs;
|
||||
if (::stat(p.c_str(), &fs) == 0) {
|
||||
struct ::timeval tv[2];
|
||||
@@ -5704,7 +5693,7 @@ public:
|
||||
, _entry(nullptr)
|
||||
{
|
||||
if (!path.empty()) {
|
||||
do { _dir = ::opendir(path.native().c_str()); } while(errno == EINTR);
|
||||
do { _dir = ::opendir(path.native().c_str()); } while(errno == EINTR && !_dir);
|
||||
if (!_dir) {
|
||||
auto error = errno;
|
||||
_base = filesystem::path();
|
||||
@@ -5731,7 +5720,7 @@ public:
|
||||
do {
|
||||
skip = false;
|
||||
errno = 0;
|
||||
do { _entry = ::readdir(_dir); } while(errno == EINTR);
|
||||
do { _entry = ::readdir(_dir); } while(errno == EINTR && !_entry);
|
||||
if (_entry) {
|
||||
_dir_entry._path = _base;
|
||||
_dir_entry._path.append_name(_entry->d_name);
|
||||
|
||||
+25
-3
@@ -230,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;
|
||||
@@ -241,6 +257,8 @@ namespace l
|
||||
|
||||
syslog_open();
|
||||
|
||||
l::warn_if_not_root();
|
||||
|
||||
memset(&ops,0,sizeof(fuse_operations));
|
||||
|
||||
args.argc = argc_;
|
||||
@@ -266,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,582 +0,0 @@
|
||||
// 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
@@ -1,427 +0,0 @@
|
||||
// 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
|
||||
@@ -12,7 +12,7 @@
|
||||
#define NONSTD_OPTIONAL_LITE_HPP
|
||||
|
||||
#define optional_lite_MAJOR 3
|
||||
#define optional_lite_MINOR 5
|
||||
#define optional_lite_MINOR 6
|
||||
#define optional_lite_PATCH 0
|
||||
|
||||
#define optional_lite_VERSION optional_STRINGIFY(optional_lite_MAJOR) "." optional_STRINGIFY(optional_lite_MINOR) "." optional_STRINGIFY(optional_lite_PATCH)
|
||||
@@ -1555,7 +1555,7 @@ private:
|
||||
void initialize( V && value )
|
||||
{
|
||||
assert( ! has_value() );
|
||||
contained.construct_value( std::move( value ) );
|
||||
contained.construct_value( std::forward<V>( value ) );
|
||||
has_value_ = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "num.hpp"
|
||||
#include "policy.hpp"
|
||||
#include "str.hpp"
|
||||
#include "syslog.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "fuse.h"
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -1,303 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "moodycamel/blockingconcurrentqueue.h"
|
||||
#include "syslog.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
|
||||
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(std::size_t const thread_count_ = std::thread::hardware_concurrency(),
|
||||
std::size_t const queue_depth_ = 1,
|
||||
std::string const name_ = {})
|
||||
: _queue(queue_depth_,thread_count_,thread_count_),
|
||||
_name(get_thread_name(name_))
|
||||
{
|
||||
syslog_debug("threadpool: spawning %zu threads of queue depth %zu named '%s'",
|
||||
thread_count_,
|
||||
queue_depth_,
|
||||
_name.c_str());
|
||||
|
||||
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_warning("threadpool: error spawning thread - %d (%s)",
|
||||
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_debug("threadpool: destroying %zu threads named '%s'",
|
||||
_threads.size(),
|
||||
_name.c_str());
|
||||
|
||||
for(auto t : _threads)
|
||||
pthread_cancel(t);
|
||||
|
||||
Func f;
|
||||
while(_queue.try_dequeue(f))
|
||||
continue;
|
||||
|
||||
for(auto t : _threads)
|
||||
pthread_join(t,NULL);
|
||||
}
|
||||
|
||||
private:
|
||||
static
|
||||
std::string
|
||||
get_thread_name(std::string const name_)
|
||||
{
|
||||
if(!name_.empty())
|
||||
return name_;
|
||||
|
||||
char name[16];
|
||||
pthread_getname_np(pthread_self(),name,sizeof(name));
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static
|
||||
void*
|
||||
start_routine(void *arg_)
|
||||
{
|
||||
ThreadPool *btp = static_cast<ThreadPool*>(arg_);
|
||||
ThreadPool::Func func;
|
||||
ThreadPool::Queue &q = btp->_queue;
|
||||
moodycamel::ConsumerToken ctok(btp->_queue);
|
||||
|
||||
while(true)
|
||||
{
|
||||
q.wait_dequeue(ctok,func);
|
||||
|
||||
func();
|
||||
}
|
||||
|
||||
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_warning("threadpool: error spawning thread - %d (%s)",
|
||||
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_debug("threadpool: 1 thread added to pool '%s' 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;
|
||||
}
|
||||
}
|
||||
|
||||
char name[16];
|
||||
pthread_getname_np(t,name,sizeof(name));
|
||||
syslog_debug("threadpool: 1 thread removed from pool '%s' named '%s'",
|
||||
_name.c_str(),
|
||||
name);
|
||||
|
||||
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,10};
|
||||
while(true)
|
||||
{
|
||||
if(_queue.try_enqueue(ptok_,f_))
|
||||
return;
|
||||
::nanosleep(&ts,NULL);
|
||||
ts.tv_nsec += 10;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename FuncType>
|
||||
void
|
||||
enqueue_work(FuncType &&f_)
|
||||
{
|
||||
timespec ts = {0,10};
|
||||
while(true)
|
||||
{
|
||||
if(_queue.try_enqueue(f_))
|
||||
return;
|
||||
::nanosleep(&ts,NULL);
|
||||
ts.tv_nsec += 10;
|
||||
}
|
||||
}
|
||||
|
||||
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,10};
|
||||
while(true)
|
||||
{
|
||||
if(_queue.try_enqueue(work))
|
||||
break;
|
||||
::nanosleep(&ts,NULL);
|
||||
ts.tv_nsec += 10;
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
public:
|
||||
std::vector<pthread_t>
|
||||
threads() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(_threads_mutex);
|
||||
|
||||
return _threads;
|
||||
}
|
||||
|
||||
private:
|
||||
Queue _queue;
|
||||
|
||||
private:
|
||||
std::string const _name;
|
||||
std::vector<pthread_t> _threads;
|
||||
mutable std::mutex _threads_mutex;
|
||||
};
|
||||
+73
-10
@@ -8,8 +8,8 @@
|
||||
uint64_t hash=wyhash(s.c_str(), s.size(), 0, _wyp);
|
||||
*/
|
||||
|
||||
#ifndef wyhash_final_version_4
|
||||
#define wyhash_final_version_4
|
||||
#ifndef wyhash_final_version_4_2
|
||||
#define wyhash_final_version_4_2
|
||||
|
||||
#ifndef WYHASH_CONDOM
|
||||
//protections that produce different results:
|
||||
@@ -21,7 +21,7 @@
|
||||
#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
|
||||
#define WYHASH_32BIT_MUM 0
|
||||
#endif
|
||||
|
||||
//includes
|
||||
@@ -52,7 +52,7 @@ static inline void _wymum(uint64_t *A, uint64_t *B){
|
||||
*A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll;
|
||||
#endif
|
||||
#elif defined(__SIZEOF_INT128__)
|
||||
__uint128_t r=*A; r*=*B;
|
||||
__uint128_t r=*A; r*=*B;
|
||||
#if(WYHASH_CONDOM>1)
|
||||
*A^=(uint64_t)r; *B^=(uint64_t)(r>>64);
|
||||
#else
|
||||
@@ -123,15 +123,15 @@ static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const
|
||||
else a=b=0;
|
||||
}
|
||||
else{
|
||||
size_t i=len;
|
||||
if(_unlikely_(i>48)){
|
||||
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));
|
||||
}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; }
|
||||
@@ -142,13 +142,13 @@ static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const
|
||||
}
|
||||
|
||||
//the default secret parameters
|
||||
static const uint64_t _wyp[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull};
|
||||
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^=0xa0761d6478bd642full; B^=0xe7037ed1a0b428dbull; _wymum(&A,&B); return _wymix(A^0xa0761d6478bd642full,B^0xe7037ed1a0b428dbull);}
|
||||
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+=0xa0761d6478bd642full; return _wymix(*seed,*seed^0xe7037ed1a0b428dbull);}
|
||||
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;}
|
||||
@@ -173,6 +173,68 @@ static inline uint64_t wytrand(uint64_t *seed){
|
||||
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 };
|
||||
@@ -197,6 +259,7 @@ static inline void make_secret(uint64_t seed, uint64_t *secret){
|
||||
if(x!=32){ ok=0; break; }
|
||||
#endif
|
||||
}
|
||||
if(ok&&!is_prime(secret[i])) ok=0;
|
||||
}while(!ok);
|
||||
}
|
||||
}
|
||||
|
||||
+427
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
ISC License
|
||||
|
||||
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
|
||||
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 _GNU_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
typedef char IOCTL_BUF[4096];
|
||||
#define IOCTL_APP_TYPE 0xDF
|
||||
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
|
||||
|
||||
#define LOAD_FUNC(func) \
|
||||
do \
|
||||
{ \
|
||||
if(!_libc_##func) \
|
||||
_libc_##func = (func##_func_t)dlsym(RTLD_NEXT,#func); \
|
||||
assert(_libc_##func != NULL); \
|
||||
} \
|
||||
while(0)
|
||||
|
||||
typedef int (*open_func_t)(const char*, int, ...);
|
||||
typedef int (*open64_func_t)(const char*, int, ...);
|
||||
typedef int (*openat_func_t)(int, const char*, int, ...);
|
||||
typedef int (*openat64_func_t)(int, const char*, int, ...);
|
||||
typedef int (*creat_func_t)(const char*, mode_t);
|
||||
typedef int (*creat64_func_t)(const char*, mode_t);
|
||||
typedef FILE* (*fopen_func_t)(const char*, const char*);
|
||||
typedef FILE* (*fopen64_func_t)(const char*, const char*);
|
||||
|
||||
static open_func_t _libc_open = NULL;
|
||||
static open64_func_t _libc_open64 = NULL;
|
||||
static openat_func_t _libc_openat = NULL;
|
||||
static openat64_func_t _libc_openat64 = NULL;
|
||||
static fopen_func_t _libc_fopen = NULL;
|
||||
static fopen64_func_t _libc_fopen64 = NULL;
|
||||
static creat_func_t _libc_creat = NULL;
|
||||
static creat64_func_t _libc_creat64 = NULL;
|
||||
|
||||
static
|
||||
int
|
||||
get_underlying_filepath(int fd_,
|
||||
char *filepath_)
|
||||
{
|
||||
int rv;
|
||||
|
||||
strcpy(filepath_,"fullpath");
|
||||
rv = ioctl(fd_,IOCTL_FILE_INFO,filepath_);
|
||||
if(rv == -1)
|
||||
return -1;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
strip_exec(const char *orig_mode_,
|
||||
char *new_mode_)
|
||||
{
|
||||
size_t i;
|
||||
size_t j;
|
||||
|
||||
for(i = j = 0; orig_mode_[i]; i++)
|
||||
{
|
||||
if(orig_mode_[i] == 'x')
|
||||
continue;
|
||||
new_mode_[j++] = orig_mode_[i];
|
||||
}
|
||||
|
||||
new_mode_[j] = '\0';
|
||||
}
|
||||
|
||||
int
|
||||
open(const char *pathname_,
|
||||
int flags_,
|
||||
...)
|
||||
{
|
||||
int rv;
|
||||
int fd;
|
||||
mode_t mode;
|
||||
struct stat st;
|
||||
|
||||
LOAD_FUNC(open);
|
||||
|
||||
mode = 0;
|
||||
if(flags_ & (O_CREAT|O_TMPFILE))
|
||||
{
|
||||
va_list args;
|
||||
va_start(args,flags_);
|
||||
mode = va_arg(args,mode_t);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
fd = _libc_open(pathname_,flags_,mode);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
|
||||
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
|
||||
return fd;
|
||||
rv = fstat(fd,&st);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
if((st.st_mode & S_IFMT) != S_IFREG)
|
||||
return fd;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
flags_ &= ~(O_EXCL|O_CREAT);
|
||||
rv = _libc_open(real_pathname,flags_,mode);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
close(fd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
open64(const char *pathname_,
|
||||
int flags_,
|
||||
...)
|
||||
{
|
||||
int rv;
|
||||
int fd;
|
||||
mode_t mode;
|
||||
struct stat st;
|
||||
|
||||
LOAD_FUNC(open64);
|
||||
|
||||
mode = 0;
|
||||
if(flags_ & (O_CREAT|O_TMPFILE))
|
||||
{
|
||||
va_list args;
|
||||
va_start(args,flags_);
|
||||
mode = va_arg(args,mode_t);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
fd = _libc_open64(pathname_,flags_,mode);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
|
||||
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
|
||||
return fd;
|
||||
rv = fstat(fd,&st);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
if((st.st_mode & S_IFMT) != S_IFREG)
|
||||
return fd;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
flags_ &= ~(O_EXCL|O_CREAT);
|
||||
rv = _libc_open64(real_pathname,flags_,mode);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
close(fd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
openat(int dirfd_,
|
||||
const char *pathname_,
|
||||
int flags_,
|
||||
...)
|
||||
{
|
||||
int rv;
|
||||
int fd;
|
||||
mode_t mode;
|
||||
struct stat st;
|
||||
|
||||
LOAD_FUNC(openat);
|
||||
|
||||
mode = 0;
|
||||
if(flags_ & (O_CREAT|O_TMPFILE))
|
||||
{
|
||||
va_list args;
|
||||
va_start(args,flags_);
|
||||
mode = va_arg(args,mode_t);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
fd = _libc_openat(dirfd_,pathname_,flags_,mode);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
|
||||
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
|
||||
return fd;
|
||||
rv = fstat(fd,&st);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
if((st.st_mode & S_IFMT) != S_IFREG)
|
||||
return fd;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
flags_ &= ~(O_EXCL|O_CREAT);
|
||||
rv = _libc_openat(dirfd_,real_pathname,flags_,mode);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
close(fd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
openat64(int dirfd_,
|
||||
const char *pathname_,
|
||||
int flags_,
|
||||
...)
|
||||
{
|
||||
int rv;
|
||||
int fd;
|
||||
mode_t mode;
|
||||
struct stat st;
|
||||
|
||||
LOAD_FUNC(openat64);
|
||||
|
||||
mode = 0;
|
||||
if(flags_ & (O_CREAT|O_TMPFILE))
|
||||
{
|
||||
va_list args;
|
||||
va_start(args,flags_);
|
||||
mode = va_arg(args,mode_t);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
fd = _libc_openat64(dirfd_,pathname_,flags_,mode);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
|
||||
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
|
||||
return fd;
|
||||
rv = fstat(fd,&st);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
if((st.st_mode & S_IFMT) != S_IFREG)
|
||||
return fd;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
flags_ &= ~(O_EXCL|O_CREAT);
|
||||
rv = _libc_openat64(dirfd_,real_pathname,flags_,mode);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
close(fd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
FILE*
|
||||
fopen(const char *pathname_,
|
||||
const char *mode_)
|
||||
{
|
||||
int fd;
|
||||
int rv;
|
||||
FILE *f;
|
||||
FILE *f2;
|
||||
struct stat st;
|
||||
|
||||
LOAD_FUNC(fopen);
|
||||
|
||||
f = _libc_fopen(pathname_,mode_);
|
||||
if(f == NULL)
|
||||
return NULL;
|
||||
|
||||
fd = fileno(f);
|
||||
if(fd == -1)
|
||||
return f;
|
||||
|
||||
rv = fstat(fd,&st);
|
||||
if(rv == -1)
|
||||
return f;
|
||||
if((st.st_mode & S_IFMT) != S_IFREG)
|
||||
return f;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return f;
|
||||
|
||||
char new_mode[64];
|
||||
strip_exec(mode_,new_mode);
|
||||
f2 = _libc_fopen(real_pathname,new_mode);
|
||||
if(f2 == NULL)
|
||||
return f;
|
||||
|
||||
fclose(f);
|
||||
|
||||
return f2;
|
||||
}
|
||||
|
||||
FILE*
|
||||
fopen64(const char *pathname_,
|
||||
const char *mode_)
|
||||
{
|
||||
int fd;
|
||||
int rv;
|
||||
FILE *f;
|
||||
FILE *f2;
|
||||
struct stat st;
|
||||
|
||||
LOAD_FUNC(fopen64);
|
||||
|
||||
f = _libc_fopen64(pathname_,mode_);
|
||||
if(f == NULL)
|
||||
return NULL;
|
||||
|
||||
fd = fileno(f);
|
||||
if(fd == -1)
|
||||
return f;
|
||||
|
||||
rv = fstat(fd,&st);
|
||||
if(rv == -1)
|
||||
return f;
|
||||
if((st.st_mode & S_IFMT) != S_IFREG)
|
||||
return f;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return f;
|
||||
|
||||
char new_mode[64];
|
||||
strip_exec(mode_,new_mode);
|
||||
f2 = _libc_fopen64(real_pathname,new_mode);
|
||||
if(f2 == NULL)
|
||||
return f;
|
||||
|
||||
fclose(f);
|
||||
|
||||
return f2;
|
||||
}
|
||||
|
||||
int
|
||||
creat(const char *pathname_,
|
||||
mode_t mode_)
|
||||
{
|
||||
int fd;
|
||||
int rv;
|
||||
|
||||
LOAD_FUNC(creat);
|
||||
|
||||
fd = _libc_creat(pathname_,mode_);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
rv = _libc_creat(real_pathname,mode_);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
close(fd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
creat64(const char *pathname_,
|
||||
mode_t mode_)
|
||||
{
|
||||
int fd;
|
||||
int rv;
|
||||
|
||||
LOAD_FUNC(creat64);
|
||||
|
||||
fd = _libc_creat64(pathname_,mode_);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
|
||||
IOCTL_BUF real_pathname;
|
||||
rv = get_underlying_filepath(fd,real_pathname);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
rv = _libc_creat64(real_pathname,mode_);
|
||||
if(rv == -1)
|
||||
return fd;
|
||||
|
||||
close(fd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
Reference in New Issue
Block a user