1//===----------------------------------------------------------------------===////
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===////
8
9#ifndef FILESYSTEM_FILE_DESCRIPTOR_H
10#define FILESYSTEM_FILE_DESCRIPTOR_H
11
12#include <__config>
13#include <cstdint>
14#include <filesystem>
15#include <string_view>
16#include <system_error>
17#include <utility>
18
19#include "error.h"
20#include "posix_compat.h"
21#include "time_utils.h"
22
23#if defined(_LIBCPP_WIN32API)
24# define WIN32_LEAN_AND_MEAN
25# define NOMINMAX
26# include <windows.h>
27#else
28# include <dirent.h> // for DIR & friends
29# include <fcntl.h> // values for fchmodat
30# include <sys/stat.h>
31# include <sys/statvfs.h>
32# include <unistd.h>
33#endif // defined(_LIBCPP_WIN32API)
34
35_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
36
37namespace detail {
38
39#if !defined(_LIBCPP_WIN32API)
40
41# if defined(DT_BLK)
42template <class DirEntT, class = decltype(DirEntT::d_type)>
43file_type get_file_type(DirEntT* ent, int) {
44 switch (ent->d_type) {
45 case DT_BLK:
46 return file_type::block;
47 case DT_CHR:
48 return file_type::character;
49 case DT_DIR:
50 return file_type::directory;
51 case DT_FIFO:
52 return file_type::fifo;
53 case DT_LNK:
54 return file_type::symlink;
55 case DT_REG:
56 return file_type::regular;
57 case DT_SOCK:
58 return file_type::socket;
59 // Unlike in lstat, hitting "unknown" here simply means that the underlying
60 // filesystem doesn't support d_type. Report is as 'none' so we correctly
61 // set the cache to empty.
62 case DT_UNKNOWN:
63 break;
64 }
65 return file_type::none;
66}
67# endif // defined(DT_BLK)
68
69template <class DirEntT>
70file_type get_file_type(DirEntT*, long) {
71 return file_type::none;
72}
73
74inline pair<string_view, file_type> posix_readdir(DIR* dir_stream, error_code& ec) {
75 struct dirent* dir_entry_ptr = nullptr;
76 errno = 0; // zero errno in order to detect errors
77 ec.clear();
78 if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) {
79 if (errno)
80 ec = capture_errno();
81 return {};
82 } else {
83 return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)};
84 }
85}
86
87#else // _LIBCPP_WIN32API
88
89inline file_type get_file_type(const WIN32_FIND_DATAW& data) {
90 if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && data.dwReserved0 == IO_REPARSE_TAG_SYMLINK)
91 return file_type::symlink;
92 if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
93 return file_type::directory;
94 return file_type::regular;
95}
96inline uintmax_t get_file_size(const WIN32_FIND_DATAW& data) {
97 return (static_cast<uint64_t>(data.nFileSizeHigh) << 32) + data.nFileSizeLow;
98}
99inline file_time_type get_write_time(const WIN32_FIND_DATAW& data) {
100 ULARGE_INTEGER tmp;
101 const FILETIME& time = data.ftLastWriteTime;
102 tmp.u.LowPart = time.dwLowDateTime;
103 tmp.u.HighPart = time.dwHighDateTime;
104 return file_time_type(file_time_type::duration(tmp.QuadPart));
105}
106
107#endif // !_LIBCPP_WIN32API
108
109// POSIX HELPERS
110
111using value_type = path::value_type;
112using string_type = path::string_type;
113
114struct FileDescriptor {
115 const path& name;
116 int fd = -1;
117 StatT m_stat;
118 file_status m_status;
119
120 template <class... Args>
121 static FileDescriptor create(const path* p, error_code& ec, Args... args) {
122 ec.clear();
123 int fd;
124#ifdef _LIBCPP_WIN32API
125 // TODO: most of the filesystem implementation uses native Win32 calls
126 // (mostly via posix_compat.h). However, here we use the C-runtime APIs to
127 // open a file, because we subsequently pass the C-runtime fd to
128 // `std::[io]fstream::__open(int fd)` in order to implement copy_file.
129 //
130 // Because we're calling the windows C-runtime, win32 error codes are
131 // translated into C error numbers by the C runtime, and returned in errno,
132 // rather than being accessible directly via GetLastError.
133 //
134 // Ideally copy_file should be calling the Win32 CopyFile2 function, which
135 // works on paths, not open files -- at which point this FileDescriptor type
136 // will no longer be needed on windows at all.
137 fd = ::_wopen(p->c_str(), args...);
138#else
139 fd = open(p->c_str(), args...);
140#endif
141
142 if (fd == -1) {
143 ec = capture_errno();
144 return FileDescriptor{p};
145 }
146 return FileDescriptor(p, fd);
147 }
148
149 template <class... Args>
150 static FileDescriptor create_with_status(const path* p, error_code& ec, Args... args) {
151 FileDescriptor fd = create(p, ec, args...);
152 if (!ec)
153 fd.refresh_status(ec);
154
155 return fd;
156 }
157
158 file_status get_status() const { return m_status; }
159 StatT const& get_stat() const { return m_stat; }
160
161 bool status_known() const { return filesystem::status_known(m_status); }
162
163 file_status refresh_status(error_code& ec);
164
165 void close() noexcept {
166 if (fd != -1) {
167#ifdef _LIBCPP_WIN32API
168 ::_close(fd);
169#else
170 ::close(fd);
171#endif
172 // FIXME: shouldn't this return an error_code?
173 }
174 fd = -1;
175 }
176
177 FileDescriptor(FileDescriptor&& other)
178 : name(other.name), fd(other.fd), m_stat(other.m_stat), m_status(other.m_status) {
179 other.fd = -1;
180 other.m_status = file_status{};
181 }
182
183 ~FileDescriptor() { close(); }
184
185 FileDescriptor(FileDescriptor const&) = delete;
186 FileDescriptor& operator=(FileDescriptor const&) = delete;
187
188private:
189 explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {}
190};
191
192inline perms posix_get_perms(const StatT& st) noexcept { return static_cast<perms>(st.st_mode) & perms::mask; }
193
194inline file_status create_file_status(error_code& m_ec, path const& p, const StatT& path_stat, error_code* ec) {
195 if (ec)
196 *ec = m_ec;
197 if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) {
198 return file_status(file_type::not_found);
199 } else if (m_ec) {
200 ErrorHandler<void> err("posix_stat", ec, &p);
201 err.report(m_ec, "failed to determine attributes for the specified path");
202 return file_status(file_type::none);
203 }
204 // else
205
206 file_status fs_tmp;
207 auto const mode = path_stat.st_mode;
208 if (S_ISLNK(mode))
209 fs_tmp.type(file_type::symlink);
210 else if (S_ISREG(mode))
211 fs_tmp.type(file_type::regular);
212 else if (S_ISDIR(mode))
213 fs_tmp.type(file_type::directory);
214 else if (S_ISBLK(mode))
215 fs_tmp.type(file_type::block);
216 else if (S_ISCHR(mode))
217 fs_tmp.type(file_type::character);
218 else if (S_ISFIFO(mode))
219 fs_tmp.type(file_type::fifo);
220 else if (S_ISSOCK(mode))
221 fs_tmp.type(file_type::socket);
222 else
223 fs_tmp.type(file_type::unknown);
224
225 fs_tmp.permissions(detail::posix_get_perms(path_stat));
226 return fs_tmp;
227}
228
229inline file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) {
230 error_code m_ec;
231 if (detail::stat(p.c_str(), &path_stat) == -1)
232 m_ec = detail::capture_errno();
233 return create_file_status(m_ec, p, path_stat, ec);
234}
235
236inline file_status posix_stat(path const& p, error_code* ec) {
237 StatT path_stat;
238 return posix_stat(p, path_stat, ec);
239}
240
241inline file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) {
242 error_code m_ec;
243 if (detail::lstat(p.c_str(), &path_stat) == -1)
244 m_ec = detail::capture_errno();
245 return create_file_status(m_ec, p, path_stat, ec);
246}
247
248inline file_status posix_lstat(path const& p, error_code* ec) {
249 StatT path_stat;
250 return posix_lstat(p, path_stat, ec);
251}
252
253// http://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
254inline bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& ec) {
255 if (detail::ftruncate(fd.fd, to_size) == -1) {
256 ec = capture_errno();
257 return true;
258 }
259 ec.clear();
260 return false;
261}
262
263inline bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) {
264 if (detail::fchmod(fd.fd, st.st_mode) == -1) {
265 ec = capture_errno();
266 return true;
267 }
268 ec.clear();
269 return false;
270}
271
272inline bool stat_equivalent(const StatT& st1, const StatT& st2) {
273 return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
274}
275
276inline file_status FileDescriptor::refresh_status(error_code& ec) {
277 // FD must be open and good.
278 m_status = file_status{};
279 m_stat = {};
280 error_code m_ec;
281 if (detail::fstat(fd, &m_stat) == -1)
282 m_ec = capture_errno();
283 m_status = create_file_status(m_ec, name, m_stat, &ec);
284 return m_status;
285}
286
287} // end namespace detail
288
289_LIBCPP_END_NAMESPACE_FILESYSTEM
290
291#endif // FILESYSTEM_FILE_DESCRIPTOR_H
292