1// Filesystem directory iterator utilities -*- C++ -*- 2 3// Copyright (C) 2014-2022 Free Software Foundation, Inc. 4// 5// This file is part of the GNU ISO C++ Library. This library is free 6// software; you can redistribute it and/or modify it under the 7// terms of the GNU General Public License as published by the 8// Free Software Foundation; either version 3, or (at your option) 9// any later version. 10 11// This library is distributed in the hope that it will be useful, 12// but WITHOUT ANY WARRANTY; without even the implied warranty of 13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14// GNU General Public License for more details. 15 16// Under Section 7 of GPL version 3, you are granted additional 17// permissions described in the GCC Runtime Library Exception, version 18// 3.1, as published by the Free Software Foundation. 19 20// You should have received a copy of the GNU General Public License and 21// a copy of the GCC Runtime Library Exception along with this program; 22// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see 23// <http://www.gnu.org/licenses/>. 24 25#ifndef _GLIBCXX_DIR_COMMON_H 26#define _GLIBCXX_DIR_COMMON_H 1 27 28#include <stdint.h> // uint32_t 29#include <string.h> // strcmp 30#include <errno.h> 31#if _GLIBCXX_FILESYSTEM_IS_WINDOWS 32#include <wchar.h> // wcscmp 33#endif 34#ifdef _GLIBCXX_HAVE_DIRENT_H 35# ifdef _GLIBCXX_HAVE_SYS_TYPES_H 36# include <sys/types.h> 37# endif 38# include <dirent.h> // opendir, readdir, fdopendir, dirfd 39# ifdef _GLIBCXX_HAVE_FCNTL_H 40# include <fcntl.h> // open, openat, fcntl, AT_FDCWD, O_NOFOLLOW etc. 41# include <unistd.h> // close, unlinkat 42# endif 43#endif 44 45namespace std _GLIBCXX_VISIBILITY(default) 46{ 47_GLIBCXX_BEGIN_NAMESPACE_VERSION 48namespace filesystem 49{ 50namespace __gnu_posix 51{ 52#if _GLIBCXX_FILESYSTEM_IS_WINDOWS 53// Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*. 54using char_type = wchar_t; 55using DIR = ::_WDIR; 56using dirent = _wdirent; 57inline DIR* opendir(const wchar_t* path) { return ::_wopendir(path); } 58inline dirent* readdir(DIR* dir) { return ::_wreaddir(dir); } 59inline int closedir(DIR* dir) { return ::_wclosedir(dir); } 60#elif defined _GLIBCXX_HAVE_DIRENT_H 61using char_type = char; 62using DIR = ::DIR; 63typedef struct ::dirent dirent; 64using ::opendir; 65using ::readdir; 66using ::closedir; 67#else 68using char_type = char; 69struct dirent { const char* d_name; }; 70struct DIR { }; 71inline DIR* opendir(const char*) { return nullptr; } 72inline dirent* readdir(DIR*) { return nullptr; } 73inline int closedir(DIR*) { return -1; } 74#undef _GLIBCXX_HAVE_DIRFD 75#undef _GLIBCXX_HAVE_UNLINKAT 76#endif 77} // namespace __gnu_posix 78 79namespace posix = __gnu_posix; 80 81inline bool 82is_permission_denied_error(int e) 83{ 84 if (e == EACCES) 85 return true; 86#ifdef __APPLE__ 87 if (e == EPERM) // See PR 99533 88 return true; 89#endif 90 return false; 91} 92 93struct _Dir_base 94{ 95 // As well as the full pathname (including the directory iterator's path) 96 // this type contains a file descriptor for a directory and a second pathname 97 // relative to that directory. The file descriptor and relative pathname 98 // can be used with POSIX openat and unlinkat. 99 struct _At_path 100 { 101 // No file descriptor given, so interpret the pathname relative to the CWD. 102 _At_path(const posix::char_type* p) noexcept 103 : pathname(p), dir_fd(fdcwd()), offset(0) 104 { } 105 106 _At_path(int fd, const posix::char_type* p, size_t offset) noexcept 107 : pathname(p), dir_fd(fd), offset(offset) 108 { } 109 110 const posix::char_type* 111 path() const noexcept { return pathname; } 112 113 int 114 dir() const noexcept { return dir_fd; } 115 116 const posix::char_type* 117 path_at_dir() const noexcept { return pathname + offset; } 118 119 private: 120 const posix::char_type* pathname; // Full path relative to CWD. 121 int dir_fd; // A directory descriptor (either the parent dir, or AT_FDCWD). 122 uint32_t offset; // Offset into pathname for the part relative to dir_fd. 123 124 // Special value representing the current working directory. 125 // Not a valid file descriptor for an open directory stream. 126 static constexpr int 127 fdcwd() noexcept 128 { 129#ifdef AT_FDCWD 130 return AT_FDCWD; 131#else 132 return -1; // Use invalid fd if AT_FDCWD isn't supported. 133#endif 134 } 135 }; 136 137 // If no error occurs then dirp is non-null, 138 // otherwise null (even if a permission denied error is ignored). 139 _Dir_base(const _At_path& atp, 140 bool skip_permission_denied, bool nofollow, 141 error_code& ec) noexcept 142 : dirp(_Dir_base::openat(atp, nofollow)) 143 { 144 if (dirp) 145 ec.clear(); 146 else if (is_permission_denied_error(errno) && skip_permission_denied) 147 ec.clear(); 148 else 149 ec.assign(errno, std::generic_category()); 150 } 151 152 _Dir_base(_Dir_base&& d) : dirp(std::exchange(d.dirp, nullptr)) { } 153 154 _Dir_base& operator=(_Dir_base&&) = delete; 155 156 ~_Dir_base() { if (dirp) posix::closedir(dirp); } 157 158 const posix::dirent* 159 advance(bool skip_permission_denied, error_code& ec) noexcept 160 { 161 ec.clear(); 162 163 int err = std::exchange(errno, 0); 164 const posix::dirent* entp = posix::readdir(dirp); 165 // std::swap cannot be used with Bionic's errno 166 err = std::exchange(errno, err); 167 168 if (entp) 169 { 170 // skip past dot and dot-dot 171 if (is_dot_or_dotdot(entp->d_name)) 172 return advance(skip_permission_denied, ec); 173 return entp; 174 } 175 else if (err) 176 { 177 if (err == EACCES && skip_permission_denied) 178 return nullptr; 179 ec.assign(err, std::generic_category()); 180 return nullptr; 181 } 182 else 183 { 184 // reached the end 185 return nullptr; 186 } 187 } 188 189 static bool is_dot_or_dotdot(const char* s) noexcept 190 { return !strcmp(s, ".") || !strcmp(s, ".."); } 191 192#if _GLIBCXX_FILESYSTEM_IS_WINDOWS 193 static bool is_dot_or_dotdot(const wchar_t* s) noexcept 194 { return !wcscmp(s, L".") || !wcscmp(s, L".."); } 195#endif 196 197 // Set the close-on-exec flag if not already done via O_CLOEXEC. 198 static bool 199 set_close_on_exec([[maybe_unused]] int fd) 200 { 201#if ! defined O_CLOEXEC && defined FD_CLOEXEC 202 int flags = ::fcntl(fd, F_GETFD); 203 if (flags == -1 || ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) 204 return false; 205#endif 206 return true; 207 } 208 209 static posix::DIR* 210 openat(const _At_path& atp, bool nofollow) 211 { 212#if _GLIBCXX_HAVE_FDOPENDIR && defined O_RDONLY && defined O_DIRECTORY \ 213 && ! _GLIBCXX_FILESYSTEM_IS_WINDOWS 214 215 // Any file descriptor we open here should be closed on exec. 216#ifdef O_CLOEXEC 217 constexpr int close_on_exec = O_CLOEXEC; 218#else 219 constexpr int close_on_exec = 0; 220#endif 221 222 int flags = O_RDONLY | O_DIRECTORY | close_on_exec; 223 224 // Directory iterators are vulnerable to race conditions unless O_NOFOLLOW 225 // is supported, because a directory could be replaced with a symlink after 226 // checking is_directory(symlink_status(f)). O_NOFOLLOW avoids the race. 227#ifdef O_NOFOLLOW 228 if (nofollow) 229 flags |= O_NOFOLLOW; 230#else 231 nofollow = false; 232#endif 233 234 int fd; 235 236#if _GLIBCXX_HAVE_OPENAT 237 fd = ::openat(atp.dir(), atp.path_at_dir(), flags); 238#else 239 // If we cannot use openat, there's no benefit to using posix::open unless 240 // we will use O_NOFOLLOW, so just use the simpler posix::opendir. 241 if (!nofollow) 242 return posix::opendir(atp.path()); 243 244 fd = ::open(atp.path(), flags); 245#endif 246 247 if (fd == -1) 248 return nullptr; 249 if (set_close_on_exec(fd)) 250 if (::DIR* dirp = ::fdopendir(fd)) 251 return dirp; 252 int err = errno; 253 ::close(fd); 254 errno = err; 255 return nullptr; 256#else 257 return posix::opendir(atp.path()); 258#endif 259 } 260 261 posix::DIR* dirp; 262}; 263 264} // namespace filesystem 265 266// BEGIN/END macros must be defined before including this file. 267_GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM 268 269inline file_type 270get_file_type(const std::filesystem::__gnu_posix::dirent& d [[gnu::unused]]) 271{ 272#ifdef _GLIBCXX_HAVE_STRUCT_DIRENT_D_TYPE 273 switch (d.d_type) 274 { 275 case DT_BLK: 276 return file_type::block; 277 case DT_CHR: 278 return file_type::character; 279 case DT_DIR: 280 return file_type::directory; 281 case DT_FIFO: 282 return file_type::fifo; 283 case DT_LNK: 284 return file_type::symlink; 285 case DT_REG: 286 return file_type::regular; 287 case DT_SOCK: 288 return file_type::socket; 289 case DT_UNKNOWN: 290 return file_type::unknown; 291 default: 292 return file_type::none; 293 } 294#else 295 return file_type::none; 296#endif 297} 298 299_GLIBCXX_END_NAMESPACE_FILESYSTEM 300 301_GLIBCXX_END_NAMESPACE_VERSION 302} // namespace std 303 304#endif // _GLIBCXX_DIR_COMMON_H 305