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, 75 error_code& ec) { 76 struct dirent* dir_entry_ptr = nullptr; 77 errno = 0; // zero errno in order to detect errors 78 ec.clear(); 79 if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) { 80 if (errno) 81 ec = capture_errno(); 82 return {}; 83 } else { 84 return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)}; 85 } 86 } 87 88 #else // _LIBCPP_WIN32API 89 90 inline file_type get_file_type(const WIN32_FIND_DATAW& data) { 91 if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && 92 data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) 93 return file_type::symlink; 94 if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 95 return file_type::directory; 96 return file_type::regular; 97 } 98 inline uintmax_t get_file_size(const WIN32_FIND_DATAW& data) { 99 return (static_cast<uint64_t>(data.nFileSizeHigh) << 32) + data.nFileSizeLow; 100 } 101 inline file_time_type get_write_time(const WIN32_FIND_DATAW& data) { 102 ULARGE_INTEGER tmp; 103 const FILETIME& time = data.ftLastWriteTime; 104 tmp.u.LowPart = time.dwLowDateTime; 105 tmp.u.HighPart = time.dwHighDateTime; 106 return file_time_type(file_time_type::duration(tmp.QuadPart)); 107 } 108 109 #endif // !_LIBCPP_WIN32API 110 111 // POSIX HELPERS 112 113 using value_type = path::value_type; 114 using string_type = path::string_type; 115 116 struct FileDescriptor { 117 const path& name; 118 int fd = -1; 119 StatT m_stat; 120 file_status m_status; 121 122 template <class... Args> 123 static FileDescriptor create(const path* p, error_code& ec, Args... args) { 124 ec.clear(); 125 int fd; 126 #ifdef _LIBCPP_WIN32API 127 // TODO: most of the filesystem implementation uses native Win32 calls 128 // (mostly via posix_compat.h). However, here we use the C-runtime APIs to 129 // open a file, because we subsequently pass the C-runtime fd to 130 // `std::[io]fstream::__open(int fd)` in order to implement copy_file. 131 // 132 // Because we're calling the windows C-runtime, win32 error codes are 133 // translated into C error numbers by the C runtime, and returned in errno, 134 // rather than being accessible directly via GetLastError. 135 // 136 // Ideally copy_file should be calling the Win32 CopyFile2 function, which 137 // works on paths, not open files -- at which point this FileDescriptor type 138 // will no longer be needed on windows at all. 139 fd = ::_wopen(p->c_str(), args...); 140 #else 141 fd = open(p->c_str(), args...); 142 #endif 143 144 if (fd == -1) { 145 ec = capture_errno(); 146 return FileDescriptor{p}; 147 } 148 return FileDescriptor(p, fd); 149 } 150 151 template <class... Args> 152 static FileDescriptor create_with_status(const path* p, error_code& ec, 153 Args... args) { 154 FileDescriptor fd = create(p, ec, args...); 155 if (!ec) 156 fd.refresh_status(ec); 157 158 return fd; 159 } 160 161 file_status get_status() const { return m_status; } 162 StatT const& get_stat() const { return m_stat; } 163 164 bool status_known() const { return _VSTD_FS::status_known(m_status); } 165 166 file_status refresh_status(error_code& ec); 167 168 void close() noexcept { 169 if (fd != -1) { 170 #ifdef _LIBCPP_WIN32API 171 ::_close(fd); 172 #else 173 ::close(fd); 174 #endif 175 // FIXME: shouldn't this return an error_code? 176 } 177 fd = -1; 178 } 179 180 FileDescriptor(FileDescriptor&& other) 181 : name(other.name), fd(other.fd), m_stat(other.m_stat), 182 m_status(other.m_status) { 183 other.fd = -1; 184 other.m_status = file_status{}; 185 } 186 187 ~FileDescriptor() { close(); } 188 189 FileDescriptor(FileDescriptor const&) = delete; 190 FileDescriptor& operator=(FileDescriptor const&) = delete; 191 192 private: 193 explicit FileDescriptor(const path* p, int descriptor = -1) : name(*p), fd(descriptor) {} 194 }; 195 196 inline perms posix_get_perms(const StatT& st) noexcept { 197 return static_cast<perms>(st.st_mode) & perms::mask; 198 } 199 200 inline file_status create_file_status(error_code& m_ec, path const& p, 201 const StatT& path_stat, error_code* ec) { 202 if (ec) 203 *ec = m_ec; 204 if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) { 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::capture_errno(); 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::capture_errno(); 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 = capture_errno(); 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 = capture_errno(); 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 = capture_errno(); 290 m_status = create_file_status(m_ec, name, m_stat, &ec); 291 return m_status; 292 } 293 294 } // end namespace detail 295 296 _LIBCPP_END_NAMESPACE_FILESYSTEM 297 298 #endif // FILESYSTEM_FILE_DESCRIPTOR_H 299