[wpiutil] Replace llvm filesystem with C++17 filesystem (#3401)

Use ghc::filesystem as fill on older GCC (e.g. RoboRIO).
This can be removed once all GCC platforms have upgraded to 8.1 or later.

File open functionality has been retained from LLVM but moved to "fs" namespace
and tweaked for improved consistency with std::filesystem (e.g. error_code is
passed by reference instead of returned).

Also update WPILibC's Filesystem functions to return std::string.
This commit is contained in:
Peter Johnson
2021-06-01 21:50:35 -07:00
committed by GitHub
parent 01dc0249de
commit fe570e000c
25 changed files with 6757 additions and 3538 deletions

View File

@@ -0,0 +1,326 @@
// 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