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