Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 60 additions & 80 deletions src/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@
#include <filesystem>
#include <ios>
#include <istream>
#include <system_error>
#include <utility>

#ifdef __GLIBCXX__
#include <ext/stdio_filebuf.h>
#endif

#ifdef _WIN32
#define NOMINMAX
#define UNICODE
#include <Windows.h>
#include <corecrt_io.h>
#endif
Expand All @@ -37,129 +36,110 @@ file::native_handle_type get_native_handle(std::FILE* file) noexcept {
#endif
}

#ifdef _WIN32
/// Makes a mode string for opening a temporary file using `_wfsopen`
/// Makes a mode string for opening a temporary file
/// @param[in] mode The file opening mode
/// @returns A suitable mode string
const wchar_t* make_mdstring(std::ios::openmode mode) noexcept {
switch (mode & ~std::ios::in & ~std::ios::out & ~std::ios::ate) {
case std::ios::openmode():
case std::ios::trunc:
return L"w+xTD";
const char* make_mdstring(std::ios::openmode mode) noexcept {
// Special case: the C++ standard forbids `app` and `trunc` at the same time
if ((mode & std::ios::app) != 0 && (mode & std::ios::trunc) != 0) {
return nullptr;
}

// - `std::ios::in` and `std::ios::out` are always applied
// - `std::ios::trunc` has no effect on the empty file
// - `std::ios::noreplace` has no effect for temporary files
// - any other platform dependent flag is not supported
unsigned filtered = mode & (std::ios::app | std::ios::binary);

switch (filtered) {
case 0:
#ifdef _WIN32
return "w+TD";
#else
return "w+";
#endif
case std::ios::app:
return L"a+TD";
#ifdef _WIN32
return "a+TD";
#else
return "a+";
#endif
case std::ios::binary:
case std::ios::trunc | std::ios::binary:
return L"w+bxTD";
#ifdef _WIN32
return "w+bTD";
#else
return "w+b";
#endif
case std::ios::app | std::ios::binary:
return L"a+bTD";
#ifdef _WIN32
return "a+bTD";
#else
return "a+b";
#endif
default:
return nullptr;
}
}

/// Creates and opens a temporary file in the current user's temporary directory
/// @param[in] mode The file opening mode
/// @param[out] ec Parameter for error reporting
/// @returns A handle to the created temporary file
/// @throws std::invalid_argument if the given openmode is invalid
std::FILE* create_file(std::ios::openmode mode, std::error_code& ec) {
const wchar_t* mdstr = make_mdstring(mode);
if (mdstr == nullptr) {
throw std::invalid_argument(
"Cannot create a temporary file: invalid openmode");
}

std::FILE* file = std::tmpfile();
if (file == nullptr) {
ec.assign(errno, std::generic_category());
return nullptr;
}
/// Reopens the given temporary file with the given open mode
/// @param[in] mdstring The temporary file opening mode
/// @param[out] file The file to reopen
/// @param[out] ec Parameter for error reporting
void reopen_file(const char* mdstring, std::FILE* file,
std::error_code& ec) noexcept {
ec.clear();

#ifdef _WIN32
HANDLE handle = get_native_handle(file);

std::wstring path;
std::string path;
path.resize(MAX_PATH);
DWORD ret = GetFinalPathNameByHandle(handle, path.data(), MAX_PATH, 0);
DWORD ret = GetFinalPathNameByHandleA(handle, path.data(), MAX_PATH, 0);
if (ret == 0) {
ec.assign(GetLastError(), std::system_category());
return nullptr;
return;
}

path.resize(ret);

file = _wfreopen(path.c_str(), make_mdstring(mode), file);
file = freopen(path.c_str(), mdstring, file);
if (file == nullptr) {
ec.assign(errno, std::generic_category());
return nullptr;
}

ec.clear();
return file;
}
#else
/// Makes a mode string for opening a file using `fopen`
/// @param[in] mode The file opening mode
/// @returns A suitable mode string
const char* make_mdstring(std::ios::openmode mode) noexcept {
unsigned filtered = mode & ~std::ios::in & ~std::ios::out & ~std::ios::ate;
switch (filtered) {
case 0:
return "r+";
case std::ios::trunc:
return "w+";
case std::ios::app:
return "a+";
case std::ios::binary:
return "r+b";
case std::ios::trunc | std::ios::binary:
return "w+b";
case std::ios::app | std::ios::binary:
return "a+b";
default:
return nullptr;
file = std::freopen(nullptr, mdstring, file);
if (file == nullptr) {
ec.assign(errno, std::generic_category());
}
}
#endif
}

/// Creates and opens a temporary file in the current user's temporary directory
/// @param[in] mode The file opening mode
/// @returns A handle to the created temporary file
/// @throws fs::filesystem_error if cannot create a temporary file
/// @throws std::invalid_argument if the given openmode is invalid
std::FILE* create_file(std::ios::openmode mode);

#ifdef _WIN32
std::FILE* create_file(std::ios::openmode mode) {
std::error_code ec;
std::FILE* file = create_file(mode, ec);
if (file == nullptr) {
throw fs::filesystem_error("Cannot create a temporary file", ec);
}

return file;
}
#else
std::FILE* create_file(std::ios::openmode mode) {
const char* mdstr = make_mdstring(mode);
if (mdstr == nullptr) {
const char* mdstring = make_mdstring(mode);
if (mdstring == nullptr) {
throw std::invalid_argument(
"Cannot create a temporary file: invalid openmode");
}

std::error_code ec;
std::FILE* file = std::tmpfile();
if (file != nullptr) {
file = freopen(nullptr, mdstr, file);
if (file == nullptr) {
ec.assign(errno, std::generic_category());
} else {
reopen_file(mdstring, file, ec);
}

if (file == nullptr) {
std::error_code ec = std::error_code(errno, std::system_category());
if (ec) {
throw fs::filesystem_error("Cannot create a temporary file", ec);
}

return file;
}
#endif
} // namespace

file::file(std::ios::openmode mode)
Expand Down
Loading