// Filesystem operation utilities -*- C++ -*- // Copyright (C) 2014-2023 Free Software Foundation, Inc. // // This file is part of the GNU ISO C++ Library. This library is free // software; you can redistribute it and/or modify it under the // terms of the GNU General Public License as published by the // Free Software Foundation; either version 3, or (at your option) // any later version. // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // Under Section 7 of GPL version 3, you are granted additional // permissions described in the GCC Runtime Library Exception, version // 3.1, as published by the Free Software Foundation. // You should have received a copy of the GNU General Public License and // a copy of the GCC Runtime Library Exception along with this program; // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see // . #ifndef _GLIBCXX_OPS_COMMON_H #define _GLIBCXX_OPS_COMMON_H 1 #include #include // std::__exchange #ifdef _GLIBCXX_HAVE_UNISTD_H # include # ifdef _GLIBCXX_HAVE_FCNTL_H # include // AT_FDCWD, O_TRUNC etc. # endif # if defined(_GLIBCXX_HAVE_SYS_STAT_H) && defined(_GLIBCXX_HAVE_SYS_TYPES_H) # include # include // mkdir, chmod # endif #endif #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H # include // utime #endif #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS # include #endif #ifdef NEED_DO_COPY_FILE # include # include # ifdef _GLIBCXX_USE_SENDFILE # include // sendfile # endif #endif namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION // Get the last OS error (for POSIX this is just errno). inline error_code __last_system_error() noexcept { #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS // N.B. use error_code::default_error_condition() to convert to generic. return {(int)::GetLastError(), std::system_category()}; #else return {errno, std::generic_category()}; #endif } // Get an error code indicating unsupported functionality. // // This should be used when a function is unable to behave as specified // due to an incomplete or partial implementation, e.g. // filesystem::equivalent(a, b) if is_other(a) && is_other(b) is true. // // Use errc::function_not_supported for functions that are entirely // unimplemented, e.g. create_symlink on Windows. // // Use errc::invalid_argument for requests to perform operations outside // the spec, e.g. trying to copy a directory using filesystem::copy_file. inline error_code __unsupported() noexcept { #if defined __AVR__ // avr-libc defines ENOTSUP and EOPNOTSUPP but with nonsense values. // ENOSYS is defined though, so use an error_code corresponding to that. // This contradicts the comment above, but we don't have much choice. return std::make_error_code(std::errc::function_not_supported); #elif defined ENOTSUP return std::make_error_code(std::errc::not_supported); #elif defined EOPNOTSUPP // This is supposed to be for socket operations return std::make_error_code(std::errc::operation_not_supported); #else return std::make_error_code(std::errc::invalid_argument); #endif } namespace filesystem { namespace __gnu_posix { #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS // Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*. inline int open(const wchar_t* path, int flags) { return ::_wopen(path, flags); } inline int open(const wchar_t* path, int flags, int mode) { return ::_wopen(path, flags, mode); } inline int close(int fd) { return ::_close(fd); } typedef struct ::__stat64 stat_type; inline int stat(const wchar_t* path, stat_type* buffer) { return ::_wstat64(path, buffer); } inline int lstat(const wchar_t* path, stat_type* buffer) { // FIXME: symlinks not currently supported return stat(path, buffer); } using ::mode_t; inline int chmod(const wchar_t* path, mode_t mode) { return ::_wchmod(path, mode); } #define _GLIBCXX_USE_CHMOD 1 inline int mkdir(const wchar_t* path, mode_t) { return ::_wmkdir(path); } #define _GLIBCXX_USE_MKDIR 1 inline wchar_t* getcwd(wchar_t* buf, size_t size) { return ::_wgetcwd(buf, size > (size_t)INT_MAX ? INT_MAX : (int)size); } #define _GLIBCXX_USE_GETCWD 1 inline int chdir(const wchar_t* path) { return ::_wchdir(path); } #define _GLIBCXX_USE_CHDIR 1 #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H using utimbuf = _utimbuf; inline int utime(const wchar_t* path, utimbuf* times) { return ::_wutime(path, times); } #endif inline int rename(const wchar_t* oldname, const wchar_t* newname) { if (MoveFileExW(oldname, newname, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) return 0; if (GetLastError() == ERROR_ACCESS_DENIED) errno = EACCES; else errno = EIO; return -1; } using off_t = _off64_t; inline int truncate(const wchar_t* path, _off64_t length) { const int fd = ::_wopen(path, _O_BINARY|_O_RDWR); if (fd == -1) return fd; const int ret = ::ftruncate64(fd, length); int err; ::_get_errno(&err); ::_close(fd); ::_set_errno(err); return ret; } using char_type = wchar_t; #elif defined _GLIBCXX_HAVE_UNISTD_H && ! defined __AVR__ using ::open; using ::close; # ifdef _GLIBCXX_HAVE_SYS_STAT_H typedef struct ::stat stat_type; using ::stat; # ifdef _GLIBCXX_USE_LSTAT using ::lstat; # else inline int lstat(const char* path, stat_type* buffer) { return stat(path, buffer); } # endif # endif using ::mode_t; # if _GLIBCXX_USE_CHMOD using ::chmod; # endif # if _GLIBCXX_USE_MKDIR using ::mkdir; # endif # if _GLIBCXX_USE_GETCWD using ::getcwd; # endif # if _GLIBCXX_USE_CHDIR using ::chdir; # endif # if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_USE_UTIME using ::utimbuf; using ::utime; # endif using ::rename; using ::off_t; # ifdef _GLIBCXX_HAVE_TRUNCATE using ::truncate; # else inline int truncate(const char* path, off_t length) { if (length == 0) { const int fd = ::open(path, O_WRONLY|O_TRUNC); if (fd == -1) return fd; ::close(fd); return 0; } errno = ENOTSUP; return -1; } # endif using char_type = char; #else // ! _GLIBCXX_FILESYSTEM_IS_WINDOWS && ! _GLIBCXX_HAVE_UNISTD_H inline int open(const char*, int, ...) { errno = ENOSYS; return -1; } inline int close(int) { errno = ENOSYS; return -1; } using mode_t = int; inline int chmod(const char*, mode_t) { errno = ENOSYS; return -1; } inline int mkdir(const char*, mode_t) { errno = ENOSYS; return -1; } inline char* getcwd(char*, size_t) { errno = ENOSYS; return nullptr; } inline int chdir(const char*) { errno = ENOSYS; return -1; } inline int rename(const char*, const char*) { errno = ENOSYS; return -1; } using off_t = long; inline int truncate(const char*, off_t) { errno = ENOSYS; return -1; } using char_type = char; #endif // _GLIBCXX_FILESYSTEM_IS_WINDOWS } // namespace __gnu_posix template inline bool is_set(Bitmask obj, Bitmask bits) { return (obj & bits) != Bitmask::none; } inline bool is_not_found_errno(int err) noexcept { return err == ENOENT || err == ENOTDIR; } #ifdef _GLIBCXX_HAVE_SYS_STAT_H using __gnu_posix::stat_type; inline std::chrono::system_clock::time_point file_time(const stat_type& st, std::error_code& ec) noexcept { using namespace std::chrono; #ifdef _GLIBCXX_USE_ST_MTIM time_t s = st.st_mtim.tv_sec; nanoseconds ns{st.st_mtim.tv_nsec}; #else time_t s = st.st_mtime; nanoseconds ns{}; #endif // FIXME // There are possible timespec values which will overflow // chrono::system_clock::time_point but would not overflow // __file_clock::time_point, due to its different epoch. // // By checking for overflow of the intermediate system_clock::duration // type, we report an error for values which are actually representable // in the file_time_type result type. // // Howard Hinnant's solution for this problem is to use // duration<__int128>{s} + ns, which doesn't overflow. // An alternative would be to do the epoch correction on s before // the addition, and then go straight to file_time_type instead of // going via chrono::system_clock::time_point. // // (This only applies to the C++17 Filesystem library, because for the // Filesystem TS we don't have a distinct __file_clock, we just use the // system clock for file timestamps). if (seconds{s} >= floor(system_clock::duration::max())) { ec = std::make_error_code(std::errc::value_too_large); // EOVERFLOW return system_clock::time_point::min(); } ec.clear(); return system_clock::time_point{seconds{s} + ns}; } struct copy_options_existing_file { bool skip, update, overwrite; }; #endif // _GLIBCXX_HAVE_SYS_STAT_H } // namespace filesystem // BEGIN/END macros must be defined before including this file. _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM #ifdef _GLIBCXX_HAVE_SYS_STAT_H using std::filesystem::__gnu_posix::stat_type; using std::filesystem::__gnu_posix::char_type; bool do_copy_file(const char_type* from, const char_type* to, std::filesystem::copy_options_existing_file options, stat_type* from_st, stat_type* to_st, std::error_code& ec) noexcept; void do_space(const char_type* pathname, uintmax_t& capacity, uintmax_t& free, uintmax_t& available, std::error_code&); inline file_type make_file_type(const stat_type& st) noexcept { #ifdef _GLIBCXX_HAVE_S_ISREG if (S_ISREG(st.st_mode)) return file_type::regular; else if (S_ISDIR(st.st_mode)) return file_type::directory; else if (S_ISCHR(st.st_mode)) return file_type::character; else if (S_ISBLK(st.st_mode)) return file_type::block; else if (S_ISFIFO(st.st_mode)) return file_type::fifo; #ifdef S_ISLNK // not present in mingw else if (S_ISLNK(st.st_mode)) return file_type::symlink; #endif #ifdef S_ISSOCK // not present until POSIX:2001 else if (S_ISSOCK(st.st_mode)) return file_type::socket; #endif #endif return file_type::unknown; } inline file_status make_file_status(const stat_type& st) noexcept { return file_status{ make_file_type(st), static_cast(st.st_mode) & perms::mask }; } inline std::filesystem::copy_options_existing_file copy_file_options(copy_options opt) { using std::filesystem::is_set; return { is_set(opt, copy_options::skip_existing), is_set(opt, copy_options::update_existing), is_set(opt, copy_options::overwrite_existing) }; } #ifdef NEED_DO_COPY_FILE bool do_copy_file(const char_type* from, const char_type* to, std::filesystem::copy_options_existing_file options, stat_type* from_st, stat_type* to_st, std::error_code& ec) noexcept { namespace fs = std::filesystem; namespace posix = fs::__gnu_posix; stat_type st1, st2; file_status t, f; if (to_st == nullptr) { if (posix::stat(to, &st1)) { const int err = errno; if (!fs::is_not_found_errno(err)) { ec.assign(err, std::generic_category()); return false; } } else to_st = &st1; } else if (to_st == from_st) to_st = nullptr; if (to_st == nullptr) t = file_status{file_type::not_found}; else t = make_file_status(*to_st); if (from_st == nullptr) { if (posix::stat(from, &st2)) { ec.assign(errno, std::generic_category()); return false; } else from_st = &st2; } f = make_file_status(*from_st); // _GLIBCXX_RESOLVE_LIB_DEFECTS // 2712. copy_file() has a number of unspecified error conditions if (!is_regular_file(f)) { ec = std::make_error_code(std::errc::invalid_argument); return false; } if (exists(t)) { if (!is_regular_file(t)) { ec = std::make_error_code(std::errc::invalid_argument); return false; } if (to_st->st_dev == from_st->st_dev && to_st->st_ino == from_st->st_ino) { ec = std::make_error_code(std::errc::file_exists); return false; } if (options.skip) { ec.clear(); return false; } else if (options.update) { const auto from_mtime = fs::file_time(*from_st, ec); if (ec) return false; if ((from_mtime <= fs::file_time(*to_st, ec)) || ec) return false; } else if (!options.overwrite) { ec = std::make_error_code(std::errc::file_exists); return false; } else if (!is_regular_file(t)) { ec = std::make_error_code(std::errc::invalid_argument); return false; } } struct CloseFD { ~CloseFD() { if (fd != -1) posix::close(fd); } bool close() { return posix::close(std::__exchange(fd, -1)) == 0; } int fd; }; int common_flags = 0; #ifdef O_CLOEXEC common_flags |= O_CLOEXEC; #endif #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS common_flags |= O_BINARY; #endif const int iflag = O_RDONLY | common_flags; CloseFD in = { posix::open(from, iflag) }; if (in.fd == -1) { ec.assign(errno, std::generic_category()); return false; } int oflag = O_WRONLY | O_CREAT | common_flags; if (options.overwrite || options.update) oflag |= O_TRUNC; else oflag |= O_EXCL; CloseFD out = { posix::open(to, oflag, S_IWUSR) }; if (out.fd == -1) { if (errno == EEXIST && options.skip) ec.clear(); else ec.assign(errno, std::generic_category()); return false; } #if defined _GLIBCXX_USE_FCHMOD && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS if (::fchmod(out.fd, from_st->st_mode)) #elif defined _GLIBCXX_USE_FCHMODAT && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS if (::fchmodat(AT_FDCWD, to, from_st->st_mode, 0)) #elif defined _GLIBCXX_USE_CHMOD if (posix::chmod(to, from_st->st_mode)) #else if (false) #endif { ec.assign(errno, std::generic_category()); return false; } size_t count = from_st->st_size; #if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS ssize_t n = 0; if (count != 0) { off_t offset = 0; n = ::sendfile(out.fd, in.fd, &offset, count); if (n < 0 && errno != ENOSYS && errno != EINVAL) { ec.assign(errno, std::generic_category()); return false; } if ((size_t)n == count) { if (!out.close() || !in.close()) { ec.assign(errno, std::generic_category()); return false; } ec.clear(); return true; } else if (n > 0) count -= n; } #endif // _GLIBCXX_USE_SENDFILE using std::ios; __gnu_cxx::stdio_filebuf sbin(in.fd, ios::in|ios::binary); __gnu_cxx::stdio_filebuf sbout(out.fd, ios::out|ios::binary); if (sbin.is_open()) in.fd = -1; if (sbout.is_open()) out.fd = -1; #ifdef _GLIBCXX_USE_SENDFILE if (n != 0) { if (n < 0) n = 0; const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in); const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out); const std::streampos errpos(std::streamoff(-1)); if (p1 == errpos || p2 == errpos) { ec = std::make_error_code(std::errc::io_error); return false; } } #endif // ostream::operator<<(streambuf*) fails if it extracts no characters, // so don't try to use it for empty files. But from_st->st_size == 0 for // some special files (e.g. procfs, see PR libstdc++/108178) so just try // to read a character to decide whether there is anything to copy or not. if (sbin.sgetc() != char_traits::eof()) if (!(std::ostream(&sbout) << &sbin)) { ec = std::make_error_code(std::errc::io_error); return false; } if (!sbout.close() || !sbin.close()) { ec.assign(errno, std::generic_category()); return false; } ec.clear(); return true; } #endif // NEED_DO_COPY_FILE #ifdef NEED_DO_SPACE #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" void do_space(const char_type* pathname, uintmax_t& capacity, uintmax_t& free, uintmax_t& available, std::error_code& ec) { #ifdef _GLIBCXX_HAVE_SYS_STATVFS_H struct ::statvfs f; if (::statvfs(pathname, &f)) ec.assign(errno, std::generic_category()); else { if (f.f_frsize != (unsigned long)-1) { const uintmax_t fragment_size = f.f_frsize; const fsblkcnt_t unknown = -1; if (f.f_blocks != unknown) capacity = f.f_blocks * fragment_size; if (f.f_bfree != unknown) free = f.f_bfree * fragment_size; if (f.f_bavail != unknown) available = f.f_bavail * fragment_size; } ec.clear(); } #elif _GLIBCXX_FILESYSTEM_IS_WINDOWS ULARGE_INTEGER bytes_avail = {}, bytes_total = {}, bytes_free = {}; if (GetDiskFreeSpaceExW(pathname, &bytes_avail, &bytes_total, &bytes_free)) { if (bytes_total.QuadPart != 0) capacity = bytes_total.QuadPart; if (bytes_free.QuadPart != 0) free = bytes_free.QuadPart; if (bytes_avail.QuadPart != 0) available = bytes_avail.QuadPart; ec.clear(); } else ec = std::__last_system_error(); #else ec = std::make_error_code(std::errc::function_not_supported); #endif } #pragma GCC diagnostic pop #endif // NEED_DO_SPACE #endif // _GLIBCXX_HAVE_SYS_STAT_H // Find OS-specific name of temporary directory from the environment, // Caller must check that the path is an accessible directory. #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS inline wstring get_temp_directory_from_env(error_code& ec) { unsigned len = 1024; std::wstring buf; do { buf.resize(len); len = GetTempPathW(buf.size(), buf.data()); } while (len > buf.size()); if (len == 0) ec = __last_system_error(); else ec.clear(); buf.resize(len); return buf; } #else inline const char* get_temp_directory_from_env(error_code& ec) noexcept { ec.clear(); for (auto env : { "TMPDIR", "TMP", "TEMP", "TEMPDIR" }) { #if _GLIBCXX_HAVE_SECURE_GETENV auto tmpdir = ::secure_getenv(env); #else auto tmpdir = ::getenv(env); #endif if (tmpdir) return tmpdir; } return "/tmp"; } #endif _GLIBCXX_END_NAMESPACE_FILESYSTEM _GLIBCXX_END_NAMESPACE_VERSION } // namespace std #endif // _GLIBCXX_OPS_COMMON_H