]> gcc.gnu.org Git - gcc.git/blob - libstdc++-v3/src/filesystem/dir-common.h
libstdc++: Improve directory iterator abstractions for openat
[gcc.git] / libstdc++-v3 / src / filesystem / dir-common.h
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
This page took 0.054334 seconds and 5 git commands to generate.