Compare commits

...
21 Commits
Author SHA1 Message Date
trapexitandGitHub b21172c609 Merge pull request #1321 from trapexit/allowmmap
Add support for 'direct-io-allow-mmap' if supported by kernel
2024-03-21 21:54:04 -04:00
Antonio SJ Musumeci f0444a1ca9 Add support for 'direct-io-allow-mmap' if supported by kernel 2024-03-21 20:48:42 -05:00
trapexitandGitHub 977d04229f Merge pull request #1320 from trapexit/readme
Suggest cache.files=auto-full rather than partial
2024-03-21 21:36:27 -04:00
Antonio SJ Musumeci 757ae66327 Suggest cache.files=auto-full rather than partial 2024-03-17 00:47:33 -05:00
trapexitandGitHub 42d0b5734c Merge pull request #1312 from trapexit/preload-tmpfile
Special case O_TMPFILE in preload
2024-02-29 15:41:22 -05:00
Antonio SJ Musumeci 78050ed6b0 Special case O_TMPFILE in preload 2024-02-29 14:33:27 -06:00
trapexitandGitHub 19ee44afe7 Merge pull request #1309 from trapexit/root-nodeid
Root nodeid
2024-02-25 18:29:58 -05:00
Antonio SJ Musumeci a234e9446c Update README to remove unnecessary references to export-support for NFS 2024-02-25 17:27:08 -06:00
Antonio SJ Musumeci 951eb9540d Ensure lookups of FUSE_ROOT_ID always return generation=0 2024-02-25 17:14:15 -06:00
trapexitandGitHub 85a936c95e Merge pull request #1308 from trapexit/readme
Change mount type from fuse.mergerfs to mergerfs in README
2024-02-24 02:09:40 -05:00
Antonio SJ Musumeci 8210e63a66 Change mount type from fuse.mergerfs to mergerfs in README 2024-02-24 01:08:30 -06:00
trapexitandGitHub facdb66746 Merge pull request #1307 from trapexit/readme
Add NFS options to remote filesystems section of readme
2024-02-19 19:20:41 -06:00
Antonio SJ Musumeci 1916e54a24 Add NFS options to remote filesystems section of readme 2024-02-19 19:19:00 -06:00
trapexitandGitHub 7fabe515f4 Merge pull request #1306 from trapexit/export-support
Add export-support option as possible workaround for NFS EIO issues
2024-02-19 19:00:54 -06:00
Antonio SJ Musumeci 0ca5d6aad0 Add export-support option as possible workaround for NFS EIO issues 2024-02-19 18:58:50 -06:00
trapexitandGitHub 0c1aeb411d Merge pull request #1305 from trapexit/lib-updates
Library updates
2024-02-18 17:56:39 -06:00
Antonio SJ Musumeci 17fdbf7f42 Update fuse_kernel.h 2024-02-18 17:50:34 -06:00
Antonio SJ Musumeci 27349e41ef Update fmt 2024-02-18 17:24:18 -06:00
Antonio SJ Musumeci b3ce95c422 Update nonstd::optional 2024-02-18 17:24:18 -06:00
Antonio SJ Musumeci cedc9f0f25 Update ghc::filesystem 2024-02-18 17:24:18 -06:00
trapexitandGitHub d68ad9ac01 Expand the preload docs (#1299) 2024-01-30 00:03:39 -06:00
26 changed files with 2452 additions and 2072 deletions
+236 -73
View File
@@ -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
@@ -385,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
@@ -415,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
@@ -674,6 +687,19 @@ it will only perform the hack when the path in question includes
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
@@ -1154,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
@@ -1292,17 +1318,38 @@ typedef char IOCTL_BUF[4096];
EXPERIMENTAL
This preloadable library overrides the creation and opening of files
in order to simulate passthrough file IO. It catches the
open/creat/fopen calls, lets mergerfs do the call, queries mergerfs
for the branch the file exists on, and reopens the file on the underlying
filesystem. Meaning that you will get native read/write performance.
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.
This will only work on dynamically linked software. Anything
statically compiled will not work. Many GoLang and Rust apps are
statically compiled.
`/usr/lib/mergerfs/preload.so`
The library will not interfere with non-mergerfs filesystems.
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.
@@ -1314,28 +1361,53 @@ 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
`/mnt/mergerfs`.
Assume `/mnt/fs0` and `/mnt/fs1` are pooled with mergerfs at `/media`.
Remember that you must bind into the container the original host paths
to the same locations otherwise the preload module will not be able to
find the files.
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
@@ -1799,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
@@ -1955,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?"
@@ -2191,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.
@@ -2314,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.
+24 -60
View File
@@ -108,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
@@ -150,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;
+119 -56
View File
@@ -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 {
+30 -14
View File
@@ -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,
+10 -2
View File
@@ -1375,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);
@@ -1386,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,
@@ -1523,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);
}
+68 -33
View File
@@ -294,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);
}
@@ -1092,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);
@@ -1122,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
{
@@ -1186,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;
@@ -1242,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;
+231 -56
View File
@@ -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)
@@ -512,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
@@ -547,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.
@@ -837,6 +854,18 @@ 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 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.
@@ -1553,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
@@ -1726,18 +1755,41 @@ unused files to be released from memory.
.PP
EXPERIMENTAL
.PP
This preloadable library overrides the creation and opening of files in
order to simulate passthrough file IO.
It catches the open/creat/fopen calls, lets mergerfs do the call,
queries mergerfs for the branch the file exists on, and reopens the file
on the underlying filesystem.
Meaning that you will get native read/write performance.
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
This will only work on dynamically linked software.
\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.
@@ -1754,22 +1806,52 @@ LD_PRELOAD=/usr/lib/mergerfs/preload.so touch /mnt/mergerfs/filename
.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]/mnt/mergerfs\f[R].
mergerfs at \f[C]/media\f[R].
.PP
Remember that you must bind into the container the original host paths
to the same locations otherwise the preload module will not be able to
find the files.
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
@@ -2322,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.
@@ -2486,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
@@ -2727,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
@@ -2853,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
+6
View File
@@ -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,7 +95,9 @@ Config::Config()
cache_symlinks(false),
category(func),
direct_io(false),
direct_io_allow_mmap(true),
dropcacheonclose(false),
export_support(true),
flushonclose(FlushOnClose::ENUM::OPENED_FOR_WRITE),
follow_symlinks(FollowSymlinks::ENUM::NEVER),
fsname(),
@@ -148,7 +152,9 @@ 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;
+2
View File
@@ -117,7 +117,9 @@ public:
ConfigBOOL cache_symlinks;
Categories category;
ConfigBOOL direct_io;
ConfigBOOL direct_io_allow_mmap;
ConfigBOOL dropcacheonclose;
ConfigBOOL export_support;
FlushOnClose flushonclose;
FollowSymlinks follow_symlinks;
ConfigSTR fsname;
+7 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+93 -96
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+29 -25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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")));
}
+4 -2
View File
@@ -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;
+20 -31
View File
@@ -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);
+2 -2
View File
@@ -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;
}
+8 -8
View File
@@ -107,7 +107,7 @@ open(const char *pathname_,
LOAD_FUNC(open);
mode = 0;
if(flags_ & O_CREAT)
if(flags_ & (O_CREAT|O_TMPFILE))
{
va_list args;
va_start(args,flags_);
@@ -119,7 +119,7 @@ open(const char *pathname_,
if(fd == -1)
return -1;
if(flags_ & (O_DIRECTORY|O_PATH))
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
return fd;
rv = fstat(fd,&st);
if(rv == -1)
@@ -155,7 +155,7 @@ open64(const char *pathname_,
LOAD_FUNC(open64);
mode = 0;
if(flags_ & O_CREAT)
if(flags_ & (O_CREAT|O_TMPFILE))
{
va_list args;
va_start(args,flags_);
@@ -167,7 +167,7 @@ open64(const char *pathname_,
if(fd == -1)
return -1;
if(flags_ & (O_DIRECTORY|O_PATH))
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
return fd;
rv = fstat(fd,&st);
if(rv == -1)
@@ -204,7 +204,7 @@ openat(int dirfd_,
LOAD_FUNC(openat);
mode = 0;
if(flags_ & O_CREAT)
if(flags_ & (O_CREAT|O_TMPFILE))
{
va_list args;
va_start(args,flags_);
@@ -216,7 +216,7 @@ openat(int dirfd_,
if(fd == -1)
return -1;
if(flags_ & (O_DIRECTORY|O_PATH))
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
return fd;
rv = fstat(fd,&st);
if(rv == -1)
@@ -253,7 +253,7 @@ openat64(int dirfd_,
LOAD_FUNC(openat64);
mode = 0;
if(flags_ & O_CREAT)
if(flags_ & (O_CREAT|O_TMPFILE))
{
va_list args;
va_start(args,flags_);
@@ -265,7 +265,7 @@ openat64(int dirfd_,
if(fd == -1)
return -1;
if(flags_ & (O_DIRECTORY|O_PATH))
if(flags_ & (O_DIRECTORY|O_PATH|O_TMPFILE))
return fd;
rv = fstat(fd,&st);
if(rv == -1)