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 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame] | 46 | #include "wpi/Errno.h" |
| 47 | #include "wpi/ErrorHandling.h" |
| 48 | #include "wpi/WindowsError.h" |
| 49 | #include "wpi/fs.h" |
| 50 | |
| 51 | namespace fs { |
| 52 | |
| 53 | #ifdef _WIN32 |
| 54 | |
| 55 | #ifdef _MSC_VER |
| 56 | #pragma comment(lib, "shell32.lib") |
| 57 | #pragma comment(lib, "ole32.lib") |
| 58 | #pragma warning(push) |
| 59 | #pragma warning(disable : 4244 4267 4146) |
| 60 | #endif |
| 61 | |
| 62 | const file_t kInvalidFile = INVALID_HANDLE_VALUE; |
| 63 | |
| 64 | static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) { |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame] | 65 | switch (Disp) { |
| 66 | case CD_CreateAlways: |
| 67 | return CREATE_ALWAYS; |
| 68 | case CD_CreateNew: |
| 69 | return CREATE_NEW; |
| 70 | case CD_OpenAlways: |
| 71 | return OPEN_ALWAYS; |
| 72 | case CD_OpenExisting: |
| 73 | return OPEN_EXISTING; |
| 74 | } |
| 75 | wpi_unreachable("unreachable!"); |
| 76 | } |
| 77 | |
| 78 | static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) { |
| 79 | DWORD Result = 0; |
| 80 | if (Access & FA_Read) |
| 81 | Result |= GENERIC_READ; |
| 82 | if (Access & FA_Write) |
| 83 | Result |= GENERIC_WRITE; |
| 84 | if (Flags & OF_Delete) |
| 85 | Result |= DELETE; |
| 86 | if (Flags & OF_UpdateAtime) |
| 87 | Result |= FILE_WRITE_ATTRIBUTES; |
| 88 | return Result; |
| 89 | } |
| 90 | |
| 91 | static file_t openFileInternal(const path& Path, std::error_code& EC, |
| 92 | DWORD Disp, DWORD Access, DWORD Flags, |
| 93 | bool Inherit = false) { |
| 94 | SECURITY_ATTRIBUTES SA; |
| 95 | SA.nLength = sizeof(SA); |
| 96 | SA.lpSecurityDescriptor = nullptr; |
| 97 | SA.bInheritHandle = Inherit; |
| 98 | |
| 99 | HANDLE H = |
| 100 | ::CreateFileW(Path.c_str(), Access, |
| 101 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA, |
| 102 | Disp, Flags, NULL); |
| 103 | if (H == INVALID_HANDLE_VALUE) { |
| 104 | DWORD LastError = ::GetLastError(); |
| 105 | EC = wpi::mapWindowsError(LastError); |
| 106 | // Provide a better error message when trying to open directories. |
| 107 | // This only runs if we failed to open the file, so there is probably |
| 108 | // no performances issues. |
| 109 | if (LastError != ERROR_ACCESS_DENIED) { |
| 110 | return kInvalidFile; |
| 111 | } |
| 112 | if (is_directory(Path)) { |
| 113 | EC = std::make_error_code(std::errc::is_a_directory); |
| 114 | } |
| 115 | return kInvalidFile; |
| 116 | } |
| 117 | EC = std::error_code(); |
| 118 | return H; |
| 119 | } |
| 120 | |
| 121 | static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) { |
| 122 | FILE_DISPOSITION_INFO Disposition; |
| 123 | Disposition.DeleteFile = Delete; |
| 124 | if (!::SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition, |
| 125 | sizeof(Disposition))) |
| 126 | return wpi::mapWindowsError(::GetLastError()); |
| 127 | return std::error_code(); |
| 128 | } |
| 129 | |
| 130 | file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp, |
| 131 | FileAccess Access, OpenFlags Flags, unsigned Mode) { |
| 132 | // Verify that we don't have both "append" and "excl". |
| 133 | assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) && |
| 134 | "Cannot specify both 'CreateNew' and 'Append' file creation flags!"); |
| 135 | |
| 136 | DWORD NativeDisp = nativeDisposition(Disp, Flags); |
| 137 | DWORD NativeAccess = nativeAccess(Access, Flags); |
| 138 | |
| 139 | bool Inherit = false; |
| 140 | if (Flags & OF_ChildInherit) { |
| 141 | Inherit = true; |
| 142 | } |
| 143 | |
| 144 | file_t Result = openFileInternal(Path, EC, NativeDisp, NativeAccess, |
| 145 | FILE_ATTRIBUTE_NORMAL, Inherit); |
| 146 | if (EC) { |
| 147 | return Result; |
| 148 | } |
| 149 | |
| 150 | if (Flags & OF_UpdateAtime) { |
| 151 | FILETIME FileTime; |
| 152 | SYSTEMTIME SystemTime; |
| 153 | ::GetSystemTime(&SystemTime); |
| 154 | if (::SystemTimeToFileTime(&SystemTime, &FileTime) == 0 || |
| 155 | ::SetFileTime(Result, NULL, &FileTime, NULL) == 0) { |
| 156 | DWORD LastError = ::GetLastError(); |
| 157 | ::CloseHandle(Result); |
| 158 | EC = wpi::mapWindowsError(LastError); |
| 159 | return kInvalidFile; |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | if (Flags & OF_Delete) { |
| 164 | if ((EC = setDeleteDisposition(Result, true))) { |
| 165 | ::CloseHandle(Result); |
| 166 | return kInvalidFile; |
| 167 | } |
| 168 | } |
| 169 | return Result; |
| 170 | } |
| 171 | |
| 172 | file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) { |
| 173 | return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags); |
| 174 | } |
| 175 | |
| 176 | int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) { |
| 177 | if (F == kInvalidFile) { |
| 178 | EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE); |
| 179 | return -1; |
| 180 | } |
| 181 | |
| 182 | int CrtOpenFlags = 0; |
| 183 | if (Flags & OF_Append) { |
| 184 | CrtOpenFlags |= _O_APPEND; |
| 185 | } |
| 186 | |
| 187 | if (Flags & OF_Text) { |
| 188 | CrtOpenFlags |= _O_TEXT; |
| 189 | } |
| 190 | |
| 191 | int ResultFD = ::_open_osfhandle(intptr_t(F), CrtOpenFlags); |
| 192 | if (ResultFD == -1) { |
| 193 | ::CloseHandle(F); |
| 194 | EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE); |
| 195 | return -1; |
| 196 | } |
| 197 | |
| 198 | EC = std::error_code(); |
| 199 | F = kInvalidFile; |
| 200 | return ResultFD; |
| 201 | } |
| 202 | |
| 203 | void CloseFile(file_t& F) { |
| 204 | ::CloseHandle(F); |
| 205 | F = kInvalidFile; |
| 206 | } |
| 207 | |
| 208 | #else // _WIN32 |
| 209 | |
| 210 | const file_t kInvalidFile = -1; |
| 211 | |
| 212 | static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags, |
| 213 | FileAccess Access) { |
| 214 | int Result = 0; |
| 215 | if (Access == FA_Read) { |
| 216 | Result |= O_RDONLY; |
| 217 | } else if (Access == FA_Write) { |
| 218 | Result |= O_WRONLY; |
| 219 | } else if (Access == (FA_Read | FA_Write)) { |
| 220 | Result |= O_RDWR; |
| 221 | } |
| 222 | |
Austin Schuh | 812d0d1 | 2021-11-04 20:16:48 -0700 | [diff] [blame] | 223 | if (Disp == CD_CreateNew) { |
| 224 | Result |= O_CREAT; // Create if it doesn't exist. |
| 225 | Result |= O_EXCL; // Fail if it does. |
| 226 | } else if (Disp == CD_CreateAlways) { |
| 227 | Result |= O_CREAT; // Create if it doesn't exist. |
| 228 | Result |= O_TRUNC; // Truncate if it does. |
| 229 | } else if (Disp == CD_OpenAlways) { |
| 230 | Result |= O_CREAT; // Create if it doesn't exist. |
| 231 | } else if (Disp == CD_OpenExisting) { |
| 232 | // Nothing special, just don't add O_CREAT and we get these semantics. |
| 233 | } |
| 234 | |
| 235 | if (Flags & F_Append) { |
| 236 | Result |= O_APPEND; |
| 237 | } |
| 238 | |
| 239 | #ifdef O_CLOEXEC |
| 240 | if (!(Flags & OF_ChildInherit)) { |
| 241 | Result |= O_CLOEXEC; |
| 242 | } |
| 243 | #endif |
| 244 | |
| 245 | return Result; |
| 246 | } |
| 247 | |
| 248 | file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp, |
| 249 | FileAccess Access, OpenFlags Flags, unsigned Mode) { |
| 250 | int OpenFlags = nativeOpenFlags(Disp, Flags, Access); |
| 251 | file_t ResultFD = kInvalidFile; |
| 252 | |
| 253 | // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal |
| 254 | // when open is overloaded, such as in Bionic. |
| 255 | auto Open = [&]() { return ::open(Path.c_str(), OpenFlags, Mode); }; |
| 256 | if ((ResultFD = wpi::sys::RetryAfterSignal(-1, Open)) < 0) { |
| 257 | EC = std::error_code(errno, std::generic_category()); |
| 258 | return kInvalidFile; |
| 259 | } |
| 260 | #ifndef O_CLOEXEC |
| 261 | if (!(Flags & OF_ChildInherit)) { |
| 262 | int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC); |
| 263 | (void)r; |
| 264 | assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed"); |
| 265 | } |
| 266 | #endif |
| 267 | EC = std::error_code(); |
| 268 | return ResultFD; |
| 269 | } |
| 270 | |
| 271 | file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) { |
| 272 | return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags, 0666); |
| 273 | } |
| 274 | |
| 275 | int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) { |
| 276 | int fd = F; |
| 277 | F = kInvalidFile; |
| 278 | EC = std::error_code(); |
| 279 | return fd; |
| 280 | } |
| 281 | |
| 282 | void CloseFile(file_t& F) { |
| 283 | ::close(F); |
| 284 | F = kInvalidFile; |
| 285 | } |
| 286 | |
| 287 | #endif // _WIN32 |
| 288 | |
| 289 | } // namespace fs |