| // Filesystem operation utilities -*- C++ -*- |
| |
| // Copyright (C) 2014-2022 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 |
| // <http://www.gnu.org/licenses/>. |
| |
| #ifndef _GLIBCXX_OPS_COMMON_H |
| #define _GLIBCXX_OPS_COMMON_H 1 |
| |
| #include <chrono> |
| #include <bits/move.h> // std::__exchange |
| |
| #ifdef _GLIBCXX_HAVE_UNISTD_H |
| # include <unistd.h> |
| # ifdef _GLIBCXX_HAVE_FCNTL_H |
| # include <fcntl.h> // AT_FDCWD, O_TRUNC etc. |
| # endif |
| # if defined(_GLIBCXX_HAVE_SYS_STAT_H) && defined(_GLIBCXX_HAVE_SYS_TYPES_H) |
| # include <sys/types.h> |
| # include <sys/stat.h> |
| # endif |
| #endif |
| #if !_GLIBCXX_USE_UTIMENSAT && _GLIBCXX_HAVE_UTIME_H |
| # include <utime.h> // utime |
| #endif |
| |
| #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS |
| # include <wchar.h> |
| #endif |
| |
| #ifdef NEED_DO_COPY_FILE |
| # include <filesystem> |
| # include <ext/stdio_filebuf.h> |
| # ifdef _GLIBCXX_USE_SENDFILE |
| # include <sys/sendfile.h> // 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 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); } |
| |
| inline int mkdir(const wchar_t* path, mode_t) |
| { return ::_wmkdir(path); } |
| |
| inline wchar_t* getcwd(wchar_t* buf, size_t size) |
| { return ::_wgetcwd(buf, size > (size_t)INT_MAX ? INT_MAX : (int)size); } |
| |
| inline int chdir(const wchar_t* path) |
| { return ::_wchdir(path); } |
| |
| #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 |
| 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; |
| using ::chmod; |
| using ::mkdir; |
| using ::getcwd; |
| using ::chdir; |
| # 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<typename Bitmask> |
| 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<seconds>(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<perms>(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 iflag = O_RDONLY; |
| #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS |
| iflag |= O_BINARY; |
| #endif |
| |
| CloseFD in = { posix::open(from, iflag) }; |
| if (in.fd == -1) |
| { |
| ec.assign(errno, std::generic_category()); |
| return false; |
| } |
| int oflag = O_WRONLY|O_CREAT; |
| if (options.overwrite || options.update) |
| oflag |= O_TRUNC; |
| else |
| oflag |= O_EXCL; |
| #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS |
| oflag |= O_BINARY; |
| #endif |
| 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)) |
| #else |
| if (posix::chmod(to, from_st->st_mode)) |
| #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 |
| off_t offset = 0; |
| ssize_t 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<char> sbin(in.fd, ios::in|ios::binary); |
| __gnu_cxx::stdio_filebuf<char> 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 |
| |
| if (count && !(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 |