master
  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  using detail::fs_time;
101  const FILETIME& time = data.ftLastWriteTime;
102  auto ts              = filetime_to_timespec(time);
103  if (!fs_time::is_representable(ts))
104    return file_time_type::min();
105  return fs_time::convert_from_timespec(ts);
106}
107inline perms get_file_perm(const WIN32_FIND_DATAW& data) {
108  unsigned st_mode = 0555; // Read-only
109  if (!(data.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
110    st_mode |= 0222; // Write
111  return static_cast<perms>(st_mode) & perms::mask;
112}
113
114#endif // !_LIBCPP_WIN32API
115
116//                       POSIX HELPERS
117
118using value_type  = path::value_type;
119using string_type = path::string_type;
120
121struct FileDescriptor {
122  const path& name;
123  int fd = -1;
124  StatT m_stat;
125  file_status m_status;
126
127  template <class... Args>
128  static FileDescriptor create(const path* p, error_code& ec, Args... args) {
129    ec.clear();
130    int fd;
131#ifdef _LIBCPP_WIN32API
132    // TODO: most of the filesystem implementation uses native Win32 calls
133    // (mostly via posix_compat.h). However, here we use the C-runtime APIs to
134    // open a file, because we subsequently pass the C-runtime fd to
135    // `std::[io]fstream::__open(int fd)` in order to implement copy_file.
136    //
137    // Because we're calling the windows C-runtime, win32 error codes are
138    // translated into C error numbers by the C runtime, and returned in errno,
139    // rather than being accessible directly via GetLastError.
140    //
141    // Ideally copy_file should be calling the Win32 CopyFile2 function, which
142    // works on paths, not open files -- at which point this FileDescriptor type
143    // will no longer be needed on windows at all.
144    fd = ::_wopen(p->c_str(), args...);
145#else
146    fd = open(p->c_str(), args...);
147#endif
148
149    if (fd == -1) {
150      ec = capture_errno();
151      return FileDescriptor{p};
152    }
153    return FileDescriptor(p, fd);
154  }
155
156  template <class... Args>
157  static FileDescriptor create_with_status(const path* p, error_code& ec, Args... args) {
158    FileDescriptor fd = create(p, ec, args...);
159    if (!ec)
160      fd.refresh_status(ec);
161
162    return fd;
163  }
164
165  file_status get_status() const { return m_status; }
166  StatT const& get_stat() const { return m_stat; }
167
168  bool status_known() const { return filesystem::status_known(m_status); }
169
170  file_status refresh_status(error_code& ec);
171
172  void close() noexcept {
173    if (fd != -1) {
174#ifdef _LIBCPP_WIN32API
175      ::_close(fd);
176#else
177      ::close(fd);
178#endif
179      // FIXME: shouldn't this return an error_code?
180    }
181    fd = -1;
182  }
183
184  FileDescriptor(FileDescriptor&& other)
185      : name(other.name), fd(other.fd), m_stat(other.m_stat), m_status(other.m_status) {
186    other.fd       = -1;
187    other.m_status = file_status{};
188  }
189
190  ~FileDescriptor() { close(); }
191
192  FileDescriptor(FileDescriptor const&)            = delete;
193  FileDescriptor& operator=(FileDescriptor const&) = delete;
194
195private:
196  explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {}
197};
198
199inline perms posix_get_perms(const StatT& st) noexcept { return static_cast<perms>(st.st_mode) & perms::mask; }
200
201inline file_status create_file_status(error_code& m_ec, path const& p, const StatT& path_stat, error_code* ec) {
202  if (ec)
203    *ec = m_ec;
204  if (m_ec && (m_ec == errc::no_such_file_or_directory || m_ec == errc::not_a_directory)) {
205    return file_status(file_type::not_found);
206  } else if (m_ec) {
207    ErrorHandler<void> err("posix_stat", ec, &p);
208    err.report(m_ec, "failed to determine attributes for the specified path");
209    return file_status(file_type::none);
210  }
211  // else
212
213  file_status fs_tmp;
214  auto const mode = path_stat.st_mode;
215  if (S_ISLNK(mode))
216    fs_tmp.type(file_type::symlink);
217  else if (S_ISREG(mode))
218    fs_tmp.type(file_type::regular);
219  else if (S_ISDIR(mode))
220    fs_tmp.type(file_type::directory);
221  else if (S_ISBLK(mode))
222    fs_tmp.type(file_type::block);
223  else if (S_ISCHR(mode))
224    fs_tmp.type(file_type::character);
225  else if (S_ISFIFO(mode))
226    fs_tmp.type(file_type::fifo);
227  else if (S_ISSOCK(mode))
228    fs_tmp.type(file_type::socket);
229  else
230    fs_tmp.type(file_type::unknown);
231
232  fs_tmp.permissions(detail::posix_get_perms(path_stat));
233  return fs_tmp;
234}
235
236inline file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) {
237  error_code m_ec;
238  if (detail::stat(p.c_str(), &path_stat) == -1)
239    m_ec = detail::get_last_error();
240  return create_file_status(m_ec, p, path_stat, ec);
241}
242
243inline file_status posix_stat(path const& p, error_code* ec) {
244  StatT path_stat;
245  return posix_stat(p, path_stat, ec);
246}
247
248inline file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) {
249  error_code m_ec;
250  if (detail::lstat(p.c_str(), &path_stat) == -1)
251    m_ec = detail::get_last_error();
252  return create_file_status(m_ec, p, path_stat, ec);
253}
254
255inline file_status posix_lstat(path const& p, error_code* ec) {
256  StatT path_stat;
257  return posix_lstat(p, path_stat, ec);
258}
259
260// http://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html
261inline bool posix_ftruncate(const FileDescriptor& fd, off_t to_size, error_code& ec) {
262  if (detail::ftruncate(fd.fd, to_size) == -1) {
263    ec = get_last_error();
264    return true;
265  }
266  ec.clear();
267  return false;
268}
269
270inline bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) {
271  if (detail::fchmod(fd.fd, st.st_mode) == -1) {
272    ec = get_last_error();
273    return true;
274  }
275  ec.clear();
276  return false;
277}
278
279inline bool stat_equivalent(const StatT& st1, const StatT& st2) {
280  return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
281}
282
283inline file_status FileDescriptor::refresh_status(error_code& ec) {
284  // FD must be open and good.
285  m_status = file_status{};
286  m_stat   = {};
287  error_code m_ec;
288  if (detail::fstat(fd, &m_stat) == -1)
289    m_ec = get_last_error();
290  m_status = create_file_status(m_ec, name, m_stat, &ec);
291  return m_status;
292}
293
294} // namespace detail
295
296_LIBCPP_END_NAMESPACE_FILESYSTEM
297
298#endif // FILESYSTEM_FILE_DESCRIPTOR_H