From 8adebc9489cc7b86e4cc4879a02a699d97e98e97 Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Sun, 21 Jun 2020 14:45:14 -0400 Subject: [PATCH] new features: follow-symlinks, rename-exdev, link-exdev * follow-symlinks: allows mergerfs to transparently follow symlinks * link-exdev: in the event a link returns EXDEV create a symlink instead * rename-exdev: in the event a rename returns EXDEV move the oldpath and create a symlink for the newpath --- Makefile | 2 +- README.md | 51 + libfuse/Makefile | 2 +- libfuse/include/fuse.h | 17 +- libfuse/lib/fuse.c | 158 +- man/mergerfs.1 | 81 + src/config.cpp | 8 +- src/config.hpp | 9 +- src/config_follow_symlinks.cpp | 58 + src/config_follow_symlinks.hpp | 30 + src/config_link_exdev.cpp | 58 + src/config_link_exdev.hpp | 30 + src/config_rename_exdev.cpp | 54 + src/config_rename_exdev.hpp | 29 + src/enum.hpp | 4 +- src/fs_mkdir.hpp | 22 +- src/fs_mkdir_as_root.hpp | 35 + src/fs_symlink.hpp | 21 +- src/fuse_getattr.cpp | 66 +- src/fuse_link.cpp | 324 +- src/fuse_link.hpp | 8 +- src/fuse_rename.cpp | 522 +-- src/fuse_rmdir.cpp | 34 +- src/fuse_symlink.cpp | 50 +- src/fuse_symlink.hpp | 15 +- src/ghc/filesystem.hpp | 5742 ++++++++++++++++++++++++++++++++ 26 files changed, 6980 insertions(+), 450 deletions(-) create mode 100644 src/config_follow_symlinks.cpp create mode 100644 src/config_follow_symlinks.hpp create mode 100644 src/config_link_exdev.cpp create mode 100644 src/config_link_exdev.hpp create mode 100644 src/config_rename_exdev.cpp create mode 100644 src/config_rename_exdev.hpp create mode 100644 src/fs_mkdir_as_root.hpp create mode 100644 src/ghc/filesystem.hpp diff --git a/Makefile b/Makefile index ddbc8a2e..cd614eec 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ USE_XATTR = 1 UGID_USE_RWLOCK = 0 ifeq ($(DEBUG),1) -OPT_FLAGS := -O0 -g +OPT_FLAGS := -O0 -g -fsanitize=undefined else OPT_FLAGS := -O2 endif diff --git a/README.md b/README.md index 673ecfcf..1bf2ed31 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,9 @@ These options are the same regardless you use them with the `mergerfs` commandli * **statfs=base|full**: Controls how statfs works. 'base' means it will always use all branches in statfs calculations. 'full' is in effect path preserving and only includes drives where the path exists. (default: base) * **statfs_ignore=none|ro|nc**: 'ro' will cause statfs calculations to ignore available space for branches mounted or tagged as 'read-only' or 'no create'. 'nc' will ignore available space for branches tagged as 'no create'. (default: none) * **nfsopenhack=off|git|all**: A workaround for exporting mergerfs over NFS where there are issues with creating files for write while setting the mode to read-only. (default: off) +* **follow-symlinks=never|directory|regular|all**: Turns symlinks into what they point to. (default: never) +* **link-exdev=passthrough|rel-symlink|abs-base-symlink|abs-pool-symlink**: When a link fails with EXDEV optionally create a symlink to the file instead. +* **rename-exdev=passthrough|rel-symlink|abs-symlink**: When a rename fails with EXDEV optionally move the file to a special directory and symlink to it. * **posix_acl=BOOL**: Enable POSIX ACL support (if supported by kernel and underlying filesystem). (default: false) * **async_read=BOOL**: Perform reads asynchronously. If disabled or unavailable the kernel will ensure there is at most one pending read request per file handle and will attempt to order requests by offset. (default: true) * **fuse_msg_size=UINT**: Set the max number of pages per FUSE message. Only available on Linux >= 4.20 and ignored otherwise. (min: 1; max: 256; default: 256) @@ -240,6 +243,54 @@ In Linux 4.20 a new feature was added allowing the negotiation of the max messag Since there should be no downsides to increasing `fuse_msg_size` / `max_pages`, outside a minor bump in RAM usage due to larger message buffers, mergerfs defaults the value to 256. On kernels before 4.20 the value has no effect. The reason the value is configurable is to enable experimentation and benchmarking. See the BENCHMARKING section for examples. +### follow-symlinks + +This feature, when enabled, will cause symlinks to be interpreted by mergerfs as their target (depending on the mode). + +When there is a getattr/stat request for a file mergerfs will check if the file is a symlink and depending on the `follow-symlinks` setting will replace the information about the symlink with that of that which it points to. + +When unlink'ing or rmdir'ing the followed symlink it will remove the symlink itself and not that which it points to. + +* never: Behave as normal. Symlinks are treated as such. +* directory: Resolve symlinks only which point to directories. +* regular: Resolve symlinks only which point to regular files. +* all: Resolve all symlinks to that which they point to. + +Symlinks which do not point to anything are left as is. + +WARNING: This feature works but there might be edge cases yet found. If you find any odd behaviors please file a ticket on [github](https://github.com/trapexit/mergerfs/issues). + + +### link-exdev + +If using path preservation and a `link` fails with EXDEV make a call to `symlink` where the `target` is the `oldlink` and the `linkpath` is the `newpath`. The `target` value is determined by the value of `link-exdev`. + +* passthrough: Return EXDEV as normal. +* rel-symlink: A relative path from the `newpath`. +* abs-base-symlink: A absolute value using the underlying branch. +* abs-pool-symlink: A absolute value using the mergerfs mount point. + +NOTE: It is possible that some applications check the file they link. In those cases it is possible it will error or complain. + + +### rename-exdev + +If using path preservation and a `rename` fails with EXDEV: + +1. Move file from **/branch/a/b/c** to **/branch/.mergerfs_rename_exdev/a/b/c**. +2. symlink the rename's `newpath` to the moved file. + +The `target` value is determined by the value of `rename-exdev`. + +* passthrough: Return EXDEV as normal. +* rel-symlink: A relative path from the `newpath`. +* abs-symlink: A absolute value using the mergerfs mount point. + +NOTE: It is possible that some applications check the file they rename. In those cases it is possible it will error or complain. + +NOTE: The reason `abs-symlink` is not split into two like `link-exdev` is due to the complexities in managing absolute base symlinks when multiple `oldpaths` exist. + + ### symlinkify Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degradation. This feature will turn non-directories which are not writable into symlinks to the original file found by the `readlink` policy after the mtime and ctime are older than the timeout. diff --git a/libfuse/Makefile b/libfuse/Makefile index 61a1a11f..03d0a0c2 100644 --- a/libfuse/Makefile +++ b/libfuse/Makefile @@ -10,7 +10,7 @@ INSTALLUTILS := endif ifeq ($(DEBUG),1) -OPT_FLAGS := -O0 -g +OPT_FLAGS := -O0 -g -fsanitize=undefined else OPT_FLAGS := -O2 endif diff --git a/libfuse/include/fuse.h b/libfuse/include/fuse.h index e6db214b..cc79b884 100644 --- a/libfuse/include/fuse.h +++ b/libfuse/include/fuse.h @@ -121,13 +121,13 @@ struct fuse_operations int (*rmdir) (const char *); /** Create a symbolic link */ - int (*symlink) (const char *, const char *); + int (*symlink) (const char *, const char *, struct stat *, fuse_timeouts_t *); /** Rename a file */ int (*rename) (const char *, const char *); /** Create a hard link to a file */ - int (*link) (const char *, const char *); + int (*link) (const char *, const char *, struct stat *, fuse_timeouts_t *); /** Change the permission bits of a file */ int (*chmod) (const char *, mode_t); @@ -794,9 +794,16 @@ int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath, const char *newpath); int fuse_fs_unlink(struct fuse_fs *fs, const char *path); int fuse_fs_rmdir(struct fuse_fs *fs, const char *path); -int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, - const char *path); -int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath); +int fuse_fs_symlink(struct fuse_fs *fs, + const char *linkname, + const char *path, + struct stat *st, + fuse_timeouts_t *timeouts); +int fuse_fs_link(struct fuse_fs *fs, + const char *oldpath, + const char *newpath, + struct stat *st, + fuse_timeouts_t *timeouts); int fuse_fs_release(struct fuse_fs *fs, fuse_file_info_t *fi); int fuse_fs_open(struct fuse_fs *fs, const char *path, diff --git a/libfuse/lib/fuse.c b/libfuse/lib/fuse.c index 02b49563..b4c94040 100644 --- a/libfuse/lib/fuse.c +++ b/libfuse/lib/fuse.c @@ -1650,25 +1650,30 @@ fuse_fs_rmdir(struct fuse_fs *fs, } int -fuse_fs_symlink(struct fuse_fs *fs, - const char *linkname, - const char *path) +fuse_fs_symlink(struct fuse_fs *fs_, + const char *linkname_, + const char *path_, + struct stat *st_, + fuse_timeouts_t *timeouts_) { - if(fs->op.symlink == NULL) + + if(fs_->op.symlink == NULL) return -ENOSYS; - fuse_get_context()->private_data = fs->user_data; + if(fs_->debug) + fprintf(stderr,"symlink %s %s\n",linkname_,path_); - if(fs->debug) - fprintf(stderr,"symlink %s %s\n",linkname,path); + fuse_get_context()->private_data = fs_->user_data; - return fs->op.symlink(linkname,path); + return fs_->op.symlink(linkname_,path_,st_,timeouts_); } int -fuse_fs_link(struct fuse_fs *fs, - const char *oldpath, - const char *newpath) +fuse_fs_link(struct fuse_fs *fs, + const char *oldpath, + const char *newpath, + struct stat *st_, + fuse_timeouts_t *timeouts_) { if(fs->op.link == NULL) return -ENOSYS; @@ -1678,7 +1683,7 @@ fuse_fs_link(struct fuse_fs *fs, if(fs->debug) fprintf(stderr,"link %s %s\n",oldpath,newpath); - return fs->op.link(oldpath,newpath); + return fs->op.link(oldpath,newpath,st_,timeouts_); } int @@ -2541,6 +2546,37 @@ update_stat(struct node *node_, node_->mtim = stnew_->st_mtim; } +static +int +set_path_info(struct fuse *f, + fuse_ino_t nodeid, + const char *name, + struct fuse_entry_param *e) +{ + struct node *node; + + node = find_node(f,nodeid,name); + if(node == NULL) + return -ENOMEM; + + e->ino = node->nodeid; + e->generation = node->generation; + + pthread_mutex_lock(&f->lock); + update_stat(node,&e->attr); + pthread_mutex_unlock(&f->lock); + + set_stat(f,e->ino,&e->attr); + if(f->conf.debug) + fprintf(stderr, + " NODEID: %llu\n" + " GEN: %llu\n", + (unsigned long long)e->ino, + (unsigned long long)e->generation); + + return 0; +} + static int lookup_path(struct fuse *f, @@ -2550,43 +2586,18 @@ lookup_path(struct fuse *f, struct fuse_entry_param *e, fuse_file_info_t *fi) { - int res; + int rv; memset(e,0,sizeof(struct fuse_entry_param)); - res = ((fi == NULL) ? - fuse_fs_getattr(f->fs,path,&e->attr,&e->timeout) : - fuse_fs_fgetattr(f->fs,&e->attr,fi,&e->timeout)); + rv = ((fi == NULL) ? + fuse_fs_getattr(f->fs,path,&e->attr,&e->timeout) : + fuse_fs_fgetattr(f->fs,&e->attr,fi,&e->timeout)); - if(res == 0) - { - struct node *node; + if(rv) + return rv; - node = find_node(f,nodeid,name); - if(node == NULL) - { - res = -ENOMEM; - } - else - { - e->ino = node->nodeid; - e->generation = node->generation; - - pthread_mutex_lock(&f->lock); - update_stat(node,&e->attr); - pthread_mutex_unlock(&f->lock); - - set_stat(f,e->ino,&e->attr); - if(f->conf.debug) - fprintf(stderr, - " NODEID: %llu\n" - " GEN: %llu\n", - (unsigned long long)e->ino, - (unsigned long long)e->generation); - } - } - - return res; + return set_path_info(f,nodeid,name,e); } static @@ -3236,26 +3247,28 @@ fuse_lib_rmdir(fuse_req_t req, static void -fuse_lib_symlink(fuse_req_t req, - const char *linkname, - fuse_ino_t parent, - const char *name) +fuse_lib_symlink(fuse_req_t req_, + const char *linkname_, + fuse_ino_t parent_, + const char *name_) { - struct fuse *f = req_fuse_prepare(req); - struct fuse_entry_param e; + int rv; char *path; - int err; + struct fuse *f; + struct fuse_entry_param e = {0}; - err = get_path_name(f,parent,name,&path); - if(!err) + f = req_fuse_prepare(req_); + + rv = get_path_name(f,parent_,name_,&path); + if(rv == 0) { - err = fuse_fs_symlink(f->fs,linkname,path); - if(!err) - err = lookup_path(f,parent,name,path,&e,NULL); - free_path(f,parent,path); + rv = fuse_fs_symlink(f->fs,linkname_,path,&e.attr,&e.timeout); + if(rv == 0) + rv = set_path_info(f,parent_,name_,&e); + free_path(f,parent_,path); } - reply_entry(req,&e,err); + reply_entry(req_,&e,rv); } static @@ -3298,27 +3311,32 @@ fuse_lib_rename(fuse_req_t req, reply_err(req,err); } -static void fuse_lib_link(fuse_req_t req,fuse_ino_t ino,fuse_ino_t newparent, - const char *newname) +static +void +fuse_lib_link(fuse_req_t req, + fuse_ino_t ino, + fuse_ino_t newparent, + const char *newname) { - struct fuse *f = req_fuse_prepare(req); - struct fuse_entry_param e; + int rv; char *oldpath; char *newpath; - int err; + struct fuse *f; + struct fuse_entry_param e = {0}; - err = get_path2(f,ino,NULL,newparent,newname, + f = req_fuse_prepare(req); + + rv = get_path2(f,ino,NULL,newparent,newname, &oldpath,&newpath,NULL,NULL); - if(!err) + if(!rv) { - err = fuse_fs_link(f->fs,oldpath,newpath); - if(!err) - err = lookup_path(f,newparent,newname,newpath, - &e,NULL); + rv = fuse_fs_link(f->fs,oldpath,newpath,&e.attr,&e.timeout); + if(rv == 0) + rv = set_path_info(f,newparent,newname,&e); free_path2(f,ino,newparent,NULL,NULL,oldpath,newpath); } - reply_entry(req,&e,err); + reply_entry(req,&e,rv); } static diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 993eb7e3..d46ce056 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -223,6 +223,18 @@ over NFS where there are issues with creating files for write while setting the mode to read\-only. (default: off) .IP \[bu] 2 +\f[B]follow\-symlinks=never|directory|regular|all\f[]: Turns symlinks +into what they point to. +(default: never) +.IP \[bu] 2 +\f[B]link\-exdev=passthrough|rel\-symlink|abs\-base\-symlink|abs\-pool\-symlink\f[]: +When a link fails with EXDEV optionally create a symlink to the file +instead. +.IP \[bu] 2 +\f[B]rename\-exdev=passthrough|rel\-symlink|abs\-symlink\f[]: When a +rename fails with EXDEV optionally move the file to a special directory +and symlink to it. +.IP \[bu] 2 \f[B]posix_acl=BOOL\f[]: Enable POSIX ACL support (if supported by kernel and underlying filesystem). (default: false) @@ -542,6 +554,75 @@ On kernels before 4.20 the value has no effect. The reason the value is configurable is to enable experimentation and benchmarking. See the BENCHMARKING section for examples. +.SS follow\-symlinks +.PP +This feature, when enabled, will cause symlinks to be interpreted by +mergerfs as their target (depending on the mode). +.PP +When there is a getattr/stat request for a file mergerfs will check if +the file is a symlink and depending on the \f[C]follow\-symlinks\f[] +setting will replace the information about the symlink with that of that +which it points to. +.PP +When unlink\[aq]ing or rmdir\[aq]ing the followed symlink it will remove +the symlink itself and not that which it points to. +.IP \[bu] 2 +never: Behave as normal. +Symlinks are treated as such. +.IP \[bu] 2 +directory: Resolve symlinks only which point to directories. +.IP \[bu] 2 +regular: Resolve symlinks only which point to regular files. +.IP \[bu] 2 +all: Resolve all symlinks to that which they point to. +.PP +Symlinks which do not point to anything are left as is. +.PP +WARNING: This feature works but there might be edge cases yet found. +If you find any odd behaviors please file a ticket on +github (https://github.com/trapexit/mergerfs/issues). +.SS link\-exdev +.PP +If using path preservation and a \f[C]link\f[] fails with EXDEV make a +call to \f[C]symlink\f[] where the \f[C]target\f[] is the +\f[C]oldlink\f[] and the \f[C]linkpath\f[] is the \f[C]newpath\f[]. +The \f[C]target\f[] value is determined by the value of +\f[C]link\-exdev\f[]. +.IP \[bu] 2 +passthrough: Return EXDEV as normal. +.IP \[bu] 2 +rel\-symlink: A relative path from the \f[C]newpath\f[]. +.IP \[bu] 2 +abs\-base\-symlink: A absolute value using the underlying branch. +.IP \[bu] 2 +abs\-pool\-symlink: A absolute value using the mergerfs mount point. +.PP +NOTE: It is possible that some applications check the file they link. +In those cases it is possible it will error or complain. +.SS rename\-exdev +.PP +If using path preservation and a \f[C]rename\f[] fails with EXDEV: +.IP "1." 3 +Move file from \f[B]/branch/a/b/c\f[] to +\f[B]/branch/.mergerfs_rename_exdev/a/b/c\f[]. +.IP "2." 3 +symlink the rename\[aq]s \f[C]newpath\f[] to the moved file. +.PP +The \f[C]target\f[] value is determined by the value of +\f[C]rename\-exdev\f[]. +.IP \[bu] 2 +passthrough: Return EXDEV as normal. +.IP \[bu] 2 +rel\-symlink: A relative path from the \f[C]newpath\f[]. +.IP \[bu] 2 +abs\-symlink: A absolute value using the mergerfs mount point. +.PP +NOTE: It is possible that some applications check the file they rename. +In those cases it is possible it will error or complain. +.PP +NOTE: The reason \f[C]abs\-symlink\f[] is not split into two like +\f[C]link\-exdev\f[] is due to the complexities in managing absolute +base symlinks when multiple \f[C]oldpaths\f[] exist. .SS symlinkify .PP Due to the levels of indirection introduced by mergerfs and the diff --git a/src/config.cpp b/src/config.cpp index aadb2ae6..988ec325 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -71,6 +71,7 @@ namespace l Config::Config() : async_read(true), auto_cache(false), + minfreespace(MINFREESPACE_DEFAULT), branches(minfreespace), cache_attr(1), cache_entry(1), @@ -83,12 +84,13 @@ Config::Config() direct_io(false), dropcacheonclose(false), fsname(), + follow_symlinks(FollowSymlinks::ENUM::NEVER), func(), fuse_msg_size(FUSE_MAX_MAX_PAGES), ignorepponrename(false), inodecalc("hybrid-hash"), link_cow(false), - minfreespace(MINFREESPACE_DEFAULT), + link_exdev(LinkEXDEV::ENUM::PASSTHROUGH), mount(), moveonenospc(false), nfsopenhack(NFSOpenHack::ENUM::OFF), @@ -97,6 +99,7 @@ Config::Config() posix_acl(false), readdir(ReadDir::ENUM::POSIX), readdirplus(false), + rename_exdev(RenameEXDEV::ENUM::PASSTHROUGH), security_capability(true), srcmounts(branches), statfs(StatFS::ENUM::BASE), @@ -124,6 +127,7 @@ Config::Config() _map["category.search"] = &category.search; _map["direct_io"] = &direct_io; _map["dropcacheonclose"] = &dropcacheonclose; + _map["follow-symlinks"] = &follow_symlinks; _map["fsname"] = &fsname; _map["func.access"] = &func.access; _map["func.chmod"] = &func.chmod; @@ -150,6 +154,7 @@ Config::Config() _map["inodecalc"] = &inodecalc; _map["kernel_cache"] = &kernel_cache; _map["link_cow"] = &link_cow; + _map["link-exdev"] = &link_exdev; _map["minfreespace"] = &minfreespace; _map["mount"] = &mount; _map["moveonenospc"] = &moveonenospc; @@ -159,6 +164,7 @@ Config::Config() _map["posix_acl"] = &posix_acl; // _map["readdir"] = &readdir; _map["readdirplus"] = &readdirplus; + _map["rename-exdev"] = &rename_exdev; _map["security_capability"] = &security_capability; _map["srcmounts"] = &srcmounts; _map["statfs"] = &statfs; diff --git a/src/config.hpp b/src/config.hpp index 6bd1d02b..f42df893 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -19,10 +19,13 @@ #include "branches.hpp" #include "category.hpp" #include "config_cachefiles.hpp" +#include "config_follow_symlinks.hpp" #include "config_inodecalc.hpp" +#include "config_link_exdev.hpp" #include "config_moveonenospc.hpp" #include "config_nfsopenhack.hpp" #include "config_readdir.hpp" +#include "config_rename_exdev.hpp" #include "config_statfs.hpp" #include "config_statfsignore.hpp" #include "config_xattr.hpp" @@ -38,7 +41,6 @@ #include #include #include -#include #include #include @@ -98,6 +100,7 @@ public: public: ConfigBOOL async_read; ConfigBOOL auto_cache; + ConfigUINT64 minfreespace; Branches branches; ConfigUINT64 cache_attr; ConfigUINT64 cache_entry; @@ -110,13 +113,14 @@ public: ConfigBOOL direct_io; ConfigBOOL dropcacheonclose; ConfigSTR fsname; + FollowSymlinks follow_symlinks; Funcs func; ConfigUINT64 fuse_msg_size; ConfigBOOL ignorepponrename; InodeCalc inodecalc; ConfigBOOL kernel_cache; ConfigBOOL link_cow; - ConfigUINT64 minfreespace; + LinkEXDEV link_exdev; ConfigSTR mount; MoveOnENOSPC moveonenospc; NFSOpenHack nfsopenhack; @@ -125,6 +129,7 @@ public: ConfigBOOL posix_acl; ReadDir readdir; ConfigBOOL readdirplus; + RenameEXDEV rename_exdev; ConfigBOOL security_capability; SrcMounts srcmounts; StatFS statfs; diff --git a/src/config_follow_symlinks.cpp b/src/config_follow_symlinks.cpp new file mode 100644 index 00000000..a1a255e7 --- /dev/null +++ b/src/config_follow_symlinks.cpp @@ -0,0 +1,58 @@ +/* + ISC License + + Copyright (c) 2020, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "config_follow_symlinks.hpp" +#include "ef.hpp" +#include "errno.hpp" + +template<> +std::string +FollowSymlinks::to_string(void) const +{ + switch(_data) + { + case FollowSymlinks::ENUM::NEVER: + return "never"; + case FollowSymlinks::ENUM::DIRECTORY: + return "directory"; + case FollowSymlinks::ENUM::REGULAR: + return "regular"; + case FollowSymlinks::ENUM::ALL: + return "all"; + } + + return "invalid"; +} + +template<> +int +FollowSymlinks::from_string(const std::string &s_) +{ + if(s_ == "never") + _data = FollowSymlinks::ENUM::NEVER; + ef(s_ == "directory") + _data = FollowSymlinks::ENUM::DIRECTORY; + ef(s_ == "regular") + _data = FollowSymlinks::ENUM::REGULAR; + ef(s_ == "all") + _data = FollowSymlinks::ENUM::ALL; + else + return -EINVAL; + + return 0; +} diff --git a/src/config_follow_symlinks.hpp b/src/config_follow_symlinks.hpp new file mode 100644 index 00000000..1ebbdecf --- /dev/null +++ b/src/config_follow_symlinks.hpp @@ -0,0 +1,30 @@ +/* + ISC License + + Copyright (c) 2020, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "enum.hpp" + +enum class FollowSymlinksEnum + { + NEVER, + DIRECTORY, + REGULAR, + ALL + }; +typedef Enum FollowSymlinks; diff --git a/src/config_link_exdev.cpp b/src/config_link_exdev.cpp new file mode 100644 index 00000000..ccd34f3a --- /dev/null +++ b/src/config_link_exdev.cpp @@ -0,0 +1,58 @@ +/* + ISC License + + Copyright (c) 2021, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "config_link_exdev.hpp" +#include "ef.hpp" +#include "errno.hpp" + +template<> +std::string +LinkEXDEV::to_string(void) const +{ + switch(_data) + { + case LinkEXDEV::ENUM::PASSTHROUGH: + return "passthrough"; + case LinkEXDEV::ENUM::REL_SYMLINK: + return "rel-symlink"; + case LinkEXDEV::ENUM::ABS_BASE_SYMLINK: + return "abs-base-symlink"; + case LinkEXDEV::ENUM::ABS_POOL_SYMLINK: + return "abs-pool-symlink"; + } + + return "invalid"; +} + +template<> +int +LinkEXDEV::from_string(const std::string &s_) +{ + if(s_ == "passthrough") + _data = LinkEXDEV::ENUM::PASSTHROUGH; + ef(s_ == "rel-symlink") + _data = LinkEXDEV::ENUM::REL_SYMLINK; + ef(s_ == "abs-base-symlink") + _data = LinkEXDEV::ENUM::ABS_BASE_SYMLINK; + ef(s_ == "abs-pool-symlink") + _data = LinkEXDEV::ENUM::ABS_POOL_SYMLINK; + else + return -EINVAL; + + return 0; +} diff --git a/src/config_link_exdev.hpp b/src/config_link_exdev.hpp new file mode 100644 index 00000000..45f5dfe7 --- /dev/null +++ b/src/config_link_exdev.hpp @@ -0,0 +1,30 @@ +/* + ISC License + + Copyright (c) 2021, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "enum.hpp" + +enum class LinkEXDEVEnum + { + PASSTHROUGH, + REL_SYMLINK, + ABS_BASE_SYMLINK, + ABS_POOL_SYMLINK + }; +typedef Enum LinkEXDEV; diff --git a/src/config_rename_exdev.cpp b/src/config_rename_exdev.cpp new file mode 100644 index 00000000..b2fd46d9 --- /dev/null +++ b/src/config_rename_exdev.cpp @@ -0,0 +1,54 @@ +/* + ISC License + + Copyright (c) 2021, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "config_rename_exdev.hpp" +#include "ef.hpp" +#include "errno.hpp" + +template<> +std::string +RenameEXDEV::to_string(void) const +{ + switch(_data) + { + case RenameEXDEV::ENUM::PASSTHROUGH: + return "passthrough"; + case RenameEXDEV::ENUM::REL_SYMLINK: + return "rel-symlink"; + case RenameEXDEV::ENUM::ABS_SYMLINK: + return "abs-symlink"; + } + + return "invalid"; +} + +template<> +int +RenameEXDEV::from_string(const std::string &s_) +{ + if(s_ == "passthrough") + _data = RenameEXDEV::ENUM::PASSTHROUGH; + ef(s_ == "rel-symlink") + _data = RenameEXDEV::ENUM::REL_SYMLINK; + ef(s_ == "abs-symlink") + _data = RenameEXDEV::ENUM::ABS_SYMLINK; + else + return -EINVAL; + + return 0; +} diff --git a/src/config_rename_exdev.hpp b/src/config_rename_exdev.hpp new file mode 100644 index 00000000..4be91114 --- /dev/null +++ b/src/config_rename_exdev.hpp @@ -0,0 +1,29 @@ +/* + ISC License + + Copyright (c) 2021, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#pragma once + +#include "enum.hpp" + +enum class RenameEXDEVEnum + { + PASSTHROUGH, + REL_SYMLINK, + ABS_SYMLINK + }; +typedef Enum RenameEXDEV; diff --git a/src/enum.hpp b/src/enum.hpp index 848ffca8..b4792bb9 100644 --- a/src/enum.hpp +++ b/src/enum.hpp @@ -67,8 +67,8 @@ public: } public: - std::string to_string() const; - int from_string(const std::string &); + std::string to_string() const final; + int from_string(const std::string &) final; public: int to_int() const diff --git a/src/fs_mkdir.hpp b/src/fs_mkdir.hpp index 8d55c595..d1fff438 100644 --- a/src/fs_mkdir.hpp +++ b/src/fs_mkdir.hpp @@ -18,6 +18,8 @@ #pragma once +#include "ghc/filesystem.hpp" + #include #include @@ -26,12 +28,30 @@ namespace fs { + static + inline + int + mkdir(const char *path_, + const mode_t mode_) + { + return ::mkdir(path_,mode_); + } + static inline int mkdir(const std::string &path_, const mode_t mode_) { - return ::mkdir(path_.c_str(),mode_); + return fs::mkdir(path_.c_str(),mode_); + } + + static + inline + int + mkdir(const ghc::filesystem::path &path_, + const mode_t mode_) + { + return fs::mkdir(path_.c_str(),mode_); } } diff --git a/src/fs_mkdir_as_root.hpp b/src/fs_mkdir_as_root.hpp new file mode 100644 index 00000000..3299ffa3 --- /dev/null +++ b/src/fs_mkdir_as_root.hpp @@ -0,0 +1,35 @@ +/* + ISC License + + Copyright (c) 2021, Antonio SJ Musumeci + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "fs_mkdir.hpp" +#include "ugid.hpp" + +namespace fs +{ + template + static + inline + int + mkdir_as_root(const T &path_, + const mode_t mode_) + { + const ugid::SetRootGuard guard; + + return fs::mkdir(path_,mode_); + } +} diff --git a/src/fs_symlink.hpp b/src/fs_symlink.hpp index 1db1823e..b86e77b2 100644 --- a/src/fs_symlink.hpp +++ b/src/fs_symlink.hpp @@ -28,30 +28,27 @@ namespace fs static inline int - symlink(const char *oldpath_, - const char *newpath_) + symlink(const char *target_, + const char *linkpath_) { - return ::symlink(oldpath_, - newpath_); + return ::symlink(target_,linkpath_); } static inline int - symlink(const std::string &oldpath_, - const std::string &newpath_) + symlink(const std::string &target_, + const std::string &linkpath_) { - return fs::symlink(oldpath_.c_str(), - newpath_.c_str()); + return ::symlink(target_.c_str(),linkpath_.c_str()); } static inline int - symlink(const char *oldpath_, - const std::string &newpath_) + symlink(const char *target_, + const std::string &linkpath_) { - return fs::symlink(oldpath_, - newpath_.c_str()); + return ::symlink(target_,linkpath_.c_str()); } } diff --git a/src/fuse_getattr.cpp b/src/fuse_getattr.cpp index 38ca49ae..e7431be3 100644 --- a/src/fuse_getattr.cpp +++ b/src/fuse_getattr.cpp @@ -19,6 +19,7 @@ #include "fs_inode.hpp" #include "fs_lstat.hpp" #include "fs_path.hpp" +#include "fs_stat.hpp" #include "symlinkify.hpp" #include "ugid.hpp" @@ -31,6 +32,42 @@ using std::string; namespace l { + static + void + set_stat_if_leads_to_dir(const std::string &path_, + struct stat *st_) + { + int rv; + struct stat st; + + rv = fs::stat(path_,&st); + if(rv == -1) + return; + + if(S_ISDIR(st.st_mode)) + *st_ = st; + + return; + } + + static + void + set_stat_if_leads_to_reg(const std::string &path_, + struct stat *st_) + { + int rv; + struct stat st; + + rv = fs::stat(path_,&st); + if(rv == -1) + return; + + if(S_ISREG(st.st_mode)) + *st_ = st; + + return; + } + static int getattr_controlfile(struct stat *st_) @@ -63,7 +100,8 @@ namespace l const char *fusepath_, struct stat *st_, const bool symlinkify_, - const time_t symlinkify_timeout_) + const time_t symlinkify_timeout_, + FollowSymlinks followsymlinks_) { int rv; string fullpath; @@ -75,7 +113,28 @@ namespace l fullpath = fs::path::make(basepaths[0],fusepath_); - rv = fs::lstat(fullpath,st_); + switch(followsymlinks_) + { + case FollowSymlinks::ENUM::NEVER: + rv = fs::lstat(fullpath,st_); + break; + case FollowSymlinks::ENUM::DIRECTORY: + rv = fs::lstat(fullpath,st_); + if(S_ISLNK(st_->st_mode)) + l::set_stat_if_leads_to_dir(fullpath,st_); + break; + case FollowSymlinks::ENUM::REGULAR: + rv = fs::lstat(fullpath,st_); + if(S_ISLNK(st_->st_mode)) + l::set_stat_if_leads_to_reg(fullpath,st_); + break; + case FollowSymlinks::ENUM::ALL: + rv = fs::stat(fullpath,st_); + if(rv != 0) + rv = fs::lstat(fullpath,st_); + break; + } + if(rv == -1) return -errno; @@ -102,7 +161,8 @@ namespace l fusepath_, st_, cfg->symlinkify, - cfg->symlinkify_timeout); + cfg->symlinkify_timeout, + cfg->follow_symlinks); timeout_->entry = ((rv >= 0) ? cfg->cache_entry : diff --git a/src/fuse_link.cpp b/src/fuse_link.cpp index 4b06adcf..8ba157aa 100644 --- a/src/fuse_link.cpp +++ b/src/fuse_link.cpp @@ -18,7 +18,11 @@ #include "errno.hpp" #include "fs_clonepath.hpp" #include "fs_link.hpp" +#include "fs_lstat.hpp" #include "fs_path.hpp" +#include "fuse_getattr.hpp" +#include "fuse_symlink.hpp" +#include "ghc/filesystem.hpp" #include "ugid.hpp" #include "fuse.h" @@ -28,7 +32,7 @@ using std::string; using std::vector; - +namespace gfs = ghc::filesystem; namespace error { @@ -52,47 +56,38 @@ namespace error namespace l { - static - int - link_create_path_core(const string &oldbasepath_, - const string &newbasepath_, - const char *oldfusepath_, - const char *newfusepath_, - const int error_) - { - int rv; - string oldfullpath; - string newfullpath; - - oldfullpath = fs::path::make(oldbasepath_,oldfusepath_); - newfullpath = fs::path::make(oldbasepath_,newfusepath_); - - rv = fs::link(oldfullpath,newfullpath); - - return error::calc(rv,error_,errno); - } - static int link_create_path_loop(const StrVec &oldbasepaths_, const string &newbasepath_, const char *oldfusepath_, const char *newfusepath_, - const string &newfusedirpath_) + const string &newfusedirpath_, + struct stat *st_) { int rv; int error; + string oldfullpath; + string newfullpath; error = -1; - for(size_t i = 0, ei = oldbasepaths_.size(); i != ei; i++) + for(auto &oldbasepath : oldbasepaths_) { - rv = fs::clonepath_as_root(newbasepath_,oldbasepaths_[i],newfusedirpath_); - if(rv == -1) - error = error::calc(rv,error,errno); - else - error = l::link_create_path_core(oldbasepaths_[i],newbasepath_, - oldfusepath_,newfusepath_, - error); + oldfullpath = fs::path::make(oldbasepath,oldfusepath_); + newfullpath = fs::path::make(oldbasepath,newfusepath_); + + rv = fs::link(oldfullpath,newfullpath); + if((rv == -1) && (errno == ENOENT)) + { + rv = fs::clonepath_as_root(newbasepath_,oldbasepath,newfusedirpath_); + if(rv == 0) + rv = fs::link(oldfullpath,newfullpath); + } + + if((rv == 0) && (st_->st_ino == 0)) + rv = fs::lstat(oldfullpath,st_); + + error = error::calc(rv,error,errno); } return -error; @@ -104,7 +99,8 @@ namespace l const Policy::Action &actionFunc_, const Branches &branches_, const char *oldfusepath_, - const char *newfusepath_) + const char *newfusepath_, + struct stat *st_) { int rv; string newfusedirpath; @@ -123,47 +119,17 @@ namespace l return l::link_create_path_loop(oldbasepaths,newbasepaths[0], oldfusepath_,newfusepath_, - newfusedirpath); + newfusedirpath, + st_); } static int - clonepath_if_would_create(const Policy::Search &searchFunc_, - const Policy::Create &createFunc_, - const Branches &branches_, - const string &oldbasepath_, - const char *oldfusepath_, - const char *newfusepath_) - { - int rv; - string newfusedirpath; - StrVec newbasepath; - - newfusedirpath = fs::path::dirname(newfusepath_); - - rv = createFunc_(branches_,newfusedirpath,&newbasepath); - if(rv == -1) - return -1; - - if(oldbasepath_ != newbasepath[0]) - return (errno=EXDEV,-1); - - rv = searchFunc_(branches_,newfusedirpath,&newbasepath); - if(rv == -1) - return -1; - - return fs::clonepath_as_root(newbasepath[0],oldbasepath_,newfusedirpath); - } - - static - int - link_preserve_path_core(const Policy::Search &searchFunc_, - const Policy::Create &createFunc_, - const Branches &branches_, - const string &oldbasepath_, - const char *oldfusepath_, - const char *newfusepath_, - const int error_) + link_preserve_path_core(const string &oldbasepath_, + const char *oldfusepath_, + const char *newfusepath_, + struct stat *st_, + const int error_) { int rv; string oldfullpath; @@ -174,36 +140,29 @@ namespace l rv = fs::link(oldfullpath,newfullpath); if((rv == -1) && (errno == ENOENT)) - { - rv = l::clonepath_if_would_create(searchFunc_,createFunc_, - branches_, - oldbasepath_, - oldfusepath_,newfusepath_); - if(rv != -1) - rv = fs::link(oldfullpath,newfullpath); - } + errno = EXDEV; + if((rv == 0) && (st_->st_ino == 0)) + rv = fs::lstat(oldfullpath,st_); return error::calc(rv,error_,errno); } static int - link_preserve_path_loop(const Policy::Search &searchFunc_, - const Policy::Create &createFunc_, - const Branches &branches_, - const char *oldfusepath_, - const char *newfusepath_, - const StrVec &oldbasepaths_) + link_preserve_path_loop(const StrVec &oldbasepaths_, + const char *oldfusepath_, + const char *newfusepath_, + struct stat *st_) { int error; error = -1; - for(size_t i = 0, ei = oldbasepaths_.size(); i != ei; i++) + for(auto &oldbasepath : oldbasepaths_) { - error = l::link_preserve_path_core(searchFunc_,createFunc_, - branches_, - oldbasepaths_[i], - oldfusepath_,newfusepath_, + error = l::link_preserve_path_core(oldbasepath, + oldfusepath_, + newfusepath_, + st_, error); } @@ -212,12 +171,11 @@ namespace l static int - link_preserve_path(const Policy::Search &searchFunc_, - const Policy::Action &actionFunc_, - const Policy::Create &createFunc_, + link_preserve_path(const Policy::Action &actionFunc_, const Branches &branches_, const char *oldfusepath_, - const char *newfusepath_) + const char *newfusepath_, + struct stat *st_) { int rv; StrVec oldbasepaths; @@ -226,35 +184,183 @@ namespace l if(rv == -1) return -errno; - return l::link_preserve_path_loop(searchFunc_,createFunc_, - branches_, - oldfusepath_,newfusepath_, - oldbasepaths); + return l::link_preserve_path_loop(oldbasepaths, + oldfusepath_, + newfusepath_, + st_); + } + + static + int + link(Config::Read &cfg_, + const char *oldpath_, + const char *newpath_, + struct stat *st_) + { + if(cfg_->func.create.policy.path_preserving() && !cfg_->ignorepponrename) + return l::link_preserve_path(cfg_->func.link.policy, + cfg_->branches, + oldpath_, + newpath_, + st_); + + return l::link_create_path(cfg_->func.getattr.policy, + cfg_->func.link.policy, + cfg_->branches, + oldpath_, + newpath_, + st_); + } + + static + int + link(Config::Read &cfg_, + const char *oldpath_, + const char *newpath_, + struct stat *st_, + fuse_timeouts_t *timeouts_) + { + int rv; + + rv = l::link(cfg_,oldpath_,newpath_,st_); + + timeouts_->entry = ((rv >= 0) ? + cfg_->cache_entry : + cfg_->cache_negative_entry); + timeouts_->attr = cfg_->cache_attr; + + return rv; + } + + static + int + link_exdev_rel_symlink(const char *oldpath_, + const char *newpath_, + struct stat *st_, + fuse_timeouts_t *timeouts_) + { + int rv; + gfs::path target(oldpath_); + gfs::path linkpath(newpath_); + + target = target.lexically_relative(linkpath.parent_path()); + + rv = FUSE::symlink(target.c_str(),linkpath.c_str()); + if(rv == 0) + rv = FUSE::getattr(oldpath_,st_,timeouts_); + + // Disable attr caching since we created a symlink but should be a regular. + timeouts_->attr = 0; + + return rv; + } + + static + int + link_exdev_abs_base_symlink(const Policy::Search &openPolicy_, + const Branches::CPtr &branches_, + const char *oldpath_, + const char *newpath_, + struct stat *st_, + fuse_timeouts_t *timeouts_) + { + int rv; + StrVec basepaths; + std::string target; + + rv = openPolicy_(branches_,oldpath_,&basepaths); + if(rv == -1) + return -errno; + + target = fs::path::make(basepaths[0],oldpath_); + + rv = FUSE::symlink(target.c_str(),newpath_); + if(rv == 0) + rv = FUSE::getattr(oldpath_,st_,timeouts_); + + // Disable attr caching since we created a symlink but should be a regular. + timeouts_->attr = 0; + + return rv; + } + + static + int + link_exdev_abs_pool_symlink(const std::string &mount_, + const char *oldpath_, + const char *newpath_, + struct stat *st_, + fuse_timeouts_t *timeouts_) + { + int rv; + StrVec basepaths; + std::string target; + + target = fs::path::make(mount_,oldpath_); + + rv = FUSE::symlink(target.c_str(),newpath_); + if(rv == 0) + rv = FUSE::getattr(oldpath_,st_,timeouts_); + + // Disable attr caching since we created a symlink but should be a regular. + timeouts_->attr = 0; + + return rv; + } + + static + int + link_exdev(Config::Read &cfg_, + const char *oldpath_, + const char *newpath_, + struct stat *st_, + fuse_timeouts_t *timeouts_) + { + switch(cfg_->link_exdev) + { + case LinkEXDEV::ENUM::PASSTHROUGH: + return -EXDEV; + case LinkEXDEV::ENUM::REL_SYMLINK: + return l::link_exdev_rel_symlink(oldpath_, + newpath_, + st_, + timeouts_); + case LinkEXDEV::ENUM::ABS_BASE_SYMLINK: + return l::link_exdev_abs_base_symlink(cfg_->func.open.policy, + cfg_->branches, + oldpath_, + newpath_, + st_, + timeouts_); + case LinkEXDEV::ENUM::ABS_POOL_SYMLINK: + return l::link_exdev_abs_pool_symlink(cfg_->mount, + oldpath_, + newpath_, + st_, + timeouts_); + } + + return -EXDEV; } } namespace FUSE { int - link(const char *from_, - const char *to_) + link(const char *oldpath_, + const char *newpath_, + struct stat *st_, + fuse_timeouts_t *timeouts_) { + int rv; Config::Read cfg; const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); - if(cfg->func.create.policy.path_preserving() && !cfg->ignorepponrename) - return l::link_preserve_path(cfg->func.getattr.policy, - cfg->func.link.policy, - cfg->func.create.policy, - cfg->branches, - from_, - to_); + rv = l::link(cfg,oldpath_,newpath_,st_,timeouts_); + if(rv == -EXDEV) + return l::link_exdev(cfg,oldpath_,newpath_,st_,timeouts_); - return l::link_create_path(cfg->func.getattr.policy, - cfg->func.link.policy, - cfg->branches, - from_, - to_); + return rv; } } diff --git a/src/fuse_link.hpp b/src/fuse_link.hpp index ab601f88..2711b931 100644 --- a/src/fuse_link.hpp +++ b/src/fuse_link.hpp @@ -16,10 +16,14 @@ #pragma once +#include "fuse.h" + namespace FUSE { int - link(const char *from, - const char *to); + link(const char *oldpath, + const char *newpath, + struct stat *st, + fuse_timeouts_t *timeouts); } diff --git a/src/fuse_rename.cpp b/src/fuse_rename.cpp index 541bce8e..ee2384e8 100644 --- a/src/fuse_rename.cpp +++ b/src/fuse_rename.cpp @@ -17,16 +17,26 @@ #include "config.hpp" #include "errno.hpp" #include "fs_clonepath.hpp" +#include "fs_link.hpp" +#include "fs_mkdir_as_root.hpp" #include "fs_path.hpp" #include "fs_remove.hpp" #include "fs_rename.hpp" +#include "fs_symlink.hpp" +#include "fs_unlink.hpp" +#include "fuse_symlink.hpp" #include "ugid.hpp" +#include "ghc/filesystem.hpp" + #include #include +#include + +#include using std::string; - +namespace gfs = ghc::filesystem; namespace error { @@ -48,265 +58,351 @@ namespace error } } -static -bool -member(const StrVec &haystack, - const string &needle) +namespace l { - for(size_t i = 0, ei = haystack.size(); i != ei; i++) - { - if(haystack[i] == needle) - return true; - } + static + bool + contains(const StrVec &haystack_, + const char *needle_) + { + for(auto &hay : haystack_) + { + if(hay == needle_) + return true; + } - return false; -} + return false; + } -static -void -_remove(const StrVec &toremove) -{ - for(size_t i = 0, ei = toremove.size(); i != ei; i++) - fs::remove(toremove[i]); -} + static + bool + contains(const StrVec &haystack_, + const string &needle_) + { + return l::contains(haystack_,needle_.c_str()); + } -static -void -_rename_create_path_core(const StrVec &oldbasepaths, - const string &oldbasepath, - const string &newbasepath, - const char *oldfusepath, - const char *newfusepath, - const string &newfusedirpath, - int &error, - StrVec &tounlink) -{ - int rv; - bool ismember; - string oldfullpath; - string newfullpath; + static + void + remove(const StrVec &toremove_) + { + for(auto &path : toremove_) + fs::remove(path); + } - ismember = member(oldbasepaths,oldbasepath); - if(ismember) - { - rv = fs::clonepath_as_root(newbasepath,oldbasepath,newfusedirpath); - if(rv != -1) - { - oldfullpath = fs::path::make(oldbasepath,oldfusepath); - newfullpath = fs::path::make(oldbasepath,newfusepath); + static + void + remove(const Branches::CPtr &branches_, + const std::string &relpath_) + { + std::string fullpath; - rv = fs::rename(oldfullpath,newfullpath); - } + for(auto &branch : *branches_) + { + fullpath = fs::path::make(branch.path,relpath_); + fs::remove(fullpath); + } + } - error = error::calc(rv,error,errno); - if(rv == -1) - tounlink.push_back(oldfullpath); - } - else - { - newfullpath = fs::path::make(oldbasepath,newfusepath); + static + int + rename_create_path(const Policy::Search &searchPolicy_, + const Policy::Action &actionPolicy_, + const Branches::CPtr &branches_, + const gfs::path &oldfusepath_, + const gfs::path &newfusepath_) + { + int rv; + int error; + StrVec toremove; + StrVec newbasepath; + StrVec oldbasepaths; + gfs::path oldfullpath; + gfs::path newfullpath; - tounlink.push_back(newfullpath); - } -} + rv = actionPolicy_(branches_,oldfusepath_,&oldbasepaths); + if(rv == -1) + return -errno; -static -int -_rename_create_path(const Policy::Search &searchFunc, - const Policy::Action &actionFunc, - const Branches::CPtr &branches_, - const char *oldfusepath, - const char *newfusepath) -{ - int rv; - int error; - string newfusedirpath; - StrVec toremove; - StrVec newbasepath; - StrVec oldbasepaths; - StrVec branches; + rv = searchPolicy_(branches_,newfusepath_.parent_path(),&newbasepath); + if(rv == -1) + return -errno; - rv = actionFunc(branches_,oldfusepath,&oldbasepaths); - if(rv == -1) - return -errno; + error = -1; + for(auto &branch : *branches_) + { + newfullpath = branch.path; + newfullpath += newfusepath_; - newfusedirpath = fs::path::dirname(newfusepath); + if(!l::contains(oldbasepaths,branch.path)) + { + toremove.push_back(newfullpath); + continue; + } - rv = searchFunc(branches_,newfusedirpath.c_str(),&newbasepath); - if(rv == -1) - return -errno; + oldfullpath = branch.path; + oldfullpath += oldfusepath_; - branches_->to_paths(branches); + rv = fs::rename(oldfullpath,newfullpath); + if(rv == -1) + { + rv = fs::clonepath_as_root(newbasepath[0],branch.path,newfusepath_.parent_path()); + if(rv == 0) + rv = fs::rename(oldfullpath,newfullpath); + } - error = -1; - for(size_t i = 0, ei = branches.size(); i != ei; i++) - { - const string &oldbasepath = branches[i]; + error = error::calc(rv,error,errno); + if(rv == -1) + toremove.push_back(oldfullpath); + } - _rename_create_path_core(oldbasepaths, - oldbasepath,newbasepath[0], - oldfusepath,newfusepath, - newfusedirpath, - error,toremove); - } + if(error == 0) + l::remove(toremove); + return -error; + } - if(error == 0) - _remove(toremove); + static + int + rename_preserve_path(const Policy::Action &actionPolicy_, + const Branches::CPtr &branches_, + const gfs::path &oldfusepath_, + const gfs::path &newfusepath_) + { + int rv; + bool success; + StrVec toremove; + StrVec oldbasepaths; + gfs::path oldfullpath; + gfs::path newfullpath; - return -error; -} + rv = actionPolicy_(branches_,oldfusepath_,&oldbasepaths); + if(rv == -1) + return -errno; -static -int -_clonepath(const Policy::Search &searchFunc, - const Branches::CPtr &branches_, - const string &dstbasepath, - const string &fusedirpath) -{ - int rv; - StrVec srcbasepath; + success = false; + for(auto &branch : *branches_) + { + newfullpath = branch.path; + newfullpath += newfusepath_; - rv = searchFunc(branches_,fusedirpath.c_str(),&srcbasepath); - if(rv == -1) - return -errno; + if(!l::contains(oldbasepaths,branch.path)) + { + toremove.push_back(newfullpath); + continue; + } - fs::clonepath_as_root(srcbasepath[0],dstbasepath,fusedirpath); + oldfullpath = branch.path; + oldfullpath += oldfusepath_; - return 0; -} + rv = fs::rename(oldfullpath,newfullpath); + if(rv == -1) + { + toremove.push_back(oldfullpath); + continue; + } -static -int -_clonepath_if_would_create(const Policy::Search &searchFunc, - const Policy::Create &createFunc, + success = true; + } + + // TODO: probably should try to be nuanced here. + if(success == false) + return -EXDEV; + + l::remove(toremove); + + return 0; + } + + static + void + rename_exdev_rename_back(const StrVec &basepaths_, + const gfs::path &oldfusepath_) + { + gfs::path oldpath; + gfs::path newpath; + + for(auto &basepath : basepaths_) + { + oldpath = basepath; + oldpath /= ".mergerfs_rename_exdev"; + oldpath += oldfusepath_; + + newpath = basepath; + newpath += oldfusepath_; + + fs::rename(oldpath,newpath); + } + } + + static + int + rename_exdev_rename_target(const Policy::Action &actionPolicy_, + const Branches::CPtr &branches_, + const gfs::path &oldfusepath_, + StrVec *basepaths_) + { + int rv; + gfs::path clonesrc; + gfs::path clonetgt; + + rv = actionPolicy_(branches_,oldfusepath_,basepaths_); + if(rv == -1) + return -errno; + + ugid::SetRootGuard ugidGuard; + for(auto &basepath : *basepaths_) + { + clonesrc = basepath; + clonetgt = basepath; + clonetgt /= ".mergerfs_rename_exdev"; + + rv = fs::clonepath(clonesrc,clonetgt,oldfusepath_.parent_path()); + if((rv == -1) && (errno == ENOENT)) + { + fs::mkdir(clonetgt,01777); + rv = fs::clonepath(clonesrc,clonetgt,oldfusepath_.parent_path()); + } + + if(rv == -1) + goto error; + + clonesrc += oldfusepath_; + clonetgt += oldfusepath_; + + rv = fs::rename(clonesrc,clonetgt); + if(rv == -1) + goto error; + } + + return 0; + + error: + l::rename_exdev_rename_back(*basepaths_,oldfusepath_); + + return -EXDEV; + } + + static + int + rename_exdev_rel_symlink(const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, - const string &oldbasepath, - const char *oldfusepath, - const char *newfusepath) -{ - int rv; - string newfusedirpath; - StrVec newbasepath; + const gfs::path &oldfusepath_, + const gfs::path &newfusepath_) + { + int rv; + StrVec basepaths; + gfs::path target; + gfs::path linkpath; - newfusedirpath = fs::path::dirname(newfusepath); + rv = l::rename_exdev_rename_target(actionPolicy_,branches_,oldfusepath_,&basepaths); + if(rv < 0) + return rv; + + linkpath = newfusepath_; + target = "/.mergerfs_rename_exdev"; + target += oldfusepath_; + target = target.lexically_relative(linkpath.parent_path()); + + rv = FUSE::symlink(target.c_str(),linkpath.c_str()); + if(rv < 0) + l::rename_exdev_rename_back(basepaths,oldfusepath_); - rv = createFunc(branches_,newfusedirpath.c_str(),&newbasepath); - if(rv == -1) return rv; + } - if(oldbasepath == newbasepath[0]) - return _clonepath(searchFunc,branches_,oldbasepath,newfusedirpath); - - return (errno=EXDEV,-1); -} - -static -void -_rename_preserve_path_core(const Policy::Search &searchFunc, - const Policy::Create &createFunc, + static + int + rename_exdev_abs_symlink(const Policy::Action &actionPolicy_, const Branches::CPtr &branches_, - const StrVec &oldbasepaths, - const string &oldbasepath, - const char *oldfusepath, - const char *newfusepath, - int &error, - StrVec &toremove) -{ - int rv; - bool ismember; - string newfullpath; + const std::string &mount_, + const gfs::path &oldfusepath_, + const gfs::path &newfusepath_) + { + int rv; + StrVec basepaths; + gfs::path target; + gfs::path linkpath; - newfullpath = fs::path::make(oldbasepath,newfusepath); + rv = l::rename_exdev_rename_target(actionPolicy_,branches_,oldfusepath_,&basepaths); + if(rv < 0) + return rv; - ismember = member(oldbasepaths,oldbasepath); - if(ismember) - { - string oldfullpath; + linkpath = newfusepath_; + target = mount_; + target /= ".mergerfs_rename_exdev"; + target += oldfusepath_; - oldfullpath = fs::path::make(oldbasepath,oldfusepath); + rv = FUSE::symlink(target.c_str(),linkpath.c_str()); + if(rv < 0) + l::rename_exdev_rename_back(basepaths,oldfusepath_); - rv = fs::rename(oldfullpath,newfullpath); - if((rv == -1) && (errno == ENOENT)) - { - rv = _clonepath_if_would_create(searchFunc,createFunc, - branches_,oldbasepath, - oldfusepath,newfusepath); - if(rv == 0) - rv = fs::rename(oldfullpath,newfullpath); - } + return rv; + } - error = error::calc(rv,error,errno); - if(rv == -1) - toremove.push_back(oldfullpath); - } - else - { - toremove.push_back(newfullpath); - } -} + static + int + rename_exdev(Config::Read &cfg_, + const gfs::path &oldfusepath_, + const gfs::path &newfusepath_) + { + switch(cfg_->rename_exdev) + { + case RenameEXDEV::ENUM::PASSTHROUGH: + return -EXDEV; + case RenameEXDEV::ENUM::REL_SYMLINK: + return l::rename_exdev_rel_symlink(cfg_->func.rename.policy, + cfg_->branches, + oldfusepath_, + newfusepath_); + case RenameEXDEV::ENUM::ABS_SYMLINK: + return l::rename_exdev_abs_symlink(cfg_->func.rename.policy, + cfg_->branches, + cfg_->mount, + oldfusepath_, + newfusepath_); + } -static -int -_rename_preserve_path(const Policy::Search &searchFunc, - const Policy::Action &actionFunc, - const Policy::Create &createFunc, - const Branches::CPtr &branches_, - const char *oldfusepath, - const char *newfusepath) -{ - int rv; - int error; - StrVec toremove; - StrVec oldbasepaths; - StrVec branches; + return -EXDEV; + } - rv = actionFunc(branches_,oldfusepath,&oldbasepaths); - if(rv == -1) - return -errno; + static + int + rename(Config::Read &cfg_, + const gfs::path &oldpath_, + const gfs::path &newpath_) + { + if(cfg_->func.create.policy.path_preserving() && !cfg_->ignorepponrename) + return l::rename_preserve_path(cfg_->func.rename.policy, + cfg_->branches, + oldpath_, + newpath_); - branches_->to_paths(branches); - - error = -1; - for(size_t i = 0, ei = branches.size(); i != ei; i++) - { - const string &oldbasepath = branches[i]; - - _rename_preserve_path_core(searchFunc,createFunc, - branches_, - oldbasepaths,oldbasepath, - oldfusepath,newfusepath, - error,toremove); - } - - if(error == 0) - _remove(toremove); - - return -error; + return l::rename_create_path(cfg_->func.getattr.policy, + cfg_->func.rename.policy, + cfg_->branches, + oldpath_, + newpath_); + } } namespace FUSE { int - rename(const char *oldpath, - const char *newpath) + rename(const char *oldfusepath_, + const char *newfusepath_) { + int rv; Config::Read cfg; + gfs::path oldfusepath(oldfusepath_); + gfs::path newfusepath(newfusepath_); const fuse_context *fc = fuse_get_context(); const ugid::Set ugid(fc->uid,fc->gid); - if(cfg->func.create.policy.path_preserving() && !cfg->ignorepponrename) - return _rename_preserve_path(cfg->func.getattr.policy, - cfg->func.rename.policy, - cfg->func.create.policy, - cfg->branches, - oldpath, - newpath); + rv = l::rename(cfg,oldfusepath,newfusepath); + if(rv == -EXDEV) + return l::rename_exdev(cfg,oldfusepath,newfusepath); - return _rename_create_path(cfg->func.getattr.policy, - cfg->func.rename.policy, - cfg->branches, - oldpath, - newpath); + return rv; } } diff --git a/src/fuse_rmdir.cpp b/src/fuse_rmdir.cpp index c7c6e107..cbfc83c6 100644 --- a/src/fuse_rmdir.cpp +++ b/src/fuse_rmdir.cpp @@ -16,8 +16,9 @@ #include "config.hpp" #include "errno.hpp" -#include "fs_rmdir.hpp" #include "fs_path.hpp" +#include "fs_rmdir.hpp" +#include "fs_unlink.hpp" #include "ugid.hpp" #include "fuse.h" @@ -50,9 +51,21 @@ namespace l { static int - rmdir_loop_core(const string &basepath_, - const char *fusepath_, - const int error_) + should_unlink(int rv_, + int errno_, + FollowSymlinks followsymlinks_) + { + return ((rv_ == -1) && + (errno_ == ENOTDIR) && + (followsymlinks_ != FollowSymlinks::ENUM::NEVER)); + } + + static + int + rmdir_core(const string &basepath_, + const char *fusepath_, + const FollowSymlinks followsymlinks_, + const int error_) { int rv; string fullpath; @@ -60,21 +73,24 @@ namespace l fullpath = fs::path::make(basepath_,fusepath_); rv = fs::rmdir(fullpath); + if(l::should_unlink(rv,errno,followsymlinks_)) + rv = fs::unlink(fullpath); return error::calc(rv,error_,errno); } static int - rmdir_loop(const StrVec &basepaths_, - const char *fusepath_) + rmdir_loop(const StrVec &basepaths_, + const char *fusepath_, + const FollowSymlinks followsymlinks_) { int error; error = 0; for(size_t i = 0, ei = basepaths_.size(); i != ei; i++) { - error = l::rmdir_loop_core(basepaths_[i],fusepath_,error); + error = l::rmdir_core(basepaths_[i],fusepath_,followsymlinks_,error); } return -error; @@ -84,6 +100,7 @@ namespace l int rmdir(const Policy::Action &actionFunc_, const Branches &branches_, + const FollowSymlinks followsymlinks_, const char *fusepath_) { int rv; @@ -93,7 +110,7 @@ namespace l if(rv == -1) return -errno; - return l::rmdir_loop(basepaths,fusepath_); + return l::rmdir_loop(basepaths,fusepath_,followsymlinks_); } } @@ -108,6 +125,7 @@ namespace FUSE return l::rmdir(cfg->func.rmdir.policy, cfg->branches, + cfg->follow_symlinks, fusepath_); } } diff --git a/src/fuse_symlink.cpp b/src/fuse_symlink.cpp index cdfb407c..697202ee 100644 --- a/src/fuse_symlink.cpp +++ b/src/fuse_symlink.cpp @@ -16,9 +16,10 @@ #include "config.hpp" #include "errno.hpp" -#include "fs_symlink.hpp" #include "fs_clonepath.hpp" #include "fs_path.hpp" +#include "fs_symlink.hpp" +#include "fuse_getattr.hpp" #include "ugid.hpp" #include "fuse.h" @@ -56,16 +57,16 @@ namespace l static int symlink_loop_core(const string &newbasepath_, - const char *oldpath_, - const char *newpath_, + const char *target_, + const char *linkpath_, const int error_) { int rv; string fullnewpath; - fullnewpath = fs::path::make(newbasepath_,newpath_); + fullnewpath = fs::path::make(newbasepath_,linkpath_); - rv = fs::symlink(oldpath_,fullnewpath); + rv = fs::symlink(target_,fullnewpath); return error::calc(rv,error_,errno); } @@ -74,8 +75,8 @@ namespace l int symlink_loop(const string &existingpath_, const StrVec &newbasepaths_, - const char *oldpath_, - const char *newpath_, + const char *target_, + const char *linkpath_, const string &newdirpath_) { int rv; @@ -89,8 +90,8 @@ namespace l error = error::calc(rv,error,errno); else error = l::symlink_loop_core(newbasepaths_[i], - oldpath_, - newpath_, + target_, + linkpath_, error); } @@ -102,15 +103,15 @@ namespace l symlink(const Policy::Search &searchFunc_, const Policy::Create &createFunc_, const Branches &branches_, - const char *oldpath_, - const char *newpath_) + const char *target_, + const char *linkpath_) { int rv; string newdirpath; StrVec newbasepaths; StrVec existingpaths; - newdirpath = fs::path::dirname(newpath_); + newdirpath = fs::path::dirname(linkpath_); rv = searchFunc_(branches_,newdirpath,&existingpaths); if(rv == -1) @@ -121,15 +122,15 @@ namespace l return -errno; return l::symlink_loop(existingpaths[0],newbasepaths, - oldpath_,newpath_,newdirpath); + target_,linkpath_,newdirpath); } } namespace FUSE { int - symlink(const char *oldpath_, - const char *newpath_) + symlink(const char *target_, + const char *linkpath_) { Config::Read cfg; const fuse_context *fc = fuse_get_context(); @@ -138,7 +139,22 @@ namespace FUSE return l::symlink(cfg->func.getattr.policy, cfg->func.symlink.policy, cfg->branches, - oldpath_, - newpath_); + target_, + linkpath_); + } + + int + symlink(const char *target_, + const char *linkpath_, + struct stat *st_, + fuse_timeouts_t *timeout_) + { + int rv; + + rv = FUSE::symlink(target_,linkpath_); + if(rv < 0) + return rv; + + return FUSE::getattr(target_,st_,timeout_); } } diff --git a/src/fuse_symlink.hpp b/src/fuse_symlink.hpp index 0bbced2e..68988a9d 100644 --- a/src/fuse_symlink.hpp +++ b/src/fuse_symlink.hpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2016, Antonio SJ Musumeci + Copyright (c) 2020, Antonio SJ Musumeci Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -16,10 +16,19 @@ #pragma once +#include "fuse.h" + +#include namespace FUSE { int - symlink(const char *oldpath, - const char *newpath); + symlink(const char *target, + const char *linkpath); + + int + symlink(const char *target, + const char *linkpath, + struct stat *st, + fuse_timeouts_t *timeouts); } diff --git a/src/ghc/filesystem.hpp b/src/ghc/filesystem.hpp new file mode 100644 index 00000000..913c36dd --- /dev/null +++ b/src/ghc/filesystem.hpp @@ -0,0 +1,5742 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// +// 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() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +// #define GHC_USE_STD_FS +// #include +// namespace fs = std::filesystem; +// #endif +// #endif +// #ifndef GHC_USE_STD_FS +// #include +// namespace fs = ghc::filesystem; +// #endif +// +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_H +#define GHC_FILESYSTEM_H + +// #define BSD manifest constant only in +// sys/param.h +#ifndef _WIN32 +#include +#endif + +#ifndef GHC_OS_DETECTED +#if defined(__APPLE__) && defined(__MACH__) +#define GHC_OS_MACOS +#elif defined(__linux__) +#define GHC_OS_LINUX +#if defined(__ANDROID__) +#define GHC_OS_ANDROID +#endif +#elif defined(_WIN64) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN64 +#elif defined(_WIN32) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN32 +#elif defined(__svr4__) +#define GHC_OS_SYS5R4 +#elif defined(BSD) +#define GHC_OS_BSD +#elif defined(__EMSCRIPTEN__) +#define GHC_OS_WEB +#include +#else +#error "Operating system currently not supported!" +#endif +#define GHC_OS_DETECTED +#if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#if _MSVC_LANG == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#elif (defined(__cplusplus) && __cplusplus >= 201703L) +#if __cplusplus == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#endif +#endif + +#if defined(GHC_FILESYSTEM_IMPLEMENTATION) +#define GHC_EXPAND_IMPL +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#define GHC_FS_API +#define GHC_FS_API_CLASS +#else +#define GHC_FS_API __attribute__((visibility("default"))) +#define GHC_FS_API_CLASS __attribute__((visibility("default"))) +#endif +#elif defined(GHC_FILESYSTEM_FWD) +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#define GHC_FS_API extern +#define GHC_FS_API_CLASS +#else +#define GHC_FS_API extern +#define GHC_FS_API_CLASS +#endif +#else +#define GHC_EXPAND_IMPL +#define GHC_INLINE inline +#define GHC_FS_API +#define GHC_FS_API_CLASS +#endif + +#ifdef GHC_EXPAND_IMPL + +#ifdef GHC_OS_WINDOWS +#include +// additional includes +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_ANDROID +#include +#if __ANDROID_API__ < 12 +#include +#endif +#include +#define statvfs statfs +#else +#include +#endif +#if !defined(__ANDROID__) || __ANDROID_API__ >= 26 +#include +#endif +#endif +#ifdef GHC_OS_MACOS +#include +#endif + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#else // GHC_EXPAND_IMPL + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_WINDOWS +#include +#endif +#endif // GHC_EXPAND_IMPL + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Enforce C++17 API where possible when compiling for C++20, handles the following cases: +// * fs::path::u8string() returns std::string instead of std::u8string +// #define GHC_FILESYSTEM_ENFORCE_CPP17_API +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories +// configure LWG conformance () +#define LWG_2682_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular +// file with that name, it is superceded by P1164R1, so only activate if really needed +// #define LWG_2935_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2936 enables new element wise (more expensive) path comparison +// * if this->root_name().native().compare(p.root_name().native()) != 0 return result +// * if this->has_root_directory() and !p.has_root_directory() return -1 +// * if !this->has_root_directory() and p.has_root_directory() return -1 +// * else result of element wise comparison of path iteration where first comparison is != 0 or 0 +// if all comparisons are 0 (on Windows this implementation does case insensitive root_name() +// comparison) +#define LWG_2936_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) +#define LWG_2937_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the windows +// version defaults to std::wstring storage backend. Still all std::string will be interpreted +// as UTF-8 encoded. With this define you can enfoce the old behavior on Windows, using +// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This +// needs more conversions so it is (an was before v1.5) slower, bot might help keeping source +// homogenous in a multi platform project. +// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, +// instead of replacing them with the unicode replacement character (U+FFFD). +// #define GHC_RAISE_UNICODE_ERRORS +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. +// instead of replacing them with the unicode replacement character (U+FFFD). +#ifndef GHC_WIN_DISABLE_AUTO_PREFIXES +#define GHC_WIN_AUTO_PREFIX_LONG_PATH +#endif // GHC_WIN_DISABLE_AUTO_PREFIXES +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) +#define GHC_FILESYSTEM_VERSION 10500L + +#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) +#define GHC_WITH_EXCEPTIONS +#endif +#if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) +#error "Can't raise unicode errors whith exception support disabled" +#endif + +namespace ghc { +namespace filesystem { + +// temporary existing exception type for yet unimplemented parts +class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error +{ +public: + not_implemented_exception() + : std::logic_error("function not implemented yet.") + { + } +}; + +template +class path_helper_base +{ +public: + using value_type = char_type; +#ifdef GHC_OS_WINDOWS + static constexpr value_type preferred_separator = '\\'; +#else + static constexpr value_type preferred_separator = '/'; +#endif +}; + +#if __cplusplus < 201703L +template +constexpr char_type path_helper_base::preferred_separator; +#endif + +#ifdef GHC_OS_WINDOWS +class path; +namespace detail { +bool has_executable_extension(const path& p); +} +#endif + +// 30.10.8 class path +class GHC_FS_API_CLASS path +#if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) +#define GHC_USE_WCHAR_T +#define GHC_NATIVEWP(p) p.c_str() +#define GHC_PLATFORM_LITERAL(str) L##str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#else +#define GHC_NATIVEWP(p) p.wstring().c_str() +#define GHC_PLATFORM_LITERAL(str) str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#endif + using string_type = std::basic_string; + using path_helper_base::preferred_separator; + + // 30.10.10.1 enumeration format + /// The path format in wich the constructor argument is given. + enum format { + generic_format, ///< The generic format, internally used by + ///< ghc::filesystem::path with slashes + native_format, ///< The format native to the current platform this code + ///< is build for + auto_format, ///< Try to auto-detect the format, fallback to native + }; + + template + struct _is_basic_string : std::false_type + { + }; + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string, std::allocator>> : std::true_type + { + }; +#ifdef __cpp_lib_string_view + template + struct _is_basic_string> : std::true_type + { + }; +#endif + + template + using path_type = typename std::enable_if::value, path>::type; +#ifdef GHC_USE_WCHAR_T + template + using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value, path>::type; +#else + template + using path_from_string = typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value, path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#endif + // 30.10.8.4.1 constructors and destructor + path() noexcept; + path(const path& p); + path(path&& p) noexcept; + path(string_type&& source, format fmt = auto_format); + template > + path(const Source& source, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, format fmt = auto_format); +#ifdef GHC_WITH_EXCEPTIONS + template > + path(const Source& source, const std::locale& loc, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); +#endif + ~path(); + + // 30.10.8.4.2 assignments + path& operator=(const path& p); + path& operator=(path&& p) noexcept; + path& operator=(string_type&& source); + path& assign(string_type&& source); + template + path& operator=(const Source& source); + template + path& assign(const Source& source); + template + path& assign(InputIterator first, InputIterator last); + + // 30.10.8.4.3 appends + path& operator/=(const path& p); + template + path& operator/=(const Source& source); + template + path& append(const Source& source); + template + path& append(InputIterator first, InputIterator last); + + // 30.10.8.4.4 concatenation + path& operator+=(const path& x); + path& operator+=(const string_type& x); +#ifdef __cpp_lib_string_view + path& operator+=(std::basic_string_view x); +#endif + path& operator+=(const value_type* x); + path& operator+=(value_type x); + template + path_from_string& operator+=(const Source& x); + template + path_type_EcharT& operator+=(EcharT x); + template + path& concat(const Source& x); + template + path& concat(InputIterator first, InputIterator last); + + // 30.10.8.4.5 modifiers + void clear() noexcept; + path& make_preferred(); + path& remove_filename(); + path& replace_filename(const path& replacement); + path& replace_extension(const path& replacement = path()); + void swap(path& rhs) noexcept; + + // 30.10.8.4.6 native format observers + const string_type& native() const noexcept; + const value_type* c_str() const noexcept; + operator string_type() const; + template , class Allocator = std::allocator> + std::basic_string string(const Allocator& a = Allocator()) const; + std::string string() const; + std::wstring wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string u8string() const; +#else + std::string u8string() const; +#endif + std::u16string u16string() const; + std::u32string u32string() const; + + // 30.10.8.4.7 generic format observers + template , class Allocator = std::allocator> + std::basic_string generic_string(const Allocator& a = Allocator()) const; + std::string generic_string() const; + std::wstring generic_wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string generic_u8string() const; +#else + std::string generic_u8string() const; +#endif + std::u16string generic_u16string() const; + std::u32string generic_u32string() const; + + // 30.10.8.4.8 compare + int compare(const path& p) const noexcept; + int compare(const string_type& s) const; +#ifdef __cpp_lib_string_view + int compare(std::basic_string_view s) const; +#endif + int compare(const value_type* s) const; + + // 30.10.8.4.9 decomposition + path root_name() const; + path root_directory() const; + path root_path() const; + path relative_path() const; + path parent_path() const; + path filename() const; + path stem() const; + path extension() const; + + // 30.10.8.4.10 query + bool empty() const noexcept; + bool has_root_name() const; + bool has_root_directory() const; + bool has_root_path() const; + bool has_relative_path() const; + bool has_parent_path() const; + bool has_filename() const; + bool has_stem() const; + bool has_extension() const; + bool is_absolute() const; + bool is_relative() const; + + // 30.10.8.4.11 generation + path lexically_normal() const; + path lexically_relative(const path& base) const; + path lexically_proximate(const path& base) const; + + // 30.10.8.5 iterators + class iterator; + using const_iterator = iterator; + iterator begin() const; + iterator end() const; + +private: + using impl_value_type = value_type; + using impl_string_type = std::basic_string; + friend class directory_iterator; + void append_name(const value_type* name); + static constexpr impl_value_type generic_separator = '/'; + template + class input_iterator_range + { + public: + typedef InputIterator iterator; + typedef InputIterator const_iterator; + typedef typename InputIterator::difference_type difference_type; + + input_iterator_range(const InputIterator& first, const InputIterator& last) + : _first(first) + , _last(last) + { + } + + InputIterator begin() const { return _first; } + InputIterator end() const { return _last; } + + private: + InputIterator _first; + InputIterator _last; + }; + friend void swap(path& lhs, path& rhs) noexcept; + friend size_t hash_value(const path& p) noexcept; + friend path canonical(const path& p, std::error_code& ec); + string_type::size_type root_name_length() const noexcept; + void postprocess_path_with_format(format fmt); + void check_long_path(); + impl_string_type _path; +#ifdef GHC_OS_WINDOWS + void handle_prefixes(); + friend bool detail::has_executable_extension(const path& p); +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + string_type::size_type _prefixLength{0}; +#else // GHC_WIN_AUTO_PREFIX_LONG_PATH + static const string_type::size_type _prefixLength{0}; +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +#else + static const string_type::size_type _prefixLength{0}; +#endif +}; + +// 30.10.8.6 path non-member functions +GHC_FS_API void swap(path& lhs, path& rhs) noexcept; +GHC_FS_API size_t hash_value(const path& p) noexcept; +#ifdef GHC_HAS_THREEWAY_COMP +GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; +#endif +GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API path operator/(const path& lhs, const path& rhs); + +// 30.10.8.6.1 path inserter and extractor +template +std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); +template +std::basic_istream& operator>>(std::basic_istream& is, path& p); + +// 30.10.8.6.2 path factory functions +template > +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(const Source& source); +template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(InputIterator first, InputIterator last); + +// 30.10.9 class filesystem_error +class GHC_FS_API_CLASS filesystem_error : public std::system_error +{ +public: + filesystem_error(const std::string& what_arg, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); + const path& path1() const noexcept; + const path& path2() const noexcept; + const char* what() const noexcept override; + +private: + std::string _what_arg; + std::error_code _ec; + path _p1, _p2; +}; + +class GHC_FS_API_CLASS path::iterator +{ +public: + using value_type = const path; + using difference_type = std::ptrdiff_t; + using pointer = const path*; + using reference = const path&; + using iterator_category = std::bidirectional_iterator_tag; + + iterator(); + iterator(const path& p, const impl_string_type::const_iterator& pos); + iterator& operator++(); + iterator operator++(int); + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + reference operator*() const; + pointer operator->() const; + +private: + friend class path; + impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; + impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; + void updateCurrent(); + impl_string_type::const_iterator _first; + impl_string_type::const_iterator _last; + impl_string_type::const_iterator _prefix; + impl_string_type::const_iterator _root; + impl_string_type::const_iterator _iter; + path _current; +}; + +struct space_info +{ + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +// 30.10.10, enumerations +enum class file_type { + none, + not_found, + regular, + directory, + symlink, + block, + character, + fifo, + socket, + unknown, +}; + +enum class perms : uint16_t { + none = 0, + + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + + all = 0777, + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + + mask = 07777, + unknown = 0xffff +}; + +enum class perm_options : uint16_t { + replace = 3, + add = 1, + remove = 2, + nofollow = 4, +}; + +enum class copy_options : uint16_t { + none = 0, + + skip_existing = 1, + overwrite_existing = 2, + update_existing = 4, + + recursive = 8, + + copy_symlinks = 0x10, + skip_symlinks = 0x20, + + directories_only = 0x40, + create_symlinks = 0x80, +#ifndef GHC_OS_WEB + create_hard_links = 0x100 +#endif +}; + +enum class directory_options : uint16_t { + none = 0, + follow_directory_symlink = 1, + skip_permission_denied = 2, +}; + +// 30.10.11 class file_status +class GHC_FS_API_CLASS file_status +{ +public: + // 30.10.11.1 constructors and destructor + file_status() noexcept; + explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; + file_status(const file_status&) noexcept; + file_status(file_status&&) noexcept; + ~file_status(); + // assignments: + file_status& operator=(const file_status&) noexcept; + file_status& operator=(file_status&&) noexcept; + // 30.10.11.3 modifiers + void type(file_type ft) noexcept; + void permissions(perms prms) noexcept; + // 30.10.11.2 observers + file_type type() const noexcept; + perms permissions() const noexcept; + friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); } +private: + file_type _type; + perms _perms; +}; + +using file_time_type = std::chrono::time_point; + +// 30.10.12 Class directory_entry +class GHC_FS_API_CLASS directory_entry +{ +public: + // 30.10.12.1 constructors and destructor + directory_entry() noexcept = default; + directory_entry(const directory_entry&) = default; + directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_entry(const path& p); +#endif + directory_entry(const path& p, std::error_code& ec); + ~directory_entry(); + + // assignments: + directory_entry& operator=(const directory_entry&) = default; + directory_entry& operator=(directory_entry&&) noexcept = default; + + // 30.10.12.2 modifiers +#ifdef GHC_WITH_EXCEPTIONS + void assign(const path& p); + void replace_filename(const path& p); + void refresh(); +#endif + void assign(const path& p, std::error_code& ec); + void replace_filename(const path& p, std::error_code& ec); + void refresh(std::error_code& ec) noexcept; + + // 30.10.12.3 observers + const filesystem::path& path() const noexcept; + operator const filesystem::path&() const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool exists() const; + bool is_block_file() const; + bool is_character_file() const; + bool is_directory() const; + bool is_fifo() const; + bool is_other() const; + bool is_regular_file() const; + bool is_socket() const; + bool is_symlink() const; + uintmax_t file_size() const; + file_time_type last_write_time() const; + file_status status() const; + file_status symlink_status() const; +#endif + bool exists(std::error_code& ec) const noexcept; + bool is_block_file(std::error_code& ec) const noexcept; + bool is_character_file(std::error_code& ec) const noexcept; + bool is_directory(std::error_code& ec) const noexcept; + bool is_fifo(std::error_code& ec) const noexcept; + bool is_other(std::error_code& ec) const noexcept; + bool is_regular_file(std::error_code& ec) const noexcept; + bool is_socket(std::error_code& ec) const noexcept; + bool is_symlink(std::error_code& ec) const noexcept; + uintmax_t file_size(std::error_code& ec) const noexcept; + file_time_type last_write_time(std::error_code& ec) const noexcept; + file_status status(std::error_code& ec) const noexcept; + file_status symlink_status(std::error_code& ec) const noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS + uintmax_t hard_link_count() const; +#endif + uintmax_t hard_link_count(std::error_code& ec) const noexcept; +#endif + +#ifdef GHC_HAS_THREEWAY_COMP + std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept; +#endif + bool operator<(const directory_entry& rhs) const noexcept; + bool operator==(const directory_entry& rhs) const noexcept; + bool operator!=(const directory_entry& rhs) const noexcept; + bool operator<=(const directory_entry& rhs) const noexcept; + bool operator>(const directory_entry& rhs) const noexcept; + bool operator>=(const directory_entry& rhs) const noexcept; + +private: + friend class directory_iterator; + filesystem::path _path; + file_status _status; + file_status _symlink_status; + uintmax_t _file_size = 0; +#ifndef GHC_OS_WINDOWS + uintmax_t _hard_link_count = 0; +#endif + time_t _last_write_time = 0; +}; + +// 30.10.13 Class directory_iterator +class GHC_FS_API_CLASS directory_iterator +{ +public: + class GHC_FS_API_CLASS proxy + { + public: + const directory_entry& operator*() const& noexcept { return _dir_entry; } + directory_entry operator*() && noexcept { return std::move(_dir_entry); } + + private: + explicit proxy(const directory_entry& dir_entry) + : _dir_entry(dir_entry) + { + } + friend class directory_iterator; + friend class recursive_directory_iterator; + directory_entry _dir_entry; + }; + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // 30.10.13.1 member functions + directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_iterator(const path& p); + directory_iterator(const path& p, directory_options options); +#endif + directory_iterator(const path& p, std::error_code& ec) noexcept; + directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + directory_iterator(const directory_iterator& rhs); + directory_iterator(directory_iterator&& rhs) noexcept; + ~directory_iterator(); + directory_iterator& operator=(const directory_iterator& rhs); + directory_iterator& operator=(directory_iterator&& rhs) noexcept; + const directory_entry& operator*() const; + const directory_entry* operator->() const; +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator& operator++(); +#endif + directory_iterator& increment(std::error_code& ec) noexcept; + + // other members as required by 27.2.3, input iterators +#ifdef GHC_WITH_EXCEPTIONS + proxy operator++(int) + { + proxy p{**this}; + ++*this; + return p; + } +#endif + bool operator==(const directory_iterator& rhs) const; + bool operator!=(const directory_iterator& rhs) const; + +private: + friend class recursive_directory_iterator; + class impl; + std::shared_ptr _impl; +}; + +// 30.10.13.2 directory_iterator non-member functions +GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; +GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; + +// 30.10.14 class recursive_directory_iterator +class GHC_FS_API_CLASS recursive_directory_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // 30.10.14.1 constructors and destructor + recursive_directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit recursive_directory_iterator(const path& p); + recursive_directory_iterator(const path& p, directory_options options); +#endif + recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; + recursive_directory_iterator(const recursive_directory_iterator& rhs); + recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; + ~recursive_directory_iterator(); + + // 30.10.14.1 observers + directory_options options() const; + int depth() const; + bool recursion_pending() const; + + const directory_entry& operator*() const; + const directory_entry* operator->() const; + + // 30.10.14.1 modifiers recursive_directory_iterator& + recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); + recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; +#ifdef GHC_WITH_EXCEPTIONS + recursive_directory_iterator& operator++(); +#endif + recursive_directory_iterator& increment(std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + void pop(); +#endif + void pop(std::error_code& ec); + void disable_recursion_pending(); + + // other members as required by 27.2.3, input iterators +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator::proxy operator++(int) + { + directory_iterator::proxy proxy{**this}; + ++*this; + return proxy; + } +#endif + bool operator==(const recursive_directory_iterator& rhs) const; + bool operator!=(const recursive_directory_iterator& rhs) const; + +private: + struct recursive_directory_iterator_impl + { + directory_options _options; + bool _recursion_pending; + std::stack _dir_iter_stack; + recursive_directory_iterator_impl(directory_options options, bool recursion_pending) + : _options(options) + , _recursion_pending(recursion_pending) + { + } + }; + std::shared_ptr _impl; +}; + +// 30.10.14.2 directory_iterator non-member functions +GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; +GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; + +// 30.10.15 filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path absolute(const path& p); +GHC_FS_API path canonical(const path& p); +GHC_FS_API void copy(const path& from, const path& to); +GHC_FS_API void copy(const path& from, const path& to, copy_options options); +GHC_FS_API bool copy_file(const path& from, const path& to); +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); +GHC_FS_API bool create_directories(const path& p); +GHC_FS_API bool create_directory(const path& p); +GHC_FS_API bool create_directory(const path& p, const path& attributes); +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); +GHC_FS_API void create_symlink(const path& to, const path& new_symlink); +GHC_FS_API path current_path(); +GHC_FS_API void current_path(const path& p); +GHC_FS_API bool exists(const path& p); +GHC_FS_API bool equivalent(const path& p1, const path& p2); +GHC_FS_API uintmax_t file_size(const path& p); +GHC_FS_API bool is_block_file(const path& p); +GHC_FS_API bool is_character_file(const path& p); +GHC_FS_API bool is_directory(const path& p); +GHC_FS_API bool is_empty(const path& p); +GHC_FS_API bool is_fifo(const path& p); +GHC_FS_API bool is_other(const path& p); +GHC_FS_API bool is_regular_file(const path& p); +GHC_FS_API bool is_socket(const path& p); +GHC_FS_API bool is_symlink(const path& p); +GHC_FS_API file_time_type last_write_time(const path& p); +GHC_FS_API void last_write_time(const path& p, file_time_type new_time); +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); +GHC_FS_API path proximate(const path& p, const path& base = current_path()); +GHC_FS_API path read_symlink(const path& p); +GHC_FS_API path relative(const path& p, const path& base = current_path()); +GHC_FS_API bool remove(const path& p); +GHC_FS_API uintmax_t remove_all(const path& p); +GHC_FS_API void rename(const path& from, const path& to); +GHC_FS_API void resize_file(const path& p, uintmax_t size); +GHC_FS_API space_info space(const path& p); +GHC_FS_API file_status status(const path& p); +GHC_FS_API file_status symlink_status(const path& p); +GHC_FS_API path temp_directory_path(); +GHC_FS_API path weakly_canonical(const path& p); +#endif +GHC_FS_API path absolute(const path& p, std::error_code& ec); +GHC_FS_API path canonical(const path& p, std::error_code& ec); +GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API path current_path(std::error_code& ec); +GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool exists(file_status s) noexcept; +GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_block_file(file_status s) noexcept; +GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_character_file(file_status s) noexcept; +GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_directory(file_status s) noexcept; +GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_fifo(file_status s) noexcept; +GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_other(file_status s) noexcept; +GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_regular_file(file_status s) noexcept; +GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_socket(file_status s) noexcept; +GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_symlink(file_status s) noexcept; +GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept; +GHC_FS_API path proximate(const path& p, std::error_code& ec); +GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); +GHC_FS_API path read_symlink(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); +GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; +GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool status_known(file_status s) noexcept; +GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; +GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); +GHC_FS_API uintmax_t hard_link_count(const path& p); +#endif +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; +#endif + +// Non-C++17 add-on std::fstream wrappers with path +template > +class basic_filebuf : public std::basic_filebuf +{ +public: + basic_filebuf() {} + ~basic_filebuf() override {} + basic_filebuf(const basic_filebuf&) = delete; + const basic_filebuf& operator=(const basic_filebuf&) = delete; + basic_filebuf* open(const path& p, std::ios_base::openmode mode) + { +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; +#else + return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; +#endif + } +}; + +template > +class basic_ifstream : public std::basic_ifstream +{ +public: + basic_ifstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } +#endif + basic_ifstream(const basic_ifstream&) = delete; + const basic_ifstream& operator=(const basic_ifstream&) = delete; + ~basic_ifstream() override {} +}; + +template > +class basic_ofstream : public std::basic_ofstream +{ +public: + basic_ofstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } +#endif + basic_ofstream(const basic_ofstream&) = delete; + const basic_ofstream& operator=(const basic_ofstream&) = delete; + ~basic_ofstream() override {} +}; + +template > +class basic_fstream : public std::basic_fstream +{ +public: + basic_fstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } +#endif + basic_fstream(const basic_fstream&) = delete; + const basic_fstream& operator=(const basic_fstream&) = delete; + ~basic_fstream() override {} +}; + +typedef basic_filebuf filebuf; +typedef basic_filebuf wfilebuf; +typedef basic_ifstream ifstream; +typedef basic_ifstream wifstream; +typedef basic_ofstream ofstream; +typedef basic_ofstream wofstream; +typedef basic_fstream fstream; +typedef basic_fstream wfstream; + +class GHC_FS_API_CLASS u8arguments +{ +public: + u8arguments(int& argc, char**& argv); + ~u8arguments() + { + _refargc = _argc; + _refargv = _argv; + } + + bool valid() const { return _isvalid; } + +private: + int _argc; + char** _argv; + int& _refargc; + char**& _refargv; + bool _isvalid; +#ifdef GHC_OS_WINDOWS + std::vector _args; + std::vector _argp; +#endif +}; + +//------------------------------------------------------------------------------------------------- +// Implementation +//------------------------------------------------------------------------------------------------- + +namespace detail { +enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; +GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); +GHC_FS_API bool is_surrogate(uint32_t c); +GHC_FS_API bool is_high_surrogate(uint32_t c); +GHC_FS_API bool is_low_surrogate(uint32_t c); +GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); +enum class portable_error { + none = 0, + exists, + not_found, + not_supported, + not_implemented, + invalid_argument, + is_a_directory, +}; +GHC_FS_API std::error_code make_error_code(portable_error err); +#ifdef GHC_OS_WINDOWS +GHC_FS_API std::error_code make_system_error(uint32_t err = 0); +#else +GHC_FS_API std::error_code make_system_error(int err = 0); +#endif +} // namespace detail + +namespace detail { + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::error_code make_error_code(portable_error err) +{ +#ifdef GHC_OS_WINDOWS + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); + case portable_error::not_found: + return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); + case portable_error::not_supported: + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); + case portable_error::is_a_directory: +#ifdef ERROR_DIRECTORY_NOT_SUPPORTED + return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); +#else + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); +#endif + } +#else + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(EEXIST, std::system_category()); + case portable_error::not_found: + return std::error_code(ENOENT, std::system_category()); + case portable_error::not_supported: + return std::error_code(ENOTSUP, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ENOSYS, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(EINVAL, std::system_category()); + case portable_error::is_a_directory: + return std::error_code(EISDIR, std::system_category()); + } +#endif + return std::error_code(); +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE std::error_code make_system_error(uint32_t err) +{ + return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); +} +#else +GHC_INLINE std::error_code make_system_error(int err) +{ + return std::error_code(err ? err : errno, std::system_category()); +} +#endif + +#endif // GHC_EXPAND_IMPL + +template +using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; +} // namespace detail + +template +constexpr detail::EnableBitmask operator&(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) & static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator|(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) | static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator^(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) ^ static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator~(Enum X) +{ + using underlying = typename std::underlying_type::type; + return static_cast(~static_cast(X)); +} + +template +detail::EnableBitmask& operator&=(Enum& X, Enum Y) +{ + X = X & Y; + return X; +} + +template +detail::EnableBitmask& operator|=(Enum& X, Enum Y) +{ + X = X | Y; + return X; +} + +template +detail::EnableBitmask& operator^=(Enum& X, Enum Y) +{ + X = X ^ Y; + return X; +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) +{ + return (static_cast(c - lo) < (hi - lo + 1)); +} + +GHC_INLINE bool is_surrogate(uint32_t c) +{ + return in_range(c, 0xd800, 0xdfff); +} + +GHC_INLINE bool is_high_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xd800; +} + +GHC_INLINE bool is_low_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xdc00; +} + +GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) +{ + if (unicode <= 0x7f) { + str.push_back(static_cast(unicode)); + } + else if (unicode >= 0x80 && unicode <= 0x7ff) { + str.push_back(static_cast((unicode >> 6) + 192)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { + str.push_back(static_cast((unicode >> 12) + 224)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if (unicode >= 0x10000 && unicode <= 0x10ffff) { + str.push_back(static_cast((unicode >> 18) + 240)); + str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(str, 0xfffd); +#endif + } +} + +// Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) +// and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; +// Generating debugging and shrinking my own DFA from scratch was a day of fun! +GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) +{ + static const uint32_t utf8_state_info[] = { + // encoded states + 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, + 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, + }; + uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; + codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); + return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); +} + +GHC_INLINE bool validUtf8(const std::string& utf8String) +{ + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { + return false; + } + } + if (utf8_state) { + return false; + } + return true; +} + +} // namespace detail + +#endif + +namespace detail { + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(utf8String.begin(), utf8String.end(), alloc); +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + if (codepoint <= 0xffff) { + result += static_cast(codepoint); + } + else { + codepoint -= 0x10000; + result += static_cast((codepoint >> 10) + 0xd800); + result += static_cast((codepoint & 0x3ff) + 0xdc00); + } + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + result += static_cast(codepoint); + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template +inline StringType fromUtf8(const charT (&utf8String)[N]) +{ +#ifdef __cpp_lib_string_view + return fromUtf8(std::basic_string_view(utf8String, N - 1)); +#else + return fromUtf8(std::basic_string(utf8String, N - 1)); +#endif +} + +template ::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1> +inline std::string toUtf8(const strT& unicodeString) +{ + return std::string(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { + char32_t c = *iter; + if (is_surrogate(c)) { + ++iter; + if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { + appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(result, 0xfffd); + if (iter == unicodeString.end()) { + break; + } +#endif + } + } + else { + appendUTF8(result, c); + } + } + return result; +} + +template ::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto c : unicodeString) { + appendUTF8(result, static_cast(c)); + } + return result; +} + +template +inline std::string toUtf8(const charT* unicodeString) +{ + return toUtf8(std::basic_string>(unicodeString)); +} + +#ifdef GHC_USE_WCHAR_T +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return StringType(temp.begin(), temp.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(wString.begin(), wString.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return fromUtf8(temp, alloc); +} + +template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return fromUtf8(unicodeString); +} + +template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return std::wstring(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + auto temp = toUtf8(unicodeString); + return fromUtf8(temp); +} + +template +inline std::wstring toWChar(const charT* unicodeString) +{ + return toWChar(std::basic_string>(unicodeString)); +} +#endif // GHC_USE_WCHAR_T + +} // namespace detail + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +template ::value, bool>::type = true> +GHC_INLINE bool startsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); +} + +template ::value, bool>::type = true> +GHC_INLINE bool endsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; +} + +} // namespace detail + +GHC_INLINE void path::check_long_path() +{ +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + postprocess_path_with_format(native_format); + } +#endif +} + +GHC_INLINE void path::postprocess_path_with_format(path::format fmt) +{ +#ifdef GHC_RAISE_UNICODE_ERRORS + if (!detail::validUtf8(_path)) { + path t; + t._path = _path; + throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); + } +#endif + switch (fmt) { +#ifdef GHC_OS_WINDOWS + case path::native_format: + case path::auto_format: + case path::generic_format: + for (auto& c : _path) { + if (c == generic_separator) { + c = preferred_separator; + } + } +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; + } +#endif + handle_prefixes(); + break; +#else + case path::auto_format: + case path::native_format: + case path::generic_format: + // nothing to do + break; +#endif + } + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } + else { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path(const Source& source, format fmt) +#ifdef GHC_USE_WCHAR_T + : _path(detail::toWChar(source)) +#else + : _path(detail::toUtf8(source)) +#endif +{ + postprocess_path_with_format(fmt); +} + +template +inline path u8path(const Source& source) +{ + return path(source); +} +template +inline path u8path(InputIterator first, InputIterator last) +{ + return path(first, last); +} + +template +inline path::path(InputIterator first, InputIterator last, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + // delegated +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) +{ +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { + if (*str1++ == 0) + return true; + } + return false; +#else // __GNUC__ +#ifdef GHC_USE_WCHAR_T + return 0 == ::_wcsicmp(str1, str2); +#else // GHC_USE_WCHAR_T + return 0 == ::_stricmp(str1, str2); +#endif // GHC_USE_WCHAR_T +#endif // __GNUC__ +#else // GHC_OS_WINDOWS + return 0 == ::strcasecmp(str1, str2); +#endif // GHC_OS_WINDOWS +} + +GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) +{ + while (len1 > 0 && len2 > 0 && ::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2)) { + --len1; + --len2; + ++str1; + ++str2; + } + if (len1 && len2) { + return *str1 < *str2 ? -1 : 1; + } + if (len1 == 0 && len2 == 0) { + return 0; + } + return len1 == 0 ? -1 : 1; +} + +GHC_INLINE const char* strerror_adapter(char* gnu, char*) +{ + return gnu; +} + +GHC_INLINE const char* strerror_adapter(int posix, char* buffer) +{ + if (posix) { + return "Error in strerror_r!"; + } + return buffer; +} + +template +GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) +{ +#if defined(GHC_OS_WINDOWS) + LPVOID msgBuf; + DWORD dw = code ? static_cast(code) : ::GetLastError(); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); + std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); + LocalFree(msgBuf); + return msg; +#else + char buffer[512]; + return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); +#endif +} + +#ifdef GHC_OS_WINDOWS +using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); +using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) +{ + std::error_code tec; + auto fs = status(target_name, tec); + if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { + ec = detail::make_error_code(detail::portable_error::not_supported); + return; + } +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(detail::fromUtf8(new_symlink.u8string()).c_str(), detail::fromUtf8(target_name.u8string()).c_str(), to_directory ? 1 : 0) == 0) { + auto result = ::GetLastError(); + if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(detail::fromUtf8(new_symlink.u8string()).c_str(), detail::fromUtf8(target_name.u8string()).c_str(), to_directory ? 3 : 2) != 0) { + return; + } + ec = detail::make_system_error(result); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { + ec = detail::make_system_error(); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) +{ + ULONG size = ::GetFullPathNameW(p, 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); + if (s2 && s2 < size) { + return path(std::wstring(buf.data(), s2)); + } + } + ec = detail::make_system_error(); + return path(); +} + +#else +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) +{ + if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} + +#ifndef GHC_OS_WEB +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ + if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} +#endif +#endif + +template +GHC_INLINE file_status file_status_from_st_mode(T mode) +{ +#ifdef GHC_OS_WINDOWS + file_type ft = file_type::unknown; + if ((mode & _S_IFDIR) == _S_IFDIR) { + ft = file_type::directory; + } + else if ((mode & _S_IFREG) == _S_IFREG) { + ft = file_type::regular; + } + else if ((mode & _S_IFCHR) == _S_IFCHR) { + ft = file_type::character; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#else + file_type ft = file_type::unknown; + if (S_ISDIR(mode)) { + ft = file_type::directory; + } + else if (S_ISREG(mode)) { + ft = file_type::regular; + } + else if (S_ISCHR(mode)) { + ft = file_type::character; + } + else if (S_ISBLK(mode)) { + ft = file_type::block; + } + else if (S_ISFIFO(mode)) { + ft = file_type::fifo; + } + else if (S_ISLNK(mode)) { + ft = file_type::symlink; + } + else if (S_ISSOCK(mode)) { + ft = file_type::socket; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#endif +} + +GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE + typedef struct _REPARSE_DATA_BUFFER + { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; + } REPARSE_DATA_BUFFER; +#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) +#endif +#endif + + std::shared_ptr file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + if (file.get() == INVALID_HANDLE_VALUE) { + ec = detail::make_system_error(); + return path(); + } + + std::shared_ptr reparseData((REPARSE_DATA_BUFFER*)std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE), std::free); + ULONG bufferUsed; + path result; + if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { + if (IsReparseTagMicrosoft(reparseData->ReparseTag)) { + switch (reparseData->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: { + auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); + auto substituteName = + std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { + result = printName; + } + else { + result = substituteName; + } + if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { + result = p.parent_path() / result; + } + break; + } + case IO_REPARSE_TAG_MOUNT_POINT: + result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + break; + default: + break; + } + } + } + else { + ec = detail::make_system_error(); + } + return result; +#else + size_t bufferSize = 256; + while (true) { + std::vector buffer(bufferSize, static_cast(0)); + auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); + if (rc < 0) { + ec = detail::make_system_error(); + return path(); + } + else if (rc < static_cast(bufferSize)) { + return path(std::string(buffer.data(), static_cast(rc))); + } + bufferSize *= 2; + } + return path(); +#endif +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) +{ + ULARGE_INTEGER ull; + ull.LowPart = ft.dwLowDateTime; + ull.HighPart = ft.dwHighDateTime; + return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) +{ + LONGLONG ll; + ll = Int32x32To64(t, 10000000) + 116444736000000000; + ft.dwLowDateTime = static_cast(ll); + ft.dwHighDateTime = static_cast(ll >> 32); +} + +template +GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) +{ + return static_cast(-1); +} + +template <> +GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) +{ + return info->nNumberOfLinks; +} + +template +GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code&, uintmax_t* sz = nullptr, time_t* lwt = nullptr) +{ + file_type ft = file_type::unknown; + if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + ft = file_type::symlink; + } + else { + if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + ft = file_type::directory; + } + else { + ft = file_type::regular; + } + } + perms prms = perms::owner_read | perms::group_read | perms::others_read; + if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { + prms = prms | perms::owner_write | perms::group_write | perms::others_write; + } + if (has_executable_extension(p)) { + prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; + } + if (sz) { + *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; + } + if (lwt) { + *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); + } + return file_status(ft, prms); +} + +#endif + +GHC_INLINE bool is_not_found_error(std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; +#else + return ec.value() == ENOENT || ec.value() == ENOTDIR; +#endif +} + +GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept +{ +#ifdef GHC_OS_WINDOWS + file_status fs; + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else { + ec.clear(); + fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); + if (nhl) { + *nhl = 0; + } + if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + fs.type(file_type::symlink); + } + } + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return ec ? file_status(file_type::none) : fs; +#else + (void)sz; + (void)nhl; + (void)lwt; + struct ::stat fs; + auto result = ::lstat(p.c_str(), &fs); + if (result == 0) { + ec.clear(); + file_status f_s = detail::file_status_from_st_mode(fs.st_mode); + return f_s; + } + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); +#endif +} + +GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (recurse_count > 16) { + ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); + return file_status(file_type::unknown); + } + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + path target = resolveSymlink(p, ec); + file_status result; + if (!ec && !target.empty()) { + if (sls) { + *sls = status_from_INFO(p, &attr, ec); + } + return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); + } + return file_status(file_type::unknown); + } + if (ec) { + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return file_status(file_type::none); + } + if (nhl) { + *nhl = 0; + } + return detail::status_from_INFO(p, &attr, ec, sz, lwt); +#else + (void)recurse_count; + struct ::stat st; + auto result = ::lstat(p.c_str(), &st); + if (result == 0) { + ec.clear(); + file_status fs = detail::file_status_from_st_mode(st.st_mode); + if (fs.type() == file_type::symlink) { + result = ::stat(p.c_str(), &st); + if (result == 0) { + if (sls) { + *sls = fs; + } + fs = detail::file_status_from_st_mode(st.st_mode); + } + } + if (sz) { + *sz = static_cast(st.st_size); + } + if (nhl) { + *nhl = st.st_nlink; + } + if (lwt) { + *lwt = st.st_mtime; + } + return fs; + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } +#endif +} + +} // namespace detail + +GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) + : _argc(argc) + , _argv(argv) + , _refargc(argc) + , _refargv(argv) + , _isvalid(false) +{ +#ifdef GHC_OS_WINDOWS + LPWSTR* p; + p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + _args.reserve(static_cast(argc)); + _argp.reserve(static_cast(argc)); + for (size_t i = 0; i < static_cast(argc); ++i) { + _args.push_back(detail::toUtf8(std::wstring(p[i]))); + _argp.push_back((char*)_args[i].data()); + } + argv = _argp.data(); + ::LocalFree(p); + _isvalid = true; +#else + std::setlocale(LC_ALL, ""); +#if defined(__ANDROID__) && __ANDROID_API__ < 26 + _isvalid = true; +#else + if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { + _isvalid = true; + } +#endif +#endif +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.1 constructors and destructor + +GHC_INLINE path::path() noexcept {} + +GHC_INLINE path::path(const path& p) + : _path(p._path) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(path&& p) noexcept + : _path(std::move(p._path)) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(string_type&& source, format fmt) + : _path(std::move(source)) +{ + postprocess_path_with_format(fmt); +} + +#endif // GHC_EXPAND_IMPL + +#ifdef GHC_WITH_EXCEPTIONS +template +inline path::path(const Source& source, const std::locale& loc, format fmt) + : path(source, fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} + +template +inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} +#endif + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE path::~path() {} + +//----------------------------------------------------------------------------- +// 30.10.8.4.2 assignments + +GHC_INLINE path& path::operator=(const path& p) +{ + _path = p._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path&& p) noexcept +{ + _path = std::move(p._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path::string_type&& source) +{ + return assign(source); +} + +GHC_INLINE path& path::assign(path::string_type&& source) +{ + _path = std::move(source); + postprocess_path_with_format(native_format); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator=(const Source& source) +{ + return assign(source); +} + +template +inline path& path::assign(const Source& source) +{ +#ifdef GHC_USE_WCHAR_T + _path.assign(detail::toWChar(source)); +#else + _path.assign(detail::toUtf8(source)); +#endif + postprocess_path_with_format(native_format); + return *this; +} + +template <> +inline path& path::assign(const path& source) +{ + _path = source._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = source._prefixLength; +#endif + return *this; +} + +template +inline path& path::assign(InputIterator first, InputIterator last) +{ + _path.assign(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.3 appends + +GHC_INLINE path& path::operator/=(const path& p) +{ + if (p.empty()) { + // was: if ((!has_root_directory() && is_absolute()) || has_filename()) + if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { + _path += preferred_separator; + } + return *this; + } + if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { + assign(p); + return *this; + } + if (p.has_root_directory()) { + assign(root_name()); + } + else if ((!has_root_directory() && is_absolute()) || has_filename()) { + _path += preferred_separator; + } + auto iter = p.begin(); + bool first = true; + if (p.has_root_name()) { + ++iter; + } + while (iter != p.end()) { + if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { + _path += preferred_separator; + } + first = false; + _path += (*iter++).native(); + } + check_long_path(); + return *this; +} + +GHC_INLINE void path::append_name(const value_type* name) +{ + if (_path.empty()) { + this->operator/=(path(name)); + } + else { + if (_path.back() != path::preferred_separator) { + _path.push_back(path::preferred_separator); + } + _path += name; + check_long_path(); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator/=(const Source& source) +{ + return append(source); +} + +template +inline path& path::append(const Source& source) +{ + return this->operator/=(path(detail::toUtf8(source))); +} + +template <> +inline path& path::append(const path& p) +{ + return this->operator/=(p); +} + +template +inline path& path::append(InputIterator first, InputIterator last) +{ + std::basic_string::value_type> part(first, last); + return append(part); +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.4 concatenation + +GHC_INLINE path& path::operator+=(const path& x) +{ + return concat(x._path); +} + +GHC_INLINE path& path::operator+=(const string_type& x) +{ + return concat(x); +} + +#ifdef __cpp_lib_string_view +GHC_INLINE path& path::operator+=(std::basic_string_view x) +{ + return concat(x); +} +#endif + +GHC_INLINE path& path::operator+=(const value_type* x) +{ + return concat(string_type(x)); +} + +GHC_INLINE path& path::operator+=(value_type x) +{ +#ifdef GHC_OS_WINDOWS + if (x == generic_separator) { + x = preferred_separator; + } +#endif + if (_path.empty() || _path.back() != preferred_separator) { + _path += x; + } + check_long_path(); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path_from_string& path::operator+=(const Source& x) +{ + return concat(x); +} + +template +inline path::path_type_EcharT& path::operator+=(EcharT x) +{ + std::basic_string part(1, x); + concat(detail::toUtf8(part)); + return *this; +} + +template +inline path& path::concat(const Source& x) +{ + path p(x); + _path += p._path; + postprocess_path_with_format(native_format); + return *this; +} +template +inline path& path::concat(InputIterator first, InputIterator last) +{ + _path.append(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.5 modifiers +GHC_INLINE void path::clear() noexcept +{ + _path.clear(); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; +#endif +} + +GHC_INLINE path& path::make_preferred() +{ + // as this filesystem implementation only uses generic_format + // internally, this must be a no-op + return *this; +} + +GHC_INLINE path& path::remove_filename() +{ + if (has_filename()) { + _path.erase(_path.size() - filename()._path.size()); + } + return *this; +} + +GHC_INLINE path& path::replace_filename(const path& replacement) +{ + remove_filename(); + return append(replacement); +} + +GHC_INLINE path& path::replace_extension(const path& replacement) +{ + if (has_extension()) { + _path.erase(_path.size() - extension()._path.size()); + } + if (!replacement.empty() && replacement._path[0] != '.') { + _path += '.'; + } + return concat(replacement); +} + +GHC_INLINE void path::swap(path& rhs) noexcept +{ + _path.swap(rhs._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + std::swap(_prefixLength, rhs._prefixLength); +#endif +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.6, native format observers +GHC_INLINE const path::string_type& path::native() const noexcept +{ + return _path; +} + +GHC_INLINE const path::value_type* path::c_str() const noexcept +{ + return native().c_str(); +} + +GHC_INLINE path::operator path::string_type() const +{ + return native(); +} + +#endif // GHC_EXPAND_IMPL + +template +inline std::basic_string path::string(const Allocator& a) const +{ +#ifdef GHC_USE_WCHAR_T + return detail::fromWChar>(_path, a); +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} + +GHC_INLINE std::wstring path::wstring() const +{ +#ifdef GHC_USE_WCHAR_T + return native(); +#else + return detail::fromUtf8(native()); +#endif +} + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); +#else + return std::u8string(reinterpret_cast(c_str())); +#endif +} +#else +GHC_INLINE std::string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} +#endif + +GHC_INLINE std::u16string path::u16string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +GHC_INLINE std::u32string path::u32string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.4.7, generic format observers +template +inline std::basic_string path::generic_string(const Allocator& a) const +{ +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto result = detail::fromWChar, path::string_type>(_path, a); +#else + auto result = detail::fromUtf8>(_path, a); +#endif + for (auto& c : result) { + if (c == preferred_separator) { + c = generic_separator; + } + } + return result; +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::generic_string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} + +GHC_INLINE std::wstring path::generic_wstring() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} // namespace filesystem + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return std::u8string(reinterpret_cast(_path.c_str())); +#endif +} +#else +GHC_INLINE std::string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} +#endif + +GHC_INLINE std::u16string path::generic_u16string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +GHC_INLINE std::u32string path::generic_u32string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.8, compare +GHC_INLINE int path::compare(const path& p) const noexcept +{ +#ifdef LWG_2936_BEHAVIOUR + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); +#ifdef GHC_OS_WINDOWS + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); +#else + auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); +#endif + if (rnc) { + return rnc; + } + bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); + if (hrd1 != hrd2) { + return hrd1 ? 1 : -1; + } + if (hrd1) { + ++rnl1; + ++rnl2; + } + auto iter1 = _path.begin() + static_cast(rnl1); + auto iter2 = p._path.begin() + static_cast(rnl2); + while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { + ++iter1; + ++iter2; + } + if (iter1 == _path.end()) { + return iter2 == p._path.end() ? 0 : -1; + } + if (iter2 == p._path.end()) { + return 1; + } + if (*iter1 == preferred_separator) { + return -1; + } + if (*iter2 == preferred_separator) { + return 1; + } + return *iter1 < *iter2 ? -1 : 1; +#else // LWG_2936_BEHAVIOUR +#ifdef GHC_OS_WINDOWS + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); + if (rnc) { + return rnc; + } + return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); +#else + return _path.compare(p._path); +#endif +#endif +} + +GHC_INLINE int path::compare(const string_type& s) const +{ + return compare(path(s)); +} + +#ifdef __cpp_lib_string_view +GHC_INLINE int path::compare(std::basic_string_view s) const +{ + return compare(path(s)); +} +#endif + +GHC_INLINE int path::compare(const value_type* s) const +{ + return compare(path(s)); +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.9, decomposition +#ifdef GHC_OS_WINDOWS +GHC_INLINE void path::handle_prefixes() +{ +#if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; + if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { + if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { + _prefixLength = 4; + } + } +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +} +#endif + +GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept +{ +#ifdef GHC_OS_WINDOWS + if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { + return 2; + } +#endif + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { + impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); + if (pos == impl_string_type::npos) { + return _path.length(); + } + else { + return pos; + } + } + return 0; +} + +GHC_INLINE path path::root_name() const +{ + return path(_path.substr(_prefixLength, root_name_length()), native_format); +} + +GHC_INLINE path path::root_directory() const +{ + if (has_root_directory()) { + static const path _root_dir(std::string(1, preferred_separator), native_format); + return _root_dir; + } + return path(); +} + +GHC_INLINE path path::root_path() const +{ + return path(root_name().string() + root_directory().string(), native_format); +} + +GHC_INLINE path path::relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); +} + +GHC_INLINE path path::parent_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + if (rootPathLen < _path.length()) { + if (empty()) { + return path(); + } + else { + auto piter = end(); + auto iter = piter.decrement(_path.end()); + if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { + --iter; + } + return path(_path.begin(), iter, native_format); + } + } + else { + return *this; + } +} + +GHC_INLINE path path::filename() const +{ + return !has_relative_path() ? path() : path(*--end()); +} + +GHC_INLINE path path::stem() const +{ + impl_string_type fn = filename().native(); + if (fn != "." && fn != "..") { + impl_string_type::size_type pos = fn.rfind('.'); + if (pos != impl_string_type::npos && pos > 0) { + return path{fn.substr(0, pos), native_format}; + } + } + return path{fn, native_format}; +} + +GHC_INLINE path path::extension() const +{ + if (has_relative_path()) { + auto iter = end(); + const auto& fn = *--iter; + impl_string_type::size_type pos = fn._path.rfind('.'); + if (pos != std::string::npos && pos > 0) { + return path(fn._path.substr(pos), native_format); + } + } + return path(); +} + +#ifdef GHC_OS_WINDOWS +namespace detail { +GHC_INLINE bool has_executable_extension(const path& p) +{ + if (p.has_relative_path()) { + auto iter = p.end(); + const auto& fn = *--iter; + auto pos = fn._path.find_last_of('.'); + if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { + return false; + } + const path::value_type* ext = fn._path.c_str() + pos + 1; + if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { + return true; + } + } + return false; +} +} // namespace detail +#endif + +//----------------------------------------------------------------------------- +// 30.10.8.4.10, query +GHC_INLINE bool path::empty() const noexcept +{ + return _path.empty(); +} + +GHC_INLINE bool path::has_root_name() const +{ + return root_name_length() > 0; +} + +GHC_INLINE bool path::has_root_directory() const +{ + auto rootLen = _prefixLength + root_name_length(); + return (_path.length() > rootLen && _path[rootLen] == preferred_separator); +} + +GHC_INLINE bool path::has_root_path() const +{ + return has_root_name() || has_root_directory(); +} + +GHC_INLINE bool path::has_relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return rootPathLen < _path.length(); +} + +GHC_INLINE bool path::has_parent_path() const +{ + return !parent_path().empty(); +} + +GHC_INLINE bool path::has_filename() const +{ + return has_relative_path() && !filename().empty(); +} + +GHC_INLINE bool path::has_stem() const +{ + return !stem().empty(); +} + +GHC_INLINE bool path::has_extension() const +{ + return !extension().empty(); +} + +GHC_INLINE bool path::is_absolute() const +{ +#ifdef GHC_OS_WINDOWS + return has_root_name() && has_root_directory(); +#else + return has_root_directory(); +#endif +} + +GHC_INLINE bool path::is_relative() const +{ + return !is_absolute(); +} + +//----------------------------------------------------------------------------- +// 30.10.8.4.11, generation +GHC_INLINE path path::lexically_normal() const +{ + path dest; + bool lastDotDot = false; + for (string_type s : *this) { + if (s == ".") { + dest /= ""; + continue; + } + else if (s == ".." && !dest.empty()) { + auto root = root_path(); + if (dest == root) { + continue; + } + else if (*(--dest.end()) != "..") { + if (dest._path.back() == preferred_separator) { + dest._path.pop_back(); + } + dest.remove_filename(); + continue; + } + } + if (!(s.empty() && lastDotDot)) { + dest /= s; + } + lastDotDot = s == ".."; + } + if (dest.empty()) { + dest = "."; + } + return dest; +} + +GHC_INLINE path path::lexically_relative(const path& base) const +{ + if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { + return path(); + } + const_iterator a = begin(), b = base.begin(); + while (a != end() && b != base.end() && *a == *b) { + ++a; + ++b; + } + if (a == end() && b == base.end()) { + return path("."); + } + int count = 0; + for (const auto& element : input_iterator_range(b, base.end())) { + if (element != "." && element != "" && element != "..") { + ++count; + } + else if (element == "..") { + --count; + } + } + if (count < 0) { + return path(); + } + path result; + for (int i = 0; i < count; ++i) { + result /= ".."; + } + for (const auto& element : input_iterator_range(a, end())) { + result /= element; + } + return result; +} + +GHC_INLINE path path::lexically_proximate(const path& base) const +{ + path result = lexically_relative(base); + return result.empty() ? *this : result; +} + +//----------------------------------------------------------------------------- +// 30.10.8.5, iterators +GHC_INLINE path::iterator::iterator() {} + +GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) + : _first(p._path.begin()) + , _last(p._path.end()) + , _prefix(_first + static_cast(p._prefixLength)) + , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) + , _iter(pos) +{ + if(pos != _last) { + updateCurrent(); + } +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + bool fromStart = i == _first || i == _prefix; + if (i != _last) { + if (fromStart && i == _first && _prefix > _first) { + i = _prefix; + } + else if (*i++ == preferred_separator) { + // we can only sit on a slash if it is a network name or a root + if (i != _last && *i == preferred_separator) { + if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { + // leadind double slashes detected, treat this and the + // following until a slash as one unit + i = std::find(++i, _last, preferred_separator); + } + else { + // skip redundant slashes + while (i != _last && *i == preferred_separator) { + ++i; + } + } + } + } + else { + if (fromStart && i != _last && *i == ':') { + ++i; + } + else { + i = std::find(i, _last, preferred_separator); + } + } + } + return i; +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + if (i != _first) { + --i; + // if this is now the root slash or the trailing slash, we are done, + // else check for network name + if (i != _root && (pos != _last || *i != preferred_separator)) { +#ifdef GHC_OS_WINDOWS + static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); + i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); + if (i > _first && *i == ':') { + i++; + } +#else + i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); +#endif + // Now we have to check if this is a network name + if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { + i -= 2; + } + } + } + return i; +} + +GHC_INLINE void path::iterator::updateCurrent() +{ + if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { + _current.clear(); + } + else { + _current.assign(_iter, increment(_iter)); + } +} + +GHC_INLINE path::iterator& path::iterator::operator++() +{ + _iter = increment(_iter); + while (_iter != _last && // we didn't reach the end + _iter != _root && // this is not a root position + *_iter == preferred_separator && // we are on a separator + (_iter + 1) != _last // the slash is not the last char + ) { + ++_iter; + } + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator++(int) +{ + path::iterator i{*this}; + ++(*this); + return i; +} + +GHC_INLINE path::iterator& path::iterator::operator--() +{ + _iter = decrement(_iter); + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator--(int) +{ + auto i = *this; + --(*this); + return i; +} + +GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const +{ + return _iter == other._iter; +} + +GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const +{ + return _iter != other._iter; +} + +GHC_INLINE path::iterator::reference path::iterator::operator*() const +{ + return _current; +} + +GHC_INLINE path::iterator::pointer path::iterator::operator->() const +{ + return &_current; +} + +GHC_INLINE path::iterator path::begin() const +{ + return iterator(*this, _path.begin()); +} + +GHC_INLINE path::iterator path::end() const +{ + return iterator(*this, _path.end()); +} + +//----------------------------------------------------------------------------- +// 30.10.8.6, path non-member functions +GHC_INLINE void swap(path& lhs, path& rhs) noexcept +{ + swap(lhs._path, rhs._path); +} + +GHC_INLINE size_t hash_value(const path& p) noexcept +{ + return std::hash()(p.generic_string()); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <=> 0; +} +#endif + +GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) == 0; +} + +GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept +{ + return !(lhs == rhs); +} + +GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) < 0; +} + +GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <= 0; +} + +GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) > 0; +} + +GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) >= 0; +} + +GHC_INLINE path operator/(const path& lhs, const path& rhs) +{ + path result(lhs); + result /= rhs; + return result; +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.8.6.1 path inserter and extractor +template +inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) +{ + os << "\""; + auto ps = p.string(); + for (auto c : ps) { + if (c == '"' || c == '\\') { + os << '\\'; + } + os << c; + } + os << "\""; + return os; +} + +template +inline std::basic_istream& operator>>(std::basic_istream& is, path& p) +{ + std::basic_string tmp; + charT c; + is >> c; + if (c == '"') { + auto sf = is.flags(); + is >> std::noskipws; + while (is) { + auto c2 = is.get(); + if (is) { + if (c2 == '\\') { + c2 = is.get(); + if (is) { + tmp += static_cast(c2); + } + } + else if (c2 == '"') { + break; + } + else { + tmp += static_cast(c2); + } + } + } + if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { + is >> std::skipws; + } + p = path(tmp); + } + else { + is >> tmp; + p = path(static_cast(c) + tmp); + } + return is; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// 30.10.9 Class filesystem_error +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) +{ +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) + , _p2(p2) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } + if (!_p2.empty()) { + _what_arg += ", '" + _p2.string() + "'"; + } +} + +GHC_INLINE const path& filesystem_error::path1() const noexcept +{ + return _p1; +} + +GHC_INLINE const path& filesystem_error::path2() const noexcept +{ + return _p2; +} + +GHC_INLINE const char* filesystem_error::what() const noexcept +{ + return _what_arg.c_str(); +} + +//----------------------------------------------------------------------------- +// 30.10.15, filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path absolute(const path& p) +{ + std::error_code ec; + path result = absolute(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path absolute(const path& p, std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (p.empty()) { + return absolute(current_path(ec), ec) / ""; + } + ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); + if (s2 && s2 < size) { + path result = path(std::wstring(buf.data(), s2)); + if (p.filename() == ".") { + result /= "."; + } + return result; + } + } + ec = detail::make_system_error(); + return path(); +#else + path base = current_path(ec); + if (!ec) { + if (p.empty()) { + return base / p; + } + if (p.has_root_name()) { + if (p.has_root_directory()) { + return p; + } + else { + return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); + } + } + else { + if (p.has_root_directory()) { + return base.root_name() / p; + } + else { + return base / p; + } + } + } + ec = detail::make_system_error(); + return path(); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path canonical(const path& p) +{ + std::error_code ec; + auto result = canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path canonical(const path& p, std::error_code& ec) +{ + if (p.empty()) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + path work = p.is_absolute() ? p : absolute(p, ec); + path result; + + auto fs = status(work, ec); + if (ec) { + return path(); + } + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + bool redo; + do { + auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); + redo = false; + result.clear(); + for (auto pe : work) { + if (pe.empty() || pe == ".") { + continue; + } + else if (pe == "..") { + result = result.parent_path(); + continue; + } + else if ((result / pe).string().length() <= rootPathLen) { + result /= pe; + continue; + } + auto sls = symlink_status(result / pe, ec); + if (ec) { + return path(); + } + if (is_symlink(sls)) { + redo = true; + auto target = read_symlink(result / pe, ec); + if (ec) { + return path(); + } + if (target.is_absolute()) { + result = target; + continue; + } + else { + result /= target; + continue; + } + } + else { + result /= pe; + } + } + work = result; + } while (redo); + ec.clear(); + return result; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy(const path& from, const path& to) +{ + copy(from, to, copy_options::none); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options) +{ + std::error_code ec; + copy(from, to, options, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept +{ + copy(from, to, copy_options::none, ec); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tec; + file_status fs_from, fs_to; + ec.clear(); + if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_from = symlink_status(from, ec); + } + else { + fs_from = status(from, ec); + } + if (!exists(fs_from)) { + if (!ec) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return; + } + if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_to = symlink_status(to, tec); + } + else { + fs_to = status(to, tec); + } + if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + else if (is_symlink(fs_from)) { + if ((options & copy_options::skip_symlinks) == copy_options::none) { + if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { + copy_symlink(from, to, ec); + } + else { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + } + } + else if (is_regular_file(fs_from)) { + if ((options & copy_options::directories_only) == copy_options::none) { + if ((options & copy_options::create_symlinks) != copy_options::none) { + create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); + } +#ifndef GHC_OS_WEB + else if ((options & copy_options::create_hard_links) != copy_options::none) { + create_hard_link(from, to, ec); + } +#endif + else if (is_directory(fs_to)) { + copy_file(from, to / from.filename(), options, ec); + } + else { + copy_file(from, to, options, ec); + } + } + } +#ifdef LWG_2682_BEHAVIOUR + else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { + ec = detail::make_error_code(detail::portable_error::is_a_directory); + } +#endif + else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { + if (!exists(fs_to)) { + create_directory(to, from, ec); + if (ec) { + return; + } + } + for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { + if (!ec) { + copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); + } + if (ec) { + return; + } + } + } + return; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool copy_file(const path& from, const path& to) +{ + return copy_file(from, to, copy_options::none); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) +{ + std::error_code ec; + auto result = copy_file(from, to, option, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } + return result; +} +#endif + +GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept +{ + return copy_file(from, to, copy_options::none, ec); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tecf, tect; + auto sf = status(from, tecf); + auto st = status(to, tect); + bool overwrite = false; + ec.clear(); + if (!is_regular_file(sf)) { + ec = tecf; + return false; + } + if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) { + ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); + return false; + } + if (exists(st)) { + if ((options & copy_options::update_existing) == copy_options::update_existing) { + auto from_time = last_write_time(from, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + auto to_time = last_write_time(to, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + if (from_time <= to_time) { + return false; + } + } + overwrite = true; + } +#ifdef GHC_OS_WINDOWS + if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { + ec = detail::make_system_error(); + return false; + } + return true; +#else + std::vector buffer(16384, '\0'); + int in = -1, out = -1; + if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { + ec = detail::make_system_error(); + return false; + } + int mode = O_CREAT | O_WRONLY | O_TRUNC; + if (!overwrite) { + mode |= O_EXCL; + } + if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { + ec = detail::make_system_error(); + ::close(in); + return false; + } + ssize_t br, bw; + while ((br = ::read(in, buffer.data(), buffer.size())) > 0) { + ssize_t offset = 0; + do { + if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { + br -= bw; + offset += bw; + } + else if (bw < 0) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + } while (br); + } + ::close(in); + ::close(out); + return true; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) +{ + std::error_code ec; + copy_symlink(existing_symlink, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); + } +} +#endif + +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept +{ + ec.clear(); + auto to = read_symlink(existing_symlink, ec); + if (!ec) { + if (exists(to, ec) && is_directory(to, ec)) { + create_directory_symlink(to, new_symlink, ec); + } + else { + create_symlink(to, new_symlink, ec); + } + } +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directories(const path& p) +{ + std::error_code ec; + auto result = create_directories(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept +{ + path current; + ec.clear(); + bool didCreate = false; + for (path::string_type part : p) { + current /= part; + if (current != p.root_name() && current != p.root_path()) { + std::error_code tec; + auto fs = status(current, tec); + if (tec && fs.type() != file_type::not_found) { + ec = tec; + return false; + } + if (!exists(fs)) { + create_directory(current, ec); + if (ec) { + std::error_code tmp_ec; + if (is_directory(current, tmp_ec)) { + ec.clear(); + } + else { + return false; + } + } + didCreate = true; + } +#ifndef LWG_2935_BEHAVIOUR + else if (!is_directory(fs)) { + ec = detail::make_error_code(detail::portable_error::exists); + return false; + } +#endif + } + } + return didCreate; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p) +{ + std::error_code ec; + auto result = create_directory(p, path(), ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept +{ + return create_directory(p, path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p, const path& attributes) +{ + std::error_code ec; + auto result = create_directory(p, attributes, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept +{ + std::error_code tec; + ec.clear(); + auto fs = status(p, tec); +#ifdef LWG_2935_BEHAVIOUR + if (status_known(fs) && exists(fs)) { + return false; + } +#else + if (status_known(fs) && exists(fs) && is_directory(fs)) { + return false; + } +#endif +#ifdef GHC_OS_WINDOWS + if (!attributes.empty()) { + if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } + } + else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } +#else + ::mode_t attribs = static_cast(perms::all); + if (!attributes.empty()) { + struct ::stat fileStat; + if (::stat(attributes.c_str(), &fileStat) != 0) { + ec = detail::make_system_error(); + return false; + } + attribs = fileStat.st_mode; + } + if (::mkdir(p.c_str(), attribs) != 0) { + ec = detail::make_system_error(); + return false; + } +#endif + return true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_directory_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, true, ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) +{ + std::error_code ec; + create_hard_link(to, new_hard_link, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); + } +} +#endif + +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept +{ + detail::create_hardlink(to, new_hard_link, ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, false, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path current_path() +{ + std::error_code ec; + auto result = current_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path current_path(std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + DWORD pathlen = ::GetCurrentDirectoryW(0, 0); + std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); + if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer.get()), path::native_format); +#else + size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); + std::unique_ptr buffer(new char[pathlen + 1]); + if (::getcwd(buffer.get(), pathlen) == nullptr) { + ec = detail::make_system_error(); + return path(); + } + return path(buffer.get()); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void current_path(const path& p) +{ + std::error_code ec; + current_path(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { + ec = detail::make_system_error(); + } +#else + if (::chdir(p.string().c_str()) == -1) { + ec = detail::make_system_error(); + } +#endif +} + +GHC_INLINE bool exists(file_status s) noexcept +{ + return status_known(s) && s.type() != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool exists(const path& p) +{ + return exists(status(p)); +} +#endif + +GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept +{ + file_status s = status(p, ec); + if (status_known(s)) { + ec.clear(); + } + return exists(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool equivalent(const path& p1, const path& p2) +{ + std::error_code ec; + bool result = equivalent(p1, p2, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); + } + return result; +} +#endif + +GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + std::shared_ptr file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + auto e1 = ::GetLastError(); + std::shared_ptr file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + if (file1.get() == INVALID_HANDLE_VALUE || file2.get() == INVALID_HANDLE_VALUE) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); +#else + if (file1 == file2) { + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); + } +#endif + return false; + } + BY_HANDLE_FILE_INFORMATION inf1, inf2; + if (!::GetFileInformationByHandle(file1.get(), &inf1)) { + ec = detail::make_system_error(); + return false; + } + if (!::GetFileInformationByHandle(file2.get(), &inf2)) { + ec = detail::make_system_error(); + return false; + } + return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && + inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; +#else + struct ::stat s1, s2; + auto rc1 = ::stat(p1.c_str(), &s1); + auto e1 = errno; + auto rc2 = ::stat(p2.c_str(), &s2); + if (rc1 || rc2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : errno); +#else + if (rc1 && rc2) { + ec = detail::make_system_error(e1 ? e1 : errno); + } +#endif + return false; + } + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t file_size(const path& p) +{ + std::error_code ec; + auto result = file_size(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; +#else + struct ::stat fileStat; + if (::stat(p.c_str(), &fileStat) == -1) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(fileStat.st_size); +#endif +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t hard_link_count(const path& p) +{ + std::error_code ec; + auto result = hard_link_count(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + uintmax_t result = static_cast(-1); + std::shared_ptr file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0), CloseHandle); + BY_HANDLE_FILE_INFORMATION inf; + if (file.get() == INVALID_HANDLE_VALUE) { + ec = detail::make_system_error(); + } + else { + if (!::GetFileInformationByHandle(file.get(), &inf)) { + ec = detail::make_system_error(); + } + else { + result = inf.nNumberOfLinks; + } + } + return result; +#else + uintmax_t result = 0; + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return ec ? static_cast(-1) : result; +#endif +} +#endif + +GHC_INLINE bool is_block_file(file_status s) noexcept +{ + return s.type() == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_block_file(const path& p) +{ + return is_block_file(status(p)); +} +#endif + +GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept +{ + return is_block_file(status(p, ec)); +} + +GHC_INLINE bool is_character_file(file_status s) noexcept +{ + return s.type() == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_character_file(const path& p) +{ + return is_character_file(status(p)); +} +#endif + +GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept +{ + return is_character_file(status(p, ec)); +} + +GHC_INLINE bool is_directory(file_status s) noexcept +{ + return s.type() == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_directory(const path& p) +{ + return is_directory(status(p)); +} +#endif + +GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept +{ + return is_directory(status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_empty(const path& p) +{ + if (is_directory(p)) { + return directory_iterator(p) == directory_iterator(); + } + else { + return file_size(p) == 0; + } +} +#endif + +GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept +{ + auto fs = status(p, ec); + if (ec) { + return false; + } + if (is_directory(fs)) { + directory_iterator iter(p, ec); + if (ec) { + return false; + } + return iter == directory_iterator(); + } + else { + auto sz = file_size(p, ec); + if (ec) { + return false; + } + return sz == 0; + } +} + +GHC_INLINE bool is_fifo(file_status s) noexcept +{ + return s.type() == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_fifo(const path& p) +{ + return is_fifo(status(p)); +} +#endif + +GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept +{ + return is_fifo(status(p, ec)); +} + +GHC_INLINE bool is_other(file_status s) noexcept +{ + return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_other(const path& p) +{ + return is_other(status(p)); +} +#endif + +GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept +{ + return is_other(status(p, ec)); +} + +GHC_INLINE bool is_regular_file(file_status s) noexcept +{ + return s.type() == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_regular_file(const path& p) +{ + return is_regular_file(status(p)); +} +#endif + +GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept +{ + return is_regular_file(status(p, ec)); +} + +GHC_INLINE bool is_socket(file_status s) noexcept +{ + return s.type() == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_socket(const path& p) +{ + return is_socket(status(p)); +} +#endif + +GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept +{ + return is_socket(status(p, ec)); +} + +GHC_INLINE bool is_symlink(file_status s) noexcept +{ + return s.type() == file_type::symlink; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_symlink(const path& p) +{ + return is_symlink(symlink_status(p)); +} +#endif + +GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept +{ + return is_symlink(symlink_status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type last_write_time(const path& p) +{ + std::error_code ec; + auto result = last_write_time(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept +{ + time_t result = 0; + ec.clear(); + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); + return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void last_write_time(const path& p, file_time_type new_time) +{ + std::error_code ec; + last_write_time(p, new_time, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept +{ + ec.clear(); + auto d = new_time.time_since_epoch(); +#ifdef GHC_OS_WINDOWS + std::shared_ptr file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL), ::CloseHandle); + FILETIME ft; + auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; + ft.dwLowDateTime = static_cast(tt); + ft.dwHighDateTime = static_cast(tt >> 32); + if (!::SetFileTime(file.get(), 0, 0, &ft)) { + ec = detail::make_system_error(); + } +#elif defined(GHC_OS_MACOS) +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 + struct ::stat fs; + if (::stat(p.c_str(), &fs) == 0) { + struct ::timeval tv[2]; + tv[0].tv_sec = fs.st_atimespec.tv_sec; + tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); + tv[1].tv_sec = std::chrono::duration_cast(d).count(); + tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); + if (::utimes(p.c_str(), tv) == 0) { + return; + } + } + ec = detail::make_system_error(); + return; +#else + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = std::chrono::duration_cast(d).count(); + times[1].tv_nsec = 0; // std::chrono::duration_cast(d).count() % 1000000000; + if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { + ec = detail::make_system_error(); + } + return; +#endif +#endif +#else +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1l << 30) - 2l) +#endif + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); + times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 12 + if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#else + if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#endif + ec = detail::make_system_error(); + } + return; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) +{ + std::error_code ec; + permissions(p, prms, opts, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept +{ + permissions(p, prms, perm_options::replace, ec); +} + +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept +{ + if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return; + } + auto fs = symlink_status(p, ec); + if ((opts & perm_options::replace) != perm_options::replace) { + if ((opts & perm_options::add) == perm_options::add) { + prms = fs.permissions() | prms; + } + else { + prms = fs.permissions() & ~prms; + } + } +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); + if (oldAttr != INVALID_FILE_ATTRIBUTES) { + DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; + if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { + return; + } + } + ec = detail::make_system_error(); +#else + int mode = 0; + if ((prms & perms::owner_read) == perms::owner_read) { + mode |= _S_IREAD; + } + if ((prms & perms::owner_write) == perms::owner_write) { + mode |= _S_IWRITE; + } + if (::_wchmod(p.wstring().c_str(), mode) != 0) { + ec = detail::make_system_error(); + } +#endif +#else + if ((opts & perm_options::nofollow) != perm_options::nofollow) { + if (::chmod(p.c_str(), static_cast(prms)) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, std::error_code& ec) +{ + auto cp = current_path(ec); + if (!ec) { + return proximate(p, cp, ec); + } + return path(); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); +} +#endif + +GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path read_symlink(const path& p) +{ + std::error_code ec; + auto result = read_symlink(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path read_symlink(const path& p, std::error_code& ec) +{ + file_status fs = symlink_status(p, ec); + if (fs.type() != file_type::symlink) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return path(); + } + auto result = detail::resolveSymlink(p, ec); + return ec ? path() : result; +} + +GHC_INLINE path relative(const path& p, std::error_code& ec) +{ + return relative(p, current_path(ec), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path relative(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_relative(weakly_canonical(base)); +} +#endif + +GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool remove(const path& p) +{ + std::error_code ec; + auto result = remove(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto cstr = p.c_str(); +#else + std::wstring np = detail::fromUtf8(p.u8string()); + auto cstr = np.c_str(); +#endif + DWORD attr = GetFileAttributesW(cstr); + if (attr == INVALID_FILE_ATTRIBUTES) { + auto error = ::GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { + return false; + } + ec = detail::make_system_error(error); + } + if (!ec) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + if (!RemoveDirectoryW(cstr)) { + ec = detail::make_system_error(); + } + } + else { + if (!DeleteFileW(cstr)) { + ec = detail::make_system_error(); + } + } + } +#else + if (::remove(p.c_str()) == -1) { + auto error = errno; + if (error == ENOENT) { + return false; + } + ec = detail::make_system_error(); + } +#endif + return ec ? false : true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t remove_all(const path& p) +{ + std::error_code ec; + auto result = remove_all(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); + uintmax_t count = 0; + if (p == "/") { + ec = detail::make_error_code(detail::portable_error::not_supported); + return static_cast(-1); + } + std::error_code tec; + auto fs = status(p, tec); + if (exists(fs) && is_directory(fs)) { + for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { + if (ec) { + break; + } + bool is_symlink_result = iter->is_symlink(ec); + if (ec) + return static_cast(-1); + bool is_directory_result = iter->is_directory(ec); + if (ec) + return static_cast(-1); + if (!is_symlink_result && is_directory_result) { + count += remove_all(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + } + else { + remove(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + ++count; + } + } + } + if (!ec) { + if (remove(p, ec)) { + ++count; + } + } + if (ec) { + return static_cast(-1); + } + return count; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void rename(const path& from, const path& to) +{ + std::error_code ec; + rename(from, to, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (from != to) { + if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { + ec = detail::make_system_error(); + } + } +#else + if (from != to) { + if (::rename(from.c_str(), to.c_str()) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void resize_file(const path& p, uintmax_t size) +{ + std::error_code ec; + resize_file(p, size, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + LARGE_INTEGER lisize; + lisize.QuadPart = static_cast(size); + if (lisize.QuadPart < 0) { +#ifdef ERROR_FILE_TOO_LARGE + ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); +#else + ec = detail::make_system_error(223); +#endif + return; + } + std::shared_ptr file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL), CloseHandle); + if (file.get() == INVALID_HANDLE_VALUE) { + ec = detail::make_system_error(); + } + else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { + ec = detail::make_system_error(); + } +#else + if (::truncate(p.c_str(), static_cast(size)) != 0) { + ec = detail::make_system_error(); + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE space_info space(const path& p) +{ + std::error_code ec; + auto result = space(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; + if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; +#else + struct ::statvfs sfs; + if (::statvfs(p.c_str(), &sfs) != 0) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(sfs.f_blocks * sfs.f_frsize), static_cast(sfs.f_bfree * sfs.f_frsize), static_cast(sfs.f_bavail * sfs.f_frsize)}; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status status(const path& p) +{ + std::error_code ec; + auto result = status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept +{ + return detail::status_ex(p, ec); +} + +GHC_INLINE bool status_known(file_status s) noexcept +{ + return s.type() != file_type::none; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status symlink_status(const path& p) +{ + std::error_code ec; + auto result = symlink_status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept +{ + return detail::symlink_status_ex(p, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path temp_directory_path() +{ + std::error_code ec; + path result = temp_directory_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + wchar_t buffer[512]; + auto rc = GetTempPathW(511, buffer); + if (!rc || rc > 511) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer)); +#else + static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; + const char* temp_path = nullptr; + for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { + temp_path = std::getenv(*temp_name); + if (temp_path) { + return path(temp_path); + } + } + return path("/tmp"); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path weakly_canonical(const path& p) +{ + std::error_code ec; + auto result = weakly_canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept +{ + path result; + ec.clear(); + bool scan = true; + for (auto pe : p) { + if (scan) { + std::error_code tec; + if (exists(result / pe, tec)) { + result /= pe; + } + else { + if (ec) { + return path(); + } + scan = false; + if (!result.empty()) { + result = canonical(result, ec) / pe; + if (ec) { + break; + } + } + else { + result /= pe; + } + } + } + else { + result /= pe; + } + } + if (scan) { + if (!result.empty()) { + result = canonical(result, ec); + } + } + return ec ? path() : result.lexically_normal(); +} + +//----------------------------------------------------------------------------- +// 30.10.11 class file_status +// 30.10.11.1 constructors and destructor +GHC_INLINE file_status::file_status() noexcept + : file_status(file_type::none) +{ +} + +GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept + : _type(ft) + , _perms(prms) +{ +} + +GHC_INLINE file_status::file_status(const file_status& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::file_status(file_status&& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::~file_status() {} + +// assignments: +GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +// 30.10.11.3 modifiers +GHC_INLINE void file_status::type(file_type ft) noexcept +{ + _type = ft; +} + +GHC_INLINE void file_status::permissions(perms prms) noexcept +{ + _perms = prms; +} + +// 30.10.11.2 observers +GHC_INLINE file_type file_status::type() const noexcept +{ + return _type; +} + +GHC_INLINE perms file_status::permissions() const noexcept +{ + return _perms; +} + +//----------------------------------------------------------------------------- +// 30.10.12 class directory_entry +// 30.10.12.1 constructors and destructor +// directory_entry::directory_entry() noexcept = default; +// directory_entry::directory_entry(const directory_entry&) = default; +// directory_entry::directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) + : _path(p) + , _file_size(0) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(0) +#endif + , _last_write_time(0) +{ + refresh(); +} +#endif + +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) + : _path(p) + , _file_size(0) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(0) +#endif + , _last_write_time(0) +{ + refresh(ec); +} + +GHC_INLINE directory_entry::~directory_entry() {} + +// assignments: +// directory_entry& directory_entry::operator=(const directory_entry&) = default; +// directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; + +// 30.10.12.2 directory_entry modifiers +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::assign(const filesystem::path& p) +{ + _path = p; + refresh(); +} +#endif + +GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) +{ + _path = p; + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) +{ + _path.replace_filename(p); + refresh(); +} +#endif + +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) +{ + _path.replace_filename(p); + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::refresh() +{ + std::error_code ec; + refresh(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); + } +} +#endif + +GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept +{ +#ifdef GHC_OS_WINDOWS + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); +#else + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); +#endif +} + +// 30.10.12.3 directory_entry observers +GHC_INLINE const filesystem::path& directory_entry::path() const noexcept +{ + return _path; +} + +GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept +{ + return _path; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::exists() const +{ + return filesystem::exists(status()); +} +#endif + +GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept +{ + return filesystem::exists(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_block_file() const +{ + return filesystem::is_block_file(status()); +} +#endif +GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept +{ + return filesystem::is_block_file(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_character_file() const +{ + return filesystem::is_character_file(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept +{ + return filesystem::is_character_file(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_directory() const +{ + return filesystem::is_directory(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept +{ + return filesystem::is_directory(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_fifo() const +{ + return filesystem::is_fifo(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept +{ + return filesystem::is_fifo(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_other() const +{ + return filesystem::is_other(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept +{ + return filesystem::is_other(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_regular_file() const +{ + return filesystem::is_regular_file(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept +{ + return filesystem::is_regular_file(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_socket() const +{ + return filesystem::is_socket(status()); +} +#endif + +GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept +{ + return filesystem::is_socket(status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_symlink() const +{ + return filesystem::is_symlink(symlink_status()); +} +#endif + +GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept +{ + return filesystem::is_symlink(symlink_status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::file_size() const +{ + if (_status.type() != file_type::none) { + return _file_size; + } + return filesystem::file_size(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _file_size; + } + return filesystem::file_size(path(), ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::hard_link_count() const +{ +#ifndef GHC_OS_WINDOWS + if (_status.type() != file_type::none) { + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept +{ +#ifndef GHC_OS_WINDOWS + if (_status.type() != file_type::none) { + ec.clear(); + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path(), ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type directory_entry::last_write_time() const +{ + if (_status.type() != file_type::none) { + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path()); +} +#endif + +GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::status() const +{ + if (_status.type() != file_type::none) { + return _status; + } + return filesystem::status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _status; + } + return filesystem::status(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::symlink_status() const +{ + if (_symlink_status.type() != file_type::none) { + return _symlink_status; + } + return filesystem::symlink_status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none) { + ec.clear(); + return _symlink_status; + } + return filesystem::symlink_status(path(), ec); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept +{ + return _path <=> rhs._path; +} +#endif + +GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept +{ + return _path < rhs._path; +} + +GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept +{ + return _path == rhs._path; +} + +GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept +{ + return _path != rhs._path; +} + +GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept +{ + return _path <= rhs._path; +} + +GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept +{ + return _path > rhs._path; +} + +GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept +{ + return _path >= rhs._path; +} + +//----------------------------------------------------------------------------- +// 30.10.13 class directory_iterator + +#ifdef GHC_OS_WINDOWS +class directory_iterator::impl +{ +public: + impl(const path& p, directory_options options) + : _base(p) + , _options(options) + , _dirHandle(INVALID_HANDLE_VALUE) + { + if (!_base.empty()) { + ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); + if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { + if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { + increment(_ec); + } + else { + _current = _base / std::wstring(_findData.cFileName); + copyToDirEntry(_ec); + } + } + else { + auto error = ::GetLastError(); + _base = filesystem::path(); + if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + } + } + void increment(std::error_code& ec) + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + do { + if (FindNextFileW(_dirHandle, &_findData)) { + _current = _base; +#ifdef GHC_USE_WCHAR_T + _current.append_name(_findData.cFileName); +#else +#ifdef GHC_RAISE_UNICODE_ERRORS + try { + _current.append_name(detail::toUtf8(_findData.cFileName).c_str()); + } + catch (filesystem_error& fe) { + ec = fe.code(); + return; + } +#else + _current.append_name(detail::toUtf8(_findData.cFileName).c_str()); +#endif +#endif + copyToDirEntry(ec); + } + else { + auto err = ::GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + _ec = ec = detail::make_system_error(err); + } + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + _current = filesystem::path(); + break; + } + } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); + } + else { + ec = _ec; + } + } + void copyToDirEntry(std::error_code& ec) + { + _dir_entry._path = _current; + if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + _dir_entry._status = detail::status_ex(_current, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); + } + else { + _dir_entry._status = detail::status_from_INFO(_current, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); + _dir_entry._symlink_status = _dir_entry._status; + } + if (ec) { + if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { + ec.clear(); + } + else { + _dir_entry._file_size = static_cast(-1); + _dir_entry._last_write_time = 0; + } + } + } + path _base; + directory_options _options; + WIN32_FIND_DATAW _findData; + HANDLE _dirHandle; + path _current; + directory_entry _dir_entry; + std::error_code _ec; +}; +#else +// POSIX implementation +class directory_iterator::impl +{ +public: + impl(const path& path, directory_options options) + : _base(path) + , _options(options) + , _dir(nullptr) + , _entry(nullptr) + { + if (!path.empty()) { + _dir = ::opendir(path.native().c_str()); + if (!_dir) { + auto error = errno; + _base = filesystem::path(); + if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + else { + increment(_ec); + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dir) { + ::closedir(_dir); + } + } + void increment(std::error_code& ec) + { + if (_dir) { + bool skip; + do { + skip = false; + errno = 0; + _entry = ::readdir(_dir); + if (_entry) { + _current = _base; + _current.append_name(_entry->d_name); + _dir_entry = directory_entry(_current, ec); + if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { + ec.clear(); + skip = true; + } + } + else { + ::closedir(_dir); + _dir = nullptr; + _current = path(); + if (errno) { + ec = detail::make_system_error(); + } + break; + } + } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); + } + } + path _base; + directory_options _options; + path _current; + DIR* _dir; + struct ::dirent* _entry; + directory_entry _dir_entry; + std::error_code _ec; +}; +#endif + +// 30.10.13.1 member functions +GHC_INLINE directory_iterator::directory_iterator() noexcept + : _impl(new impl(path(), directory_options::none)) +{ +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator::directory_iterator(const path& p) + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } + _impl->_ec.clear(); +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } +} +#endif + +GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE directory_iterator::~directory_iterator() {} + +GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +GHC_INLINE const directory_entry& directory_iterator::operator*() const +{ + return _impl->_dir_entry; +} + +GHC_INLINE const directory_entry* directory_iterator::operator->() const +{ + return &_impl->_dir_entry; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator& directory_iterator::operator++() +{ + std::error_code ec; + _impl->increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_current, ec); + } + return *this; +} +#endif + +GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept +{ + _impl->increment(ec); + return *this; +} + +GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const +{ + return _impl->_current == rhs._impl->_current; +} + +GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const +{ + return _impl->_current != rhs._impl->_current; +} + +// 30.10.13.2 directory_iterator non-member functions + +GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE directory_iterator end(const directory_iterator&) noexcept +{ + return directory_iterator(); +} + +//----------------------------------------------------------------------------- +// 30.10.14 class recursive_directory_iterator + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator()); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options)); +} +#endif + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} + +// 30.10.14.1 observers +GHC_INLINE directory_options recursive_directory_iterator::options() const +{ + return _impl->_options; +} + +GHC_INLINE int recursive_directory_iterator::depth() const +{ + return static_cast(_impl->_dir_iter_stack.size() - 1); +} + +GHC_INLINE bool recursive_directory_iterator::recursion_pending() const +{ + return _impl->_recursion_pending; +} + +GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const +{ + return *(_impl->_dir_iter_stack.top()); +} + +GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const +{ + return &(*(_impl->_dir_iter_stack.top())); +} + +// 30.10.14.1 modifiers recursive_directory_iterator& +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() +{ + std::error_code ec; + increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } + return *this; +} +#endif + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept +{ + auto status = (*this)->status(ec); + if (ec) + return *this; + auto symlink_status = (*this)->symlink_status(ec); + if (ec) + return *this; + if (recursion_pending() && is_directory(status) && (!is_symlink(symlink_status) || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { + _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); + } + else { + _impl->_dir_iter_stack.top().increment(ec); + } + if (!ec) { + while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } + } + else if (!_impl->_dir_iter_stack.empty()) { + _impl->_dir_iter_stack.pop(); + } + _impl->_recursion_pending = true; + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void recursive_directory_iterator::pop() +{ + std::error_code ec; + pop(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } +} +#endif + +GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) +{ + if (depth() == 0) { + *this = recursive_directory_iterator(); + } + else { + do { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); + } +} + +GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() +{ + _impl->_recursion_pending = false; +} + +// other members as required by 27.2.3, input iterators +GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); +} + +GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); +} + +// 30.10.14.2 directory_iterator non-member functions +GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept +{ + return recursive_directory_iterator(); +} + +#endif // GHC_EXPAND_IMPL + +} // namespace filesystem +} // namespace ghc + +// cleanup some macros +#undef GHC_INLINE +#undef GHC_EXPAND_IMPL + +#endif // GHC_FILESYSTEM_H