| // Copyright (c) FIRST and other WPILib contributors. |
| // Open Source Software; you can modify and/or share it under the terms of |
| // the WPILib BSD license file in the root directory of this project. |
| |
| //===----------------------------------------------------------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <cassert> |
| |
| #ifdef _WIN32 |
| #include <fcntl.h> |
| #include <io.h> |
| #include <sys/types.h> |
| // Require at least Windows 7 API. |
| #define _WIN32_WINNT 0x0601 |
| #define _WIN32_IE 0x0800 // MinGW at it again. FIXME: verify if still needed. |
| #define WIN32_LEAN_AND_MEAN |
| #ifndef NOMINMAX |
| #define NOMINMAX |
| #endif |
| |
| #define WIN32_NO_STATUS |
| #include <windows.h> |
| #undef WIN32_NO_STATUS |
| #include <winternl.h> |
| #include <ntstatus.h> |
| |
| #include <shellapi.h> |
| #include <shlobj.h> |
| |
| #include "wpi/WindowsError.h" |
| |
| #else // _WIN32 |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| |
| #endif // _WIN32 |
| |
| #if defined(__APPLE__) |
| #include <Availability.h> |
| #endif |
| #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) \ |
| || (defined(__cplusplus) && __cplusplus >= 201703L)) \ |
| && defined(__has_include) |
| #if __has_include(<filesystem>) \ |
| && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \ |
| || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) \ |
| && (defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 10 \ |
| || (__GNUC__ >= 9 && __GNUC_MINOR__ >= 1)) |
| #define GHC_USE_STD_FS |
| #endif |
| #endif |
| #ifndef GHC_USE_STD_FS |
| // #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE |
| #define GHC_FILESYSTEM_IMPLEMENTATION |
| #include "wpi/ghc/filesystem.hpp" |
| #endif |
| |
| #include "wpi/Errno.h" |
| #include "wpi/ErrorHandling.h" |
| #include "wpi/WindowsError.h" |
| #include "wpi/fs.h" |
| |
| namespace fs { |
| |
| #ifdef _WIN32 |
| |
| #ifdef _MSC_VER |
| #pragma comment(lib, "shell32.lib") |
| #pragma comment(lib, "ole32.lib") |
| #pragma warning(push) |
| #pragma warning(disable : 4244 4267 4146) |
| #endif |
| |
| const file_t kInvalidFile = INVALID_HANDLE_VALUE; |
| |
| static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) { |
| // This is a compatibility hack. Really we should respect the creation |
| // disposition, but a lot of old code relied on the implicit assumption that |
| // OF_Append implied it would open an existing file. Since the disposition is |
| // now explicit and defaults to CD_CreateAlways, this assumption would cause |
| // any usage of OF_Append to append to a new file, even if the file already |
| // existed. A better solution might have two new creation dispositions: |
| // CD_AppendAlways and CD_AppendNew. This would also address the problem of |
| // OF_Append being used on a read-only descriptor, which doesn't make sense. |
| if (Flags & OF_Append) |
| return OPEN_ALWAYS; |
| |
| switch (Disp) { |
| case CD_CreateAlways: |
| return CREATE_ALWAYS; |
| case CD_CreateNew: |
| return CREATE_NEW; |
| case CD_OpenAlways: |
| return OPEN_ALWAYS; |
| case CD_OpenExisting: |
| return OPEN_EXISTING; |
| } |
| wpi_unreachable("unreachable!"); |
| } |
| |
| static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) { |
| DWORD Result = 0; |
| if (Access & FA_Read) |
| Result |= GENERIC_READ; |
| if (Access & FA_Write) |
| Result |= GENERIC_WRITE; |
| if (Flags & OF_Delete) |
| Result |= DELETE; |
| if (Flags & OF_UpdateAtime) |
| Result |= FILE_WRITE_ATTRIBUTES; |
| return Result; |
| } |
| |
| static file_t openFileInternal(const path& Path, std::error_code& EC, |
| DWORD Disp, DWORD Access, DWORD Flags, |
| bool Inherit = false) { |
| SECURITY_ATTRIBUTES SA; |
| SA.nLength = sizeof(SA); |
| SA.lpSecurityDescriptor = nullptr; |
| SA.bInheritHandle = Inherit; |
| |
| HANDLE H = |
| ::CreateFileW(Path.c_str(), Access, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA, |
| Disp, Flags, NULL); |
| if (H == INVALID_HANDLE_VALUE) { |
| DWORD LastError = ::GetLastError(); |
| EC = wpi::mapWindowsError(LastError); |
| // Provide a better error message when trying to open directories. |
| // This only runs if we failed to open the file, so there is probably |
| // no performances issues. |
| if (LastError != ERROR_ACCESS_DENIED) { |
| return kInvalidFile; |
| } |
| if (is_directory(Path)) { |
| EC = std::make_error_code(std::errc::is_a_directory); |
| } |
| return kInvalidFile; |
| } |
| EC = std::error_code(); |
| return H; |
| } |
| |
| static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) { |
| FILE_DISPOSITION_INFO Disposition; |
| Disposition.DeleteFile = Delete; |
| if (!::SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition, |
| sizeof(Disposition))) |
| return wpi::mapWindowsError(::GetLastError()); |
| return std::error_code(); |
| } |
| |
| file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp, |
| FileAccess Access, OpenFlags Flags, unsigned Mode) { |
| // Verify that we don't have both "append" and "excl". |
| assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) && |
| "Cannot specify both 'CreateNew' and 'Append' file creation flags!"); |
| |
| DWORD NativeDisp = nativeDisposition(Disp, Flags); |
| DWORD NativeAccess = nativeAccess(Access, Flags); |
| |
| bool Inherit = false; |
| if (Flags & OF_ChildInherit) { |
| Inherit = true; |
| } |
| |
| file_t Result = openFileInternal(Path, EC, NativeDisp, NativeAccess, |
| FILE_ATTRIBUTE_NORMAL, Inherit); |
| if (EC) { |
| return Result; |
| } |
| |
| if (Flags & OF_UpdateAtime) { |
| FILETIME FileTime; |
| SYSTEMTIME SystemTime; |
| ::GetSystemTime(&SystemTime); |
| if (::SystemTimeToFileTime(&SystemTime, &FileTime) == 0 || |
| ::SetFileTime(Result, NULL, &FileTime, NULL) == 0) { |
| DWORD LastError = ::GetLastError(); |
| ::CloseHandle(Result); |
| EC = wpi::mapWindowsError(LastError); |
| return kInvalidFile; |
| } |
| } |
| |
| if (Flags & OF_Delete) { |
| if ((EC = setDeleteDisposition(Result, true))) { |
| ::CloseHandle(Result); |
| return kInvalidFile; |
| } |
| } |
| return Result; |
| } |
| |
| file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) { |
| return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags); |
| } |
| |
| int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) { |
| if (F == kInvalidFile) { |
| EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE); |
| return -1; |
| } |
| |
| int CrtOpenFlags = 0; |
| if (Flags & OF_Append) { |
| CrtOpenFlags |= _O_APPEND; |
| } |
| |
| if (Flags & OF_Text) { |
| CrtOpenFlags |= _O_TEXT; |
| } |
| |
| int ResultFD = ::_open_osfhandle(intptr_t(F), CrtOpenFlags); |
| if (ResultFD == -1) { |
| ::CloseHandle(F); |
| EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE); |
| return -1; |
| } |
| |
| EC = std::error_code(); |
| F = kInvalidFile; |
| return ResultFD; |
| } |
| |
| void CloseFile(file_t& F) { |
| ::CloseHandle(F); |
| F = kInvalidFile; |
| } |
| |
| #else // _WIN32 |
| |
| const file_t kInvalidFile = -1; |
| |
| static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags, |
| FileAccess Access) { |
| int Result = 0; |
| if (Access == FA_Read) { |
| Result |= O_RDONLY; |
| } else if (Access == FA_Write) { |
| Result |= O_WRONLY; |
| } else if (Access == (FA_Read | FA_Write)) { |
| Result |= O_RDWR; |
| } |
| |
| // This is for compatibility with old code that assumed F_Append implied |
| // would open an existing file. See Windows/Path.inc for a longer comment. |
| if (Flags & F_Append) { |
| Disp = CD_OpenAlways; |
| } |
| |
| if (Disp == CD_CreateNew) { |
| Result |= O_CREAT; // Create if it doesn't exist. |
| Result |= O_EXCL; // Fail if it does. |
| } else if (Disp == CD_CreateAlways) { |
| Result |= O_CREAT; // Create if it doesn't exist. |
| Result |= O_TRUNC; // Truncate if it does. |
| } else if (Disp == CD_OpenAlways) { |
| Result |= O_CREAT; // Create if it doesn't exist. |
| } else if (Disp == CD_OpenExisting) { |
| // Nothing special, just don't add O_CREAT and we get these semantics. |
| } |
| |
| if (Flags & F_Append) { |
| Result |= O_APPEND; |
| } |
| |
| #ifdef O_CLOEXEC |
| if (!(Flags & OF_ChildInherit)) { |
| Result |= O_CLOEXEC; |
| } |
| #endif |
| |
| return Result; |
| } |
| |
| file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp, |
| FileAccess Access, OpenFlags Flags, unsigned Mode) { |
| int OpenFlags = nativeOpenFlags(Disp, Flags, Access); |
| file_t ResultFD = kInvalidFile; |
| |
| // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal |
| // when open is overloaded, such as in Bionic. |
| auto Open = [&]() { return ::open(Path.c_str(), OpenFlags, Mode); }; |
| if ((ResultFD = wpi::sys::RetryAfterSignal(-1, Open)) < 0) { |
| EC = std::error_code(errno, std::generic_category()); |
| return kInvalidFile; |
| } |
| #ifndef O_CLOEXEC |
| if (!(Flags & OF_ChildInherit)) { |
| int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC); |
| (void)r; |
| assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed"); |
| } |
| #endif |
| EC = std::error_code(); |
| return ResultFD; |
| } |
| |
| file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) { |
| return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags, 0666); |
| } |
| |
| int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) { |
| int fd = F; |
| F = kInvalidFile; |
| EC = std::error_code(); |
| return fd; |
| } |
| |
| void CloseFile(file_t& F) { |
| ::close(F); |
| F = kInvalidFile; |
| } |
| |
| #endif // _WIN32 |
| |
| } // namespace fs |