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