Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame^] | 1 | // Copyright (c) FIRST and other WPILib contributors. |
| 2 | // Open Source Software; you can modify and/or share it under the terms of |
| 3 | // the WPILib BSD license file in the root directory of this project. |
| 4 | |
| 5 | //===----------------------------------------------------------------------===// |
| 6 | // |
| 7 | // The LLVM Compiler Infrastructure |
| 8 | // |
| 9 | // This file is distributed under the University of Illinois Open Source |
| 10 | // License. See LICENSE.TXT for details. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include <cassert> |
| 15 | |
| 16 | #ifdef _WIN32 |
| 17 | #include <fcntl.h> |
| 18 | #include <io.h> |
| 19 | #include <sys/types.h> |
| 20 | // Require at least Windows 7 API. |
| 21 | #define _WIN32_WINNT 0x0601 |
| 22 | #define _WIN32_IE 0x0800 // MinGW at it again. FIXME: verify if still needed. |
| 23 | #define WIN32_LEAN_AND_MEAN |
| 24 | #ifndef NOMINMAX |
| 25 | #define NOMINMAX |
| 26 | #endif |
| 27 | |
| 28 | #define WIN32_NO_STATUS |
| 29 | #include <windows.h> |
| 30 | #undef WIN32_NO_STATUS |
| 31 | #include <winternl.h> |
| 32 | #include <ntstatus.h> |
| 33 | |
| 34 | #include <shellapi.h> |
| 35 | #include <shlobj.h> |
| 36 | |
| 37 | #include "wpi/WindowsError.h" |
| 38 | |
| 39 | #else // _WIN32 |
| 40 | |
| 41 | #include <fcntl.h> |
| 42 | #include <unistd.h> |
| 43 | |
| 44 | #endif // _WIN32 |
| 45 | |
| 46 | #if defined(__APPLE__) |
| 47 | #include <Availability.h> |
| 48 | #endif |
| 49 | #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) \ |
| 50 | || (defined(__cplusplus) && __cplusplus >= 201703L)) \ |
| 51 | && defined(__has_include) |
| 52 | #if __has_include(<filesystem>) \ |
| 53 | && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \ |
| 54 | || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) \ |
| 55 | && (defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 10 \ |
| 56 | || (__GNUC__ >= 9 && __GNUC_MINOR__ >= 1)) |
| 57 | #define GHC_USE_STD_FS |
| 58 | #endif |
| 59 | #endif |
| 60 | #ifndef GHC_USE_STD_FS |
| 61 | // #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE |
| 62 | #define GHC_FILESYSTEM_IMPLEMENTATION |
| 63 | #include "wpi/ghc/filesystem.hpp" |
| 64 | #endif |
| 65 | |
| 66 | #include "wpi/Errno.h" |
| 67 | #include "wpi/ErrorHandling.h" |
| 68 | #include "wpi/WindowsError.h" |
| 69 | #include "wpi/fs.h" |
| 70 | |
| 71 | namespace fs { |
| 72 | |
| 73 | #ifdef _WIN32 |
| 74 | |
| 75 | #ifdef _MSC_VER |
| 76 | #pragma comment(lib, "shell32.lib") |
| 77 | #pragma comment(lib, "ole32.lib") |
| 78 | #pragma warning(push) |
| 79 | #pragma warning(disable : 4244 4267 4146) |
| 80 | #endif |
| 81 | |
| 82 | const file_t kInvalidFile = INVALID_HANDLE_VALUE; |
| 83 | |
| 84 | static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) { |
| 85 | // This is a compatibility hack. Really we should respect the creation |
| 86 | // disposition, but a lot of old code relied on the implicit assumption that |
| 87 | // OF_Append implied it would open an existing file. Since the disposition is |
| 88 | // now explicit and defaults to CD_CreateAlways, this assumption would cause |
| 89 | // any usage of OF_Append to append to a new file, even if the file already |
| 90 | // existed. A better solution might have two new creation dispositions: |
| 91 | // CD_AppendAlways and CD_AppendNew. This would also address the problem of |
| 92 | // OF_Append being used on a read-only descriptor, which doesn't make sense. |
| 93 | if (Flags & OF_Append) |
| 94 | return OPEN_ALWAYS; |
| 95 | |
| 96 | switch (Disp) { |
| 97 | case CD_CreateAlways: |
| 98 | return CREATE_ALWAYS; |
| 99 | case CD_CreateNew: |
| 100 | return CREATE_NEW; |
| 101 | case CD_OpenAlways: |
| 102 | return OPEN_ALWAYS; |
| 103 | case CD_OpenExisting: |
| 104 | return OPEN_EXISTING; |
| 105 | } |
| 106 | wpi_unreachable("unreachable!"); |
| 107 | } |
| 108 | |
| 109 | static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) { |
| 110 | DWORD Result = 0; |
| 111 | if (Access & FA_Read) |
| 112 | Result |= GENERIC_READ; |
| 113 | if (Access & FA_Write) |
| 114 | Result |= GENERIC_WRITE; |
| 115 | if (Flags & OF_Delete) |
| 116 | Result |= DELETE; |
| 117 | if (Flags & OF_UpdateAtime) |
| 118 | Result |= FILE_WRITE_ATTRIBUTES; |
| 119 | return Result; |
| 120 | } |
| 121 | |
| 122 | static file_t openFileInternal(const path& Path, std::error_code& EC, |
| 123 | DWORD Disp, DWORD Access, DWORD Flags, |
| 124 | bool Inherit = false) { |
| 125 | SECURITY_ATTRIBUTES SA; |
| 126 | SA.nLength = sizeof(SA); |
| 127 | SA.lpSecurityDescriptor = nullptr; |
| 128 | SA.bInheritHandle = Inherit; |
| 129 | |
| 130 | HANDLE H = |
| 131 | ::CreateFileW(Path.c_str(), Access, |
| 132 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA, |
| 133 | Disp, Flags, NULL); |
| 134 | if (H == INVALID_HANDLE_VALUE) { |
| 135 | DWORD LastError = ::GetLastError(); |
| 136 | EC = wpi::mapWindowsError(LastError); |
| 137 | // Provide a better error message when trying to open directories. |
| 138 | // This only runs if we failed to open the file, so there is probably |
| 139 | // no performances issues. |
| 140 | if (LastError != ERROR_ACCESS_DENIED) { |
| 141 | return kInvalidFile; |
| 142 | } |
| 143 | if (is_directory(Path)) { |
| 144 | EC = std::make_error_code(std::errc::is_a_directory); |
| 145 | } |
| 146 | return kInvalidFile; |
| 147 | } |
| 148 | EC = std::error_code(); |
| 149 | return H; |
| 150 | } |
| 151 | |
| 152 | static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) { |
| 153 | FILE_DISPOSITION_INFO Disposition; |
| 154 | Disposition.DeleteFile = Delete; |
| 155 | if (!::SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition, |
| 156 | sizeof(Disposition))) |
| 157 | return wpi::mapWindowsError(::GetLastError()); |
| 158 | return std::error_code(); |
| 159 | } |
| 160 | |
| 161 | file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp, |
| 162 | FileAccess Access, OpenFlags Flags, unsigned Mode) { |
| 163 | // Verify that we don't have both "append" and "excl". |
| 164 | assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) && |
| 165 | "Cannot specify both 'CreateNew' and 'Append' file creation flags!"); |
| 166 | |
| 167 | DWORD NativeDisp = nativeDisposition(Disp, Flags); |
| 168 | DWORD NativeAccess = nativeAccess(Access, Flags); |
| 169 | |
| 170 | bool Inherit = false; |
| 171 | if (Flags & OF_ChildInherit) { |
| 172 | Inherit = true; |
| 173 | } |
| 174 | |
| 175 | file_t Result = openFileInternal(Path, EC, NativeDisp, NativeAccess, |
| 176 | FILE_ATTRIBUTE_NORMAL, Inherit); |
| 177 | if (EC) { |
| 178 | return Result; |
| 179 | } |
| 180 | |
| 181 | if (Flags & OF_UpdateAtime) { |
| 182 | FILETIME FileTime; |
| 183 | SYSTEMTIME SystemTime; |
| 184 | ::GetSystemTime(&SystemTime); |
| 185 | if (::SystemTimeToFileTime(&SystemTime, &FileTime) == 0 || |
| 186 | ::SetFileTime(Result, NULL, &FileTime, NULL) == 0) { |
| 187 | DWORD LastError = ::GetLastError(); |
| 188 | ::CloseHandle(Result); |
| 189 | EC = wpi::mapWindowsError(LastError); |
| 190 | return kInvalidFile; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | if (Flags & OF_Delete) { |
| 195 | if ((EC = setDeleteDisposition(Result, true))) { |
| 196 | ::CloseHandle(Result); |
| 197 | return kInvalidFile; |
| 198 | } |
| 199 | } |
| 200 | return Result; |
| 201 | } |
| 202 | |
| 203 | file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) { |
| 204 | return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags); |
| 205 | } |
| 206 | |
| 207 | int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) { |
| 208 | if (F == kInvalidFile) { |
| 209 | EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE); |
| 210 | return -1; |
| 211 | } |
| 212 | |
| 213 | int CrtOpenFlags = 0; |
| 214 | if (Flags & OF_Append) { |
| 215 | CrtOpenFlags |= _O_APPEND; |
| 216 | } |
| 217 | |
| 218 | if (Flags & OF_Text) { |
| 219 | CrtOpenFlags |= _O_TEXT; |
| 220 | } |
| 221 | |
| 222 | int ResultFD = ::_open_osfhandle(intptr_t(F), CrtOpenFlags); |
| 223 | if (ResultFD == -1) { |
| 224 | ::CloseHandle(F); |
| 225 | EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE); |
| 226 | return -1; |
| 227 | } |
| 228 | |
| 229 | EC = std::error_code(); |
| 230 | F = kInvalidFile; |
| 231 | return ResultFD; |
| 232 | } |
| 233 | |
| 234 | void CloseFile(file_t& F) { |
| 235 | ::CloseHandle(F); |
| 236 | F = kInvalidFile; |
| 237 | } |
| 238 | |
| 239 | #else // _WIN32 |
| 240 | |
| 241 | const file_t kInvalidFile = -1; |
| 242 | |
| 243 | static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags, |
| 244 | FileAccess Access) { |
| 245 | int Result = 0; |
| 246 | if (Access == FA_Read) { |
| 247 | Result |= O_RDONLY; |
| 248 | } else if (Access == FA_Write) { |
| 249 | Result |= O_WRONLY; |
| 250 | } else if (Access == (FA_Read | FA_Write)) { |
| 251 | Result |= O_RDWR; |
| 252 | } |
| 253 | |
| 254 | // This is for compatibility with old code that assumed F_Append implied |
| 255 | // would open an existing file. See Windows/Path.inc for a longer comment. |
| 256 | if (Flags & F_Append) { |
| 257 | Disp = CD_OpenAlways; |
| 258 | } |
| 259 | |
| 260 | if (Disp == CD_CreateNew) { |
| 261 | Result |= O_CREAT; // Create if it doesn't exist. |
| 262 | Result |= O_EXCL; // Fail if it does. |
| 263 | } else if (Disp == CD_CreateAlways) { |
| 264 | Result |= O_CREAT; // Create if it doesn't exist. |
| 265 | Result |= O_TRUNC; // Truncate if it does. |
| 266 | } else if (Disp == CD_OpenAlways) { |
| 267 | Result |= O_CREAT; // Create if it doesn't exist. |
| 268 | } else if (Disp == CD_OpenExisting) { |
| 269 | // Nothing special, just don't add O_CREAT and we get these semantics. |
| 270 | } |
| 271 | |
| 272 | if (Flags & F_Append) { |
| 273 | Result |= O_APPEND; |
| 274 | } |
| 275 | |
| 276 | #ifdef O_CLOEXEC |
| 277 | if (!(Flags & OF_ChildInherit)) { |
| 278 | Result |= O_CLOEXEC; |
| 279 | } |
| 280 | #endif |
| 281 | |
| 282 | return Result; |
| 283 | } |
| 284 | |
| 285 | file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp, |
| 286 | FileAccess Access, OpenFlags Flags, unsigned Mode) { |
| 287 | int OpenFlags = nativeOpenFlags(Disp, Flags, Access); |
| 288 | file_t ResultFD = kInvalidFile; |
| 289 | |
| 290 | // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal |
| 291 | // when open is overloaded, such as in Bionic. |
| 292 | auto Open = [&]() { return ::open(Path.c_str(), OpenFlags, Mode); }; |
| 293 | if ((ResultFD = wpi::sys::RetryAfterSignal(-1, Open)) < 0) { |
| 294 | EC = std::error_code(errno, std::generic_category()); |
| 295 | return kInvalidFile; |
| 296 | } |
| 297 | #ifndef O_CLOEXEC |
| 298 | if (!(Flags & OF_ChildInherit)) { |
| 299 | int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC); |
| 300 | (void)r; |
| 301 | assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed"); |
| 302 | } |
| 303 | #endif |
| 304 | EC = std::error_code(); |
| 305 | return ResultFD; |
| 306 | } |
| 307 | |
| 308 | file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) { |
| 309 | return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags, 0666); |
| 310 | } |
| 311 | |
| 312 | int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) { |
| 313 | int fd = F; |
| 314 | F = kInvalidFile; |
| 315 | EC = std::error_code(); |
| 316 | return fd; |
| 317 | } |
| 318 | |
| 319 | void CloseFile(file_t& F) { |
| 320 | ::close(F); |
| 321 | F = kInvalidFile; |
| 322 | } |
| 323 | |
| 324 | #endif // _WIN32 |
| 325 | |
| 326 | } // namespace fs |