xref: /netbsd-src/external/gpl3/gcc/dist/libstdc++-v3/src/filesystem/dir-common.h (revision 0e2e28bced52bda3788c857106bde6c44d2df3b8)
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 
45 namespace std _GLIBCXX_VISIBILITY(default)
46 {
47 _GLIBCXX_BEGIN_NAMESPACE_VERSION
48 namespace filesystem
49 {
50 namespace __gnu_posix
51 {
52 #if _GLIBCXX_FILESYSTEM_IS_WINDOWS
53 // Adapt the Windows _wxxx functions to look like POSIX xxx, but for wchar_t*.
54 using char_type = wchar_t;
55 using DIR = ::_WDIR;
56 using dirent = _wdirent;
57 inline DIR* opendir(const wchar_t* path) { return ::_wopendir(path); }
58 inline dirent* readdir(DIR* dir) { return ::_wreaddir(dir); }
59 inline int closedir(DIR* dir) { return ::_wclosedir(dir); }
60 #elif defined _GLIBCXX_HAVE_DIRENT_H
61 using char_type = char;
62 using DIR = ::DIR;
63 typedef struct ::dirent dirent;
64 using ::opendir;
65 using ::readdir;
66 using ::closedir;
67 #else
68 using char_type = char;
69 struct dirent { const char* d_name; };
70 struct DIR { };
71 inline DIR* opendir(const char*) { return nullptr; }
72 inline dirent* readdir(DIR*) { return nullptr; }
73 inline int closedir(DIR*) { return -1; }
74 #undef _GLIBCXX_HAVE_DIRFD
75 #undef _GLIBCXX_HAVE_UNLINKAT
76 #endif
77 } // namespace __gnu_posix
78 
79 namespace posix = __gnu_posix;
80 
81 inline bool
82 is_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 
93 struct _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 
269 inline file_type
270 get_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