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 
37 namespace detail {
38 
39 #if !defined(_LIBCPP_WIN32API)
40 
41 #  if defined(DT_BLK)
42 template <class DirEntT, class = decltype(DirEntT::d_type)>
43 file_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 
69 template <class DirEntT>
70 file_type get_file_type(DirEntT*, long) {
71   return file_type::none;
72 }
73 
74 inline 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 
89 inline 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 }
96 inline uintmax_t get_file_size(const WIN32_FIND_DATAW& data) {
97   return (static_cast<uint64_t>(data.nFileSizeHigh) << 32) + data.nFileSizeLow;
98 }
99 inline 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 
111 using value_type  = path::value_type;
112 using string_type = path::string_type;
113 
114 struct 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 
188 private:
189   explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {}
190 };
191 
192 inline perms posix_get_perms(const StatT& st) noexcept { return static_cast<perms>(st.st_mode) & perms::mask; }
193 
194 inline 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 
229 inline 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 
236 inline file_status posix_stat(path const& p, error_code* ec) {
237   StatT path_stat;
238   return posix_stat(p, path_stat, ec);
239 }
240 
241 inline 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 
248 inline 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
254 inline 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 
263 inline 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 
272 inline 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 
276 inline 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