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 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 } 107 inline 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 118 using value_type = path::value_type; 119 using string_type = path::string_type; 120 121 struct 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 195 private: 196 explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {} 197 }; 198 199 inline perms posix_get_perms(const StatT& st) noexcept { return static_cast<perms>(st.st_mode) & perms::mask; } 200 201 inline 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 236 inline 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 243 inline file_status posix_stat(path const& p, error_code* ec) { 244 StatT path_stat; 245 return posix_stat(p, path_stat, ec); 246 } 247 248 inline 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 255 inline 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 261 inline 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 270 inline 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 279 inline 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 283 inline 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 299