| // Filesystem directory iterator utilities -*- C++ -*- | |
| // Copyright (C) 2014-2026 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_DIR_COMMON_H | |
| #define _GLIBCXX_DIR_COMMON_H 1 | |
| #include <stdint.h> // uint32_t | |
| #include <string.h> // strcmp | |
| #include <errno.h> | |
| #if _GLIBCXX_FILESYSTEM_IS_WINDOWS | |
| #include <wchar.h> // wcscmp | |
| #endif | |
| #ifdef _GLIBCXX_HAVE_DIRENT_H | |
| # ifdef _GLIBCXX_HAVE_SYS_TYPES_H | |
| # include <sys/types.h> | |
| # endif | |
| # include <dirent.h> // opendir, readdir, fdopendir, dirfd | |
| # ifdef _GLIBCXX_HAVE_FCNTL_H | |
| # include <fcntl.h> // open, openat, fcntl, AT_FDCWD, O_NOFOLLOW etc. | |
| # include <unistd.h> // 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, [[maybe_unused]] 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 |