// Filesystem directory iterator utilities -*- C++ -*- // Copyright (C) 2014-2024 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_DIR_COMMON_H #define _GLIBCXX_DIR_COMMON_H 1 #include // uint32_t #include // strcmp #include #if _GLIBCXX_FILESYSTEM_IS_WINDOWS #include // wcscmp #endif #ifdef _GLIBCXX_HAVE_DIRENT_H # ifdef _GLIBCXX_HAVE_SYS_TYPES_H # include # endif # include // opendir, readdir, fdopendir, dirfd # ifdef _GLIBCXX_HAVE_FCNTL_H # include // open, openat, fcntl, AT_FDCWD, O_NOFOLLOW etc. # include // close, unlinkat # endif #endif namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION namespace filesystem { namespace __gnu_posix { #if _GLIBCXX_FILESYSTEM_IS_WINDOWS // Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*. using char_type = wchar_t; using DIR = ::_WDIR; using dirent = _wdirent; inline DIR* opendir(const wchar_t* path) { return ::_wopendir(path); } inline dirent* readdir(DIR* dir) { return ::_wreaddir(dir); } inline int closedir(DIR* dir) { return ::_wclosedir(dir); } #elif defined _GLIBCXX_HAVE_DIRENT_H using char_type = char; using DIR = ::DIR; typedef struct ::dirent dirent; using ::opendir; using ::readdir; using ::closedir; #else using char_type = char; struct dirent { const char* d_name; }; struct DIR { }; inline DIR* opendir(const char*) { return nullptr; } inline dirent* readdir(DIR*) { return nullptr; } inline int closedir(DIR*) { return -1; } #undef _GLIBCXX_HAVE_DIRFD #undef _GLIBCXX_HAVE_UNLINKAT #endif } // namespace __gnu_posix namespace posix = __gnu_posix; inline bool is_permission_denied_error(int e) { if (e == EACCES) return true; #ifdef __APPLE__ if (e == EPERM) // See PR 99533 return true; #endif return false; } struct _Dir_base { // As well as the full pathname (including the directory iterator's path) // this type contains a file descriptor for a directory and a second pathname // relative to that directory. The file descriptor and relative pathname // can be used with POSIX openat and unlinkat. struct _At_path { // No file descriptor given, so interpret the pathname relative to the CWD. _At_path(const posix::char_type* p) noexcept : pathname(p), dir_fd(fdcwd()), offset(0) { } _At_path(int fd, const posix::char_type* p, size_t offset) noexcept : pathname(p), dir_fd(fd), offset(offset) { } const posix::char_type* path() const noexcept { return pathname; } int dir() const noexcept { return dir_fd; } const posix::char_type* path_at_dir() const noexcept { return pathname + offset; } private: const posix::char_type* pathname; // Full path relative to CWD. int dir_fd; // A directory descriptor (either the parent dir, or AT_FDCWD). uint32_t offset; // Offset into pathname for the part relative to dir_fd. // Special value representing the current working directory. // Not a valid file descriptor for an open directory stream. static constexpr int fdcwd() noexcept { #ifdef AT_FDCWD return AT_FDCWD; #else return -1; // Use invalid fd if AT_FDCWD isn't supported. #endif } }; // If no error occurs then dirp is non-null, // otherwise null (even if a permission denied error is ignored). _Dir_base(const _At_path& atp, bool skip_permission_denied, bool nofollow, error_code& ec) noexcept : dirp(_Dir_base::openat(atp, nofollow)) { if (dirp) ec.clear(); else if (is_permission_denied_error(errno) && skip_permission_denied) ec.clear(); else ec.assign(errno, std::generic_category()); } _Dir_base(_Dir_base&& d) : dirp(std::exchange(d.dirp, nullptr)) { } _Dir_base& operator=(_Dir_base&&) = delete; ~_Dir_base() { if (dirp) posix::closedir(dirp); } const posix::dirent* advance(bool skip_permission_denied, error_code& ec) noexcept { ec.clear(); int err = std::exchange(errno, 0); const posix::dirent* entp = posix::readdir(dirp); // std::swap cannot be used with Bionic's errno err = std::exchange(errno, err); if (entp) { // skip past dot and dot-dot if (is_dot_or_dotdot(entp->d_name)) return advance(skip_permission_denied, ec); return entp; } else if (err) { if (err == EACCES && skip_permission_denied) return nullptr; ec.assign(err, std::generic_category()); return nullptr; } else { // reached the end return nullptr; } } static bool is_dot_or_dotdot(const char* s) noexcept { return !strcmp(s, ".") || !strcmp(s, ".."); } #if _GLIBCXX_FILESYSTEM_IS_WINDOWS static bool is_dot_or_dotdot(const wchar_t* s) noexcept { return !wcscmp(s, L".") || !wcscmp(s, L".."); } #endif // Set the close-on-exec flag if not already done via O_CLOEXEC. static bool set_close_on_exec([[maybe_unused]] int fd) { #if ! defined O_CLOEXEC && defined FD_CLOEXEC int flags = ::fcntl(fd, F_GETFD); if (flags == -1 || ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) return false; #endif return true; } static posix::DIR* openat(const _At_path& atp, bool nofollow) { #if _GLIBCXX_HAVE_FDOPENDIR && defined O_RDONLY && defined O_DIRECTORY \ && ! _GLIBCXX_FILESYSTEM_IS_WINDOWS // Any file descriptor we open here should be closed on exec. #ifdef O_CLOEXEC constexpr int close_on_exec = O_CLOEXEC; #else constexpr int close_on_exec = 0; #endif int flags = O_RDONLY | O_DIRECTORY | close_on_exec; // Directory iterators are vulnerable to race conditions unless O_NOFOLLOW // is supported, because a directory could be replaced with a symlink after // checking is_directory(symlink_status(f)). O_NOFOLLOW avoids the race. #ifdef O_NOFOLLOW if (nofollow) flags |= O_NOFOLLOW; #else nofollow = false; #endif int fd; #if _GLIBCXX_HAVE_OPENAT fd = ::openat(atp.dir(), atp.path_at_dir(), flags); #else // If we cannot use openat, there's no benefit to using posix::open unless // we will use O_NOFOLLOW, so just use the simpler posix::opendir. if (!nofollow) return posix::opendir(atp.path()); fd = ::open(atp.path(), flags); #endif if (fd == -1) return nullptr; if (set_close_on_exec(fd)) if (::DIR* dirp = ::fdopendir(fd)) return dirp; int err = errno; ::close(fd); errno = err; return nullptr; #else return posix::opendir(atp.path()); #endif } posix::DIR* dirp; }; } // namespace filesystem // BEGIN/END macros must be defined before including this file. _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM inline file_type get_file_type(const std::filesystem::__gnu_posix::dirent& d [[gnu::unused]]) { #ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE switch (d.d_type) { case DT_BLK: return file_type::block; case DT_CHR: return file_type::character; case DT_DIR: return file_type::directory; case DT_FIFO: return file_type::fifo; case DT_LNK: return file_type::symlink; case DT_REG: return file_type::regular; case DT_SOCK: return file_type::socket; case DT_UNKNOWN: return file_type::unknown; default: return file_type::none; } #else return file_type::none; #endif } _GLIBCXX_END_NAMESPACE_FILESYSTEM _GLIBCXX_END_NAMESPACE_VERSION } // namespace std #endif // _GLIBCXX_DIR_COMMON_H