mirror of
https://github.com/minetest/minetest.git
synced 2025-02-17 10:23:47 +01:00
Introduce proper error handling for file streams
This commit is contained in:
@ -278,9 +278,7 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo
|
|||||||
<< "\" as \"" << vfs_path << "\"." << std::endl;
|
<< "\" as \"" << vfs_path << "\"." << std::endl;
|
||||||
|
|
||||||
std::string contents;
|
std::string contents;
|
||||||
if (!fs::ReadFile(real_path, contents)) {
|
if (!fs::ReadFile(real_path, contents, true)) {
|
||||||
errorstream << "Client::scanModSubfolder(): Can't read file \""
|
|
||||||
<< real_path << "\"." << std::endl;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1945,8 +1943,7 @@ void Client::makeScreenshot()
|
|||||||
|
|
||||||
while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
|
while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
|
||||||
filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext;
|
filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext;
|
||||||
std::ifstream tmp(filename.c_str());
|
if (!fs::PathExists(filename))
|
||||||
if (!tmp.good())
|
|
||||||
break; // File did not apparently exist, we'll go with it
|
break; // File did not apparently exist, we'll go with it
|
||||||
serial++;
|
serial++;
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,9 @@ void FileCache::createDir()
|
|||||||
|
|
||||||
bool FileCache::loadByPath(const std::string &path, std::ostream &os)
|
bool FileCache::loadByPath(const std::string &path, std::ostream &os)
|
||||||
{
|
{
|
||||||
std::ifstream fis(path.c_str(), std::ios_base::binary);
|
auto fis = open_ifstream(path.c_str(), true);
|
||||||
|
if (!fis.good())
|
||||||
if(!fis.good()){
|
|
||||||
verbosestream<<"FileCache: File not found in cache: "
|
|
||||||
<<path<<std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
bool bad = false;
|
bool bad = false;
|
||||||
for(;;){
|
for(;;){
|
||||||
@ -70,15 +66,10 @@ bool FileCache::loadByPath(const std::string &path, std::ostream &os)
|
|||||||
bool FileCache::updateByPath(const std::string &path, std::string_view data)
|
bool FileCache::updateByPath(const std::string &path, std::string_view data)
|
||||||
{
|
{
|
||||||
createDir();
|
createDir();
|
||||||
std::ofstream file(path.c_str(), std::ios_base::binary |
|
|
||||||
std::ios_base::trunc);
|
|
||||||
|
|
||||||
if(!file.good())
|
auto file = open_ofstream(path.c_str(), true);
|
||||||
{
|
if (!file.good())
|
||||||
errorstream<<"FileCache: Can't write to file at "
|
|
||||||
<<path<<std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
file << data;
|
file << data;
|
||||||
file.close();
|
file.close();
|
||||||
@ -101,8 +92,7 @@ bool FileCache::load(const std::string &name, std::ostream &os)
|
|||||||
bool FileCache::exists(const std::string &name)
|
bool FileCache::exists(const std::string &name)
|
||||||
{
|
{
|
||||||
std::string path = m_dir + DIR_DELIM + name;
|
std::string path = m_dir + DIR_DELIM + name;
|
||||||
std::ifstream fis(path.c_str(), std::ios_base::binary);
|
return fs::PathExists(path);
|
||||||
return fis.good();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileCache::updateCopyFile(const std::string &name, const std::string &src_path)
|
bool FileCache::updateCopyFile(const std::string &name, const std::string &src_path)
|
||||||
|
@ -160,10 +160,10 @@ public:
|
|||||||
private:
|
private:
|
||||||
StringMap m_programs;
|
StringMap m_programs;
|
||||||
|
|
||||||
std::string readFile(const std::string &path)
|
inline std::string readFile(const std::string &path)
|
||||||
{
|
{
|
||||||
std::string ret;
|
std::string ret;
|
||||||
if (!fs::ReadFile(path, ret))
|
if (!fs::ReadFile(path, ret, true))
|
||||||
ret.clear();
|
ret.clear();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -710,7 +710,8 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
|
|||||||
prefix.append("#line 0\n");
|
prefix.append("#line 0\n");
|
||||||
|
|
||||||
std::string content;
|
std::string content;
|
||||||
fs::ReadFile(path, content);
|
if (!fs::ReadFile(path, content, true))
|
||||||
|
return "";
|
||||||
|
|
||||||
return prefix + content;
|
return prefix + content;
|
||||||
}
|
}
|
||||||
|
@ -26,35 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
|
|
||||||
ContentType getContentType(const std::string &path)
|
ContentType getContentType(const std::string &path)
|
||||||
{
|
{
|
||||||
std::ifstream modpack_is((path + DIR_DELIM + "modpack.txt").c_str());
|
if (fs::IsFile(path + DIR_DELIM "modpack.txt") || fs::IsFile(path + DIR_DELIM "modpack.conf"))
|
||||||
if (modpack_is.good()) {
|
|
||||||
modpack_is.close();
|
|
||||||
return ContentType::MODPACK;
|
return ContentType::MODPACK;
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream modpack2_is((path + DIR_DELIM + "modpack.conf").c_str());
|
if (fs::IsFile(path + DIR_DELIM "init.lua"))
|
||||||
if (modpack2_is.good()) {
|
|
||||||
modpack2_is.close();
|
|
||||||
return ContentType::MODPACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream init_is((path + DIR_DELIM + "init.lua").c_str());
|
|
||||||
if (init_is.good()) {
|
|
||||||
init_is.close();
|
|
||||||
return ContentType::MOD;
|
return ContentType::MOD;
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream game_is((path + DIR_DELIM + "game.conf").c_str());
|
if (fs::IsFile(path + DIR_DELIM "game.conf"))
|
||||||
if (game_is.good()) {
|
|
||||||
game_is.close();
|
|
||||||
return ContentType::GAME;
|
return ContentType::GAME;
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream txp_is((path + DIR_DELIM + "texture_pack.conf").c_str());
|
if (fs::IsFile(path + DIR_DELIM "texture_pack.conf"))
|
||||||
if (txp_is.good()) {
|
|
||||||
txp_is.close();
|
|
||||||
return ContentType::TXP;
|
return ContentType::TXP;
|
||||||
}
|
|
||||||
|
|
||||||
return ContentType::UNKNOWN;
|
return ContentType::UNKNOWN;
|
||||||
}
|
}
|
||||||
@ -66,19 +48,19 @@ void parseContentInfo(ContentSpec &spec)
|
|||||||
switch (getContentType(spec.path)) {
|
switch (getContentType(spec.path)) {
|
||||||
case ContentType::MOD:
|
case ContentType::MOD:
|
||||||
spec.type = "mod";
|
spec.type = "mod";
|
||||||
conf_path = spec.path + DIR_DELIM + "mod.conf";
|
conf_path = spec.path + DIR_DELIM "mod.conf";
|
||||||
break;
|
break;
|
||||||
case ContentType::MODPACK:
|
case ContentType::MODPACK:
|
||||||
spec.type = "modpack";
|
spec.type = "modpack";
|
||||||
conf_path = spec.path + DIR_DELIM + "modpack.conf";
|
conf_path = spec.path + DIR_DELIM "modpack.conf";
|
||||||
break;
|
break;
|
||||||
case ContentType::GAME:
|
case ContentType::GAME:
|
||||||
spec.type = "game";
|
spec.type = "game";
|
||||||
conf_path = spec.path + DIR_DELIM + "game.conf";
|
conf_path = spec.path + DIR_DELIM "game.conf";
|
||||||
break;
|
break;
|
||||||
case ContentType::TXP:
|
case ContentType::TXP:
|
||||||
spec.type = "txp";
|
spec.type = "txp";
|
||||||
conf_path = spec.path + DIR_DELIM + "texture_pack.conf";
|
conf_path = spec.path + DIR_DELIM "texture_pack.conf";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
spec.type = "unknown";
|
spec.type = "unknown";
|
||||||
@ -124,8 +106,6 @@ void parseContentInfo(ContentSpec &spec)
|
|||||||
spec.textdomain = spec.name;
|
spec.textdomain = spec.name;
|
||||||
|
|
||||||
if (spec.desc.empty()) {
|
if (spec.desc.empty()) {
|
||||||
std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
|
fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc);
|
||||||
spec.desc = std::string((std::istreambuf_iterator<char>(is)),
|
|
||||||
std::istreambuf_iterator<char>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,11 +165,9 @@ void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open and deserialize file to check player name
|
// Open and deserialize file to check player name
|
||||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(path.c_str(), true);
|
||||||
if (!is.good()) {
|
if (!is.good())
|
||||||
errorstream << "Failed to open " << path << std::endl;
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
deSerialize(&testplayer, is, path, NULL);
|
deSerialize(&testplayer, is, path, NULL);
|
||||||
is.close();
|
is.close();
|
||||||
@ -205,7 +203,7 @@ bool PlayerDatabaseFiles::removePlayer(const std::string &name)
|
|||||||
RemotePlayer temp_player("", NULL);
|
RemotePlayer temp_player("", NULL);
|
||||||
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
||||||
// Open file and deserialize
|
// Open file and deserialize
|
||||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(path.c_str(), false);
|
||||||
if (!is.good())
|
if (!is.good())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -231,7 +229,7 @@ bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
|
|||||||
const std::string player_to_load = player->getName();
|
const std::string player_to_load = player->getName();
|
||||||
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
|
||||||
// Open file and deserialize
|
// Open file and deserialize
|
||||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(path.c_str(), false);
|
||||||
if (!is.good())
|
if (!is.good())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -260,7 +258,7 @@ void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
|
|||||||
|
|
||||||
const std::string &filename = it->name;
|
const std::string &filename = it->name;
|
||||||
std::string full_path = m_savedir + DIR_DELIM + filename;
|
std::string full_path = m_savedir + DIR_DELIM + filename;
|
||||||
std::ifstream is(full_path.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(full_path.c_str(), true);
|
||||||
if (!is.good())
|
if (!is.good())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -332,7 +330,7 @@ void AuthDatabaseFiles::reload()
|
|||||||
bool AuthDatabaseFiles::readAuthFile()
|
bool AuthDatabaseFiles::readAuthFile()
|
||||||
{
|
{
|
||||||
std::string path = m_savedir + DIR_DELIM + "auth.txt";
|
std::string path = m_savedir + DIR_DELIM + "auth.txt";
|
||||||
std::ifstream file(path, std::ios::binary);
|
auto file = open_ifstream(path.c_str(), false);
|
||||||
if (!file.good()) {
|
if (!file.good()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -528,8 +526,7 @@ Json::Value *ModStorageDatabaseFiles::getOrCreateJson(const std::string &modname
|
|||||||
|
|
||||||
std::string path = m_storage_dir + DIR_DELIM + modname;
|
std::string path = m_storage_dir + DIR_DELIM + modname;
|
||||||
if (fs::PathExists(path)) {
|
if (fs::PathExists(path)) {
|
||||||
std::ifstream is(path.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(path.c_str(), true);
|
||||||
|
|
||||||
Json::CharReaderBuilder builder;
|
Json::CharReaderBuilder builder;
|
||||||
builder.settings_["collectComments"] = false;
|
builder.settings_["collectComments"] = false;
|
||||||
std::string errs;
|
std::string errs;
|
||||||
|
@ -41,6 +41,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Error from last OS call as string
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define LAST_OS_ERROR() porting::ConvertError(GetLastError())
|
||||||
|
#else
|
||||||
|
#define LAST_OS_ERROR() strerror(errno)
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace fs
|
namespace fs
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -849,40 +856,44 @@ const char *GetFilenameFromPath(const char *path)
|
|||||||
|
|
||||||
bool safeWriteToFile(const std::string &path, std::string_view content)
|
bool safeWriteToFile(const std::string &path, std::string_view content)
|
||||||
{
|
{
|
||||||
std::string tmp_file = path + ".~mt";
|
// Note: this is not safe if two MT processes try this at the same time (FIXME?)
|
||||||
|
const std::string tmp_file = path + ".~mt";
|
||||||
|
|
||||||
// Write to a tmp file
|
// Write data to a temporary file
|
||||||
bool tmp_success = false;
|
std::string write_error;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// We've observed behavior suggesting that the MSVC implementation of std::ofstream::flush doesn't
|
// We've observed behavior suggesting that the MSVC implementation of std::ofstream::flush doesn't
|
||||||
// actually flush, so we use win32 APIs.
|
// actually flush, so we use win32 APIs.
|
||||||
HANDLE tmp_handle = CreateFile(
|
HANDLE handle = CreateFile(tmp_file.c_str(), GENERIC_WRITE, 0, nullptr,
|
||||||
tmp_file.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
if (tmp_handle == INVALID_HANDLE_VALUE) {
|
if (handle == INVALID_HANDLE_VALUE) {
|
||||||
|
errorstream << "Failed to open file: " << LAST_OS_ERROR() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DWORD bytes_written;
|
DWORD bytes_written;
|
||||||
tmp_success = (WriteFile(tmp_handle, content.data(), content.size(), &bytes_written, nullptr) &&
|
if (!WriteFile(handle, content.data(), content.size(), &bytes_written, nullptr))
|
||||||
FlushFileBuffers(tmp_handle));
|
write_error = LAST_OS_ERROR();
|
||||||
CloseHandle(tmp_handle);
|
else if (!FlushFileBuffers(handle))
|
||||||
|
write_error = LAST_OS_ERROR();
|
||||||
|
CloseHandle(handle);
|
||||||
#else
|
#else
|
||||||
std::ofstream os(tmp_file.c_str(), std::ios::binary);
|
auto os = open_ofstream(tmp_file.c_str(), true);
|
||||||
if (!os.good()) {
|
if (!os.good())
|
||||||
return false;
|
return false;
|
||||||
}
|
os << content << std::flush;
|
||||||
os << content;
|
|
||||||
os.flush();
|
|
||||||
os.close();
|
os.close();
|
||||||
tmp_success = !os.fail();
|
if (os.fail())
|
||||||
|
write_error = "iostream fail";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!tmp_success) {
|
if (!write_error.empty()) {
|
||||||
|
errorstream << "Failed to write file: " << write_error << std::endl;
|
||||||
remove(tmp_file.c_str());
|
remove(tmp_file.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rename_success = false;
|
std::string rename_error;
|
||||||
|
|
||||||
// Move the finished temporary file over the real file
|
// Move the finished temporary file over the real file
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -890,22 +901,25 @@ bool safeWriteToFile(const std::string &path, std::string_view content)
|
|||||||
// to query the file. This can make the move file call below fail.
|
// to query the file. This can make the move file call below fail.
|
||||||
// We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed
|
// We retry up to 5 times, with a 1ms sleep between, before we consider the whole operation failed
|
||||||
for (int attempt = 0; attempt < 5; attempt++) {
|
for (int attempt = 0; attempt < 5; attempt++) {
|
||||||
rename_success = MoveFileEx(tmp_file.c_str(), path.c_str(),
|
auto ok = MoveFileEx(tmp_file.c_str(), path.c_str(),
|
||||||
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
|
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH);
|
||||||
if (rename_success)
|
if (ok) {
|
||||||
|
rename_error.clear();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
rename_error = LAST_OS_ERROR();
|
||||||
sleep_ms(1);
|
sleep_ms(1);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// On POSIX compliant systems rename() is specified to be able to swap the
|
// On POSIX compliant systems rename() is specified to be able to swap the
|
||||||
// file in place of the destination file, making this a truly error-proof
|
// file in place of the destination file, making this a truly error-proof
|
||||||
// transaction.
|
// transaction.
|
||||||
rename_success = rename(tmp_file.c_str(), path.c_str()) == 0;
|
if (rename(tmp_file.c_str(), path.c_str()) != 0)
|
||||||
|
rename_error = LAST_OS_ERROR();
|
||||||
#endif
|
#endif
|
||||||
if (!rename_success) {
|
|
||||||
warningstream << "Failed to write to file: " << path.c_str() << std::endl;
|
if (!rename_error.empty()) {
|
||||||
// Remove the temporary file because moving it over the target file
|
errorstream << "Failed to overwrite \"" << path << "\": " << rename_error << std::endl;
|
||||||
// failed.
|
|
||||||
remove(tmp_file.c_str());
|
remove(tmp_file.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -949,7 +963,7 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string
|
|||||||
|
|
||||||
irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i));
|
irr_ptr<io::IReadFile> toread(opened_zip->createAndOpenFile(i));
|
||||||
|
|
||||||
std::ofstream os(fullpath.c_str(), std::ios::binary);
|
auto os = open_ofstream(fullpath.c_str(), true);
|
||||||
if (!os.good())
|
if (!os.good())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -976,12 +990,11 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool ReadFile(const std::string &path, std::string &out)
|
bool ReadFile(const std::string &path, std::string &out, bool log_error)
|
||||||
{
|
{
|
||||||
std::ifstream is(path, std::ios::binary | std::ios::ate);
|
auto is = open_ifstream(path.c_str(), log_error, std::ios::ate);
|
||||||
if (!is.good()) {
|
if (!is.good())
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
auto size = is.tellg();
|
auto size = is.tellg();
|
||||||
out.resize(size);
|
out.resize(size);
|
||||||
@ -996,4 +1009,22 @@ bool Rename(const std::string &from, const std::string &to)
|
|||||||
return rename(from.c_str(), to.c_str()) == 0;
|
return rename(from.c_str(), to.c_str()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OpenStream(std::filebuf &stream, const char *filename,
|
||||||
|
std::ios::openmode mode, bool log_error, bool log_warn)
|
||||||
|
{
|
||||||
|
assert((mode & std::ios::in) || (mode & std::ios::out));
|
||||||
|
assert(!stream.is_open());
|
||||||
|
// C++ dropped the ball hard for file opening error handling, there's not even
|
||||||
|
// an implementation-defined mechanism for returning any kind of error code or message.
|
||||||
|
if (!stream.open(filename, mode)) {
|
||||||
|
if (log_warn || log_error) {
|
||||||
|
std::string msg = LAST_OS_ERROR();
|
||||||
|
(log_error ? errorstream : warningstream)
|
||||||
|
<< "Failed to open \"" << filename << "\": " << msg << std::endl;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define DIR_DELIM "\\"
|
#define DIR_DELIM "\\"
|
||||||
@ -148,14 +149,74 @@ std::string AbsolutePath(const std::string &path);
|
|||||||
// delimiter is found.
|
// delimiter is found.
|
||||||
const char *GetFilenameFromPath(const char *path);
|
const char *GetFilenameFromPath(const char *path);
|
||||||
|
|
||||||
|
// Replace the content of a file on disk in a way that is safe from
|
||||||
|
// corruption/truncation on a crash.
|
||||||
|
// logs and returns false on error
|
||||||
bool safeWriteToFile(const std::string &path, std::string_view content);
|
bool safeWriteToFile(const std::string &path, std::string_view content);
|
||||||
|
|
||||||
#ifndef SERVER
|
#ifndef SERVER
|
||||||
bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination);
|
bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool ReadFile(const std::string &path, std::string &out);
|
bool ReadFile(const std::string &path, std::string &out, bool log_error = false);
|
||||||
|
|
||||||
bool Rename(const std::string &from, const std::string &to);
|
bool Rename(const std::string &from, const std::string &to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a file buffer with error handling, commonly used with `std::fstream.rdbuf()`.
|
||||||
|
*
|
||||||
|
* @param stream stream references, must not already be open
|
||||||
|
* @param filename filename to open
|
||||||
|
* @param mode mode bits (used as-is)
|
||||||
|
* @param log_error log failure to errorstream?
|
||||||
|
* @param log_warn log failure to warningstream?
|
||||||
|
* @return true if success
|
||||||
|
*/
|
||||||
|
bool OpenStream(std::filebuf &stream, const char *filename,
|
||||||
|
std::ios::openmode mode, bool log_error, bool log_warn);
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
|
||||||
|
// outside of namespace for convenience:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to open an output file stream (= writing).
|
||||||
|
*
|
||||||
|
* For compatibility with fopen() binary mode and truncate are always set.
|
||||||
|
* Use `fs::OpenStream` for more control.
|
||||||
|
* @param name file name
|
||||||
|
* @param log if true, failure to open the file will be logged to errorstream
|
||||||
|
* @param mode additional mode bits (e.g. std::ios::app)
|
||||||
|
* @return file stream, will be !good in case of error
|
||||||
|
*/
|
||||||
|
inline std::ofstream open_ofstream(const char *name, bool log,
|
||||||
|
std::ios::openmode mode = std::ios::openmode())
|
||||||
|
{
|
||||||
|
mode |= std::ios::out | std::ios::binary;
|
||||||
|
if (!(mode & std::ios::app))
|
||||||
|
mode |= std::ios::trunc;
|
||||||
|
std::ofstream ofs;
|
||||||
|
if (!fs::OpenStream(*ofs.rdbuf(), name, mode, log, false))
|
||||||
|
ofs.setstate(std::ios::failbit);
|
||||||
|
return ofs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to open an input file stream (= reading).
|
||||||
|
*
|
||||||
|
* For compatibility with fopen() binary mode is always set.
|
||||||
|
* Use `fs::OpenStream` for more control.
|
||||||
|
* @param name file name
|
||||||
|
* @param log if true, failure to open the file will be logged to errorstream
|
||||||
|
* @param mode additional mode bits (e.g. std::ios::ate)
|
||||||
|
* @return file stream, will be !good in case of error
|
||||||
|
*/
|
||||||
|
inline std::ifstream open_ifstream(const char *name, bool log,
|
||||||
|
std::ios::openmode mode = std::ios::openmode())
|
||||||
|
{
|
||||||
|
mode |= std::ios::in | std::ios::binary;
|
||||||
|
std::ifstream ifs;
|
||||||
|
if (!fs::OpenStream(*ifs.rdbuf(), name, mode, log, false))
|
||||||
|
ifs.setstate(std::ios::failbit);
|
||||||
|
return ifs;
|
||||||
|
}
|
||||||
|
@ -147,18 +147,15 @@ static void MSVC_LocaleWorkaround(int argc, char* argv[])
|
|||||||
exit(0);
|
exit(0);
|
||||||
// NOTREACHED
|
// NOTREACHED
|
||||||
} else {
|
} else {
|
||||||
char buffer[1024];
|
auto e = GetLastError();
|
||||||
|
|
||||||
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
|
errorstream
|
||||||
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), buffer,
|
<< "*******************************************************" << '\n'
|
||||||
sizeof(buffer) - 1, NULL);
|
<< "CMD: " << app_name << '\n';
|
||||||
|
<< "Failed to restart with current locale: ";
|
||||||
errorstream << "*******************************************************" << std::endl;
|
<< porting::ConvertError(e) << '\n';
|
||||||
errorstream << "CMD: " << app_name << std::endl;
|
<< "Expect language to be broken!" << '\n';
|
||||||
errorstream << "Failed to restart with current locale: " << std::endl;
|
<< "*******************************************************" << std::endl;
|
||||||
errorstream << buffer;
|
|
||||||
errorstream << "Expect language to be broken!" << std::endl;
|
|
||||||
errorstream << "*******************************************************" << std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,10 +620,9 @@ bool GUIEngine::setTexture(texture_layer layer, const std::string &texturepath,
|
|||||||
bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
|
bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
|
||||||
{
|
{
|
||||||
#if USE_CURL
|
#if USE_CURL
|
||||||
std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
|
auto target_file = open_ofstream(target.c_str(), true);
|
||||||
if (!target_file.good()) {
|
if (!target_file.good())
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
HTTPFetchRequest fetch_request;
|
HTTPFetchRequest fetch_request;
|
||||||
HTTPFetchResult fetch_result;
|
HTTPFetchResult fetch_result;
|
||||||
|
23
src/log.cpp
23
src/log.cpp
@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "exceptions.h"
|
#include "exceptions.h"
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "filesys.h"
|
||||||
|
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
@ -36,7 +37,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cerrno>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
class LevelTarget : public LogTarget {
|
class LevelTarget : public LogTarget {
|
||||||
@ -299,25 +299,24 @@ void FileLogOutput::setFile(const std::string &filename, s64 file_size_max)
|
|||||||
bool is_too_large = false;
|
bool is_too_large = false;
|
||||||
if (file_size_max > 0) {
|
if (file_size_max > 0) {
|
||||||
std::ifstream ifile(filename, std::ios::binary | std::ios::ate);
|
std::ifstream ifile(filename, std::ios::binary | std::ios::ate);
|
||||||
is_too_large = ifile.tellg() > file_size_max;
|
if (ifile.good())
|
||||||
ifile.close();
|
is_too_large = ifile.tellg() > file_size_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_too_large) {
|
if (is_too_large) {
|
||||||
std::string filename_secondary = filename + ".1";
|
std::string filename_secondary = filename + ".1";
|
||||||
actionstream << "The log file grew too big; it is moved to " <<
|
actionstream << "The log file grew too big; it is moved to " <<
|
||||||
filename_secondary << std::endl;
|
filename_secondary << std::endl;
|
||||||
remove(filename_secondary.c_str());
|
fs::DeleteSingleFileOrEmptyDirectory(filename_secondary);
|
||||||
rename(filename.c_str(), filename_secondary.c_str());
|
fs::Rename(filename, filename_secondary);
|
||||||
}
|
}
|
||||||
m_stream.open(filename, std::ios::app | std::ios::ate);
|
|
||||||
|
|
||||||
if (!m_stream.good())
|
// Intentionally not using open_ofstream() to keep the text mode
|
||||||
throw FileNotGoodException("Failed to open log file " +
|
if (!fs::OpenStream(*m_stream.rdbuf(), filename.c_str(), std::ios::out | std::ios::app, true, false))
|
||||||
filename + ": " + strerror(errno));
|
throw FileNotGoodException("Failed to open log file");
|
||||||
|
|
||||||
m_stream << "\n\n"
|
m_stream << "\n\n"
|
||||||
"-------------" << std::endl <<
|
"-------------\n" <<
|
||||||
" Separator" << std::endl <<
|
" Separator\n" <<
|
||||||
"-------------\n" << std::endl;
|
"-------------\n" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,13 +96,9 @@ bool MapSettingsManager::setMapSettingNoiseParams(
|
|||||||
|
|
||||||
bool MapSettingsManager::loadMapMeta()
|
bool MapSettingsManager::loadMapMeta()
|
||||||
{
|
{
|
||||||
std::ifstream is(m_map_meta_path.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(m_map_meta_path.c_str(), true);
|
||||||
|
if (!is.good())
|
||||||
if (!is.good()) {
|
|
||||||
errorstream << "loadMapMeta: could not open "
|
|
||||||
<< m_map_meta_path << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_map_settings->parseConfigLines(is)) {
|
if (!m_map_settings->parseConfigLines(is)) {
|
||||||
errorstream << "loadMapMeta: Format error. '[end_of_params]' missing?" << std::endl;
|
errorstream << "loadMapMeta: Format error. '[end_of_params]' missing?" << std::endl;
|
||||||
|
@ -485,12 +485,9 @@ bool Schematic::serializeToLua(std::ostream *os, bool use_comments,
|
|||||||
bool Schematic::loadSchematicFromFile(const std::string &filename,
|
bool Schematic::loadSchematicFromFile(const std::string &filename,
|
||||||
const NodeDefManager *ndef, StringMap *replace_names)
|
const NodeDefManager *ndef, StringMap *replace_names)
|
||||||
{
|
{
|
||||||
std::ifstream is(filename.c_str(), std::ios_base::binary);
|
auto is = open_ifstream(filename.c_str(), true);
|
||||||
if (!is.good()) {
|
if (!is.good())
|
||||||
errorstream << __FUNCTION__ << ": unable to open file '"
|
|
||||||
<< filename << "'" << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_ndef)
|
if (!m_ndef)
|
||||||
m_ndef = ndef;
|
m_ndef = ndef;
|
||||||
|
@ -582,7 +582,7 @@ static void createCacheDirTag()
|
|||||||
if (fs::PathExists(path))
|
if (fs::PathExists(path))
|
||||||
return;
|
return;
|
||||||
fs::CreateAllDirs(path_cache);
|
fs::CreateAllDirs(path_cache);
|
||||||
std::ofstream ofs(path, std::ios::out | std::ios::binary);
|
auto ofs = open_ofstream(path.c_str(), false);
|
||||||
if (!ofs.good())
|
if (!ofs.good())
|
||||||
return;
|
return;
|
||||||
ofs << "Signature: 8a477f597d28d172789f06886806bc55\n"
|
ofs << "Signature: 8a477f597d28d172789f06886806bc55\n"
|
||||||
@ -800,6 +800,21 @@ std::string QuoteArgv(const std::string &arg)
|
|||||||
ret.push_back('"');
|
ret.push_back('"');
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string ConvertError(DWORD error_code)
|
||||||
|
{
|
||||||
|
wchar_t buffer[320];
|
||||||
|
|
||||||
|
auto r = FormatMessageW(
|
||||||
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
|
nullptr, error_code, 0, buffer, ARRLEN(buffer) - 1, nullptr);
|
||||||
|
if (!r)
|
||||||
|
return std::to_string(error_code);
|
||||||
|
|
||||||
|
if (!buffer[0]) // should not happen normally
|
||||||
|
return "?";
|
||||||
|
return wide_to_utf8(buffer);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)
|
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)
|
||||||
|
@ -293,6 +293,9 @@ void attachOrCreateConsole();
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Quotes an argument for use in a CreateProcess() commandline (not cmd.exe!!)
|
// Quotes an argument for use in a CreateProcess() commandline (not cmd.exe!!)
|
||||||
std::string QuoteArgv(const std::string &arg);
|
std::string QuoteArgv(const std::string &arg);
|
||||||
|
|
||||||
|
// Convert an error code (e.g. from GetLastError()) into a string.
|
||||||
|
std::string ConvertError(DWORD error_code);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);
|
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);
|
||||||
|
@ -298,7 +298,7 @@ int LuaAreaStore::l_from_file(lua_State *L)
|
|||||||
const char *filename = luaL_checkstring(L, 2);
|
const char *filename = luaL_checkstring(L, 2);
|
||||||
CHECK_SECURE_PATH(L, filename, false);
|
CHECK_SECURE_PATH(L, filename, false);
|
||||||
|
|
||||||
std::ifstream is(filename, std::ios::binary);
|
auto is = open_ifstream(filename, true);
|
||||||
return deserialization_helper(L, o->as, is);
|
return deserialization_helper(L, o->as, is);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2545,9 +2545,7 @@ bool Server::addMediaFile(const std::string &filename,
|
|||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
std::string filedata;
|
std::string filedata;
|
||||||
if (!fs::ReadFile(filepath, filedata)) {
|
if (!fs::ReadFile(filepath, filedata, true)) {
|
||||||
errorstream << "Server::addMediaFile(): Failed to open \""
|
|
||||||
<< filename << "\" for reading" << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2715,9 +2713,7 @@ void Server::sendRequestedMedia(session_t peer_id,
|
|||||||
|
|
||||||
// Read data
|
// Read data
|
||||||
std::string data;
|
std::string data;
|
||||||
if (!fs::ReadFile(m.path, data)) {
|
if (!fs::ReadFile(m.path, data, true)) {
|
||||||
errorstream << "Server::sendRequestedMedia(): Failed to read \""
|
|
||||||
<< name << "\"" << std::endl;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
file_size_bunch_total += data.size();
|
file_size_bunch_total += data.size();
|
||||||
@ -3618,7 +3614,7 @@ namespace {
|
|||||||
auto filepath = fs::CreateTempFile();
|
auto filepath = fs::CreateTempFile();
|
||||||
if (filepath.empty())
|
if (filepath.empty())
|
||||||
return "";
|
return "";
|
||||||
std::ofstream os(filepath, std::ios::binary);
|
auto os = open_ofstream(filepath.c_str(), true);
|
||||||
if (!os.good())
|
if (!os.good())
|
||||||
return "";
|
return "";
|
||||||
os << content;
|
os << content;
|
||||||
@ -4200,7 +4196,7 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code)
|
|||||||
for (const auto &i : m_media) {
|
for (const auto &i : m_media) {
|
||||||
if (str_ends_with(i.first, suffix)) {
|
if (str_ends_with(i.first, suffix)) {
|
||||||
std::string data;
|
std::string data;
|
||||||
if (fs::ReadFile(i.second.path, data)) {
|
if (fs::ReadFile(i.second.path, data, true)) {
|
||||||
translations->loadTranslation(data);
|
translations->loadTranslation(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,9 +48,8 @@ void BanManager::load()
|
|||||||
{
|
{
|
||||||
MutexAutoLock lock(m_mutex);
|
MutexAutoLock lock(m_mutex);
|
||||||
infostream<<"BanManager: loading from "<<m_banfilepath<<std::endl;
|
infostream<<"BanManager: loading from "<<m_banfilepath<<std::endl;
|
||||||
std::ifstream is(m_banfilepath.c_str(), std::ios::binary);
|
auto is = open_ifstream(m_banfilepath.c_str(), false);
|
||||||
if (!is.good()) {
|
if (!is.good()) {
|
||||||
infostream<<"BanManager: failed loading from "<<m_banfilepath<<std::endl;
|
|
||||||
throw SerializationError("BanManager::load(): Couldn't open file");
|
throw SerializationError("BanManager::load(): Couldn't open file");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,11 +392,8 @@ bool Settings::updateConfigFile(const char *filename)
|
|||||||
if (!was_modified)
|
if (!was_modified)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!fs::safeWriteToFile(filename, os.str())) {
|
if (!fs::safeWriteToFile(filename, os.str()))
|
||||||
errorstream << "Error writing configuration file: \""
|
|
||||||
<< filename << "\"" << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "texture_override.h"
|
#include "texture_override.h"
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "filesys.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -48,7 +49,7 @@ static const std::map<std::string, OverrideTarget> override_LUT = {
|
|||||||
|
|
||||||
TextureOverrideSource::TextureOverrideSource(const std::string &filepath)
|
TextureOverrideSource::TextureOverrideSource(const std::string &filepath)
|
||||||
{
|
{
|
||||||
std::ifstream infile(filepath.c_str());
|
auto infile = open_ifstream(filepath.c_str(), false);
|
||||||
std::string line;
|
std::string line;
|
||||||
int line_index = 0;
|
int line_index = 0;
|
||||||
while (std::getline(infile, line)) {
|
while (std::getline(infile, line)) {
|
||||||
|
@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||||||
#include "modchannels.h"
|
#include "modchannels.h"
|
||||||
#include "util/numeric.h"
|
#include "util/numeric.h"
|
||||||
#include "porting.h"
|
#include "porting.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
content_t t_CONTENT_STONE;
|
content_t t_CONTENT_STONE;
|
||||||
content_t t_CONTENT_GRASS;
|
content_t t_CONTENT_GRASS;
|
||||||
@ -348,11 +349,14 @@ void TestBase::runTest(const char *name, std::function<void()> &&test)
|
|||||||
rawstream << " at " << e.file << ":" << e.line << std::endl;
|
rawstream << " at " << e.file << ":" << e.line << std::endl;
|
||||||
rawstream << "[FAIL] ";
|
rawstream << "[FAIL] ";
|
||||||
num_tests_failed++;
|
num_tests_failed++;
|
||||||
} catch (std::exception &e) {
|
}
|
||||||
|
#if CATCH_UNHANDLED_EXCEPTIONS == 1
|
||||||
|
catch (std::exception &e) {
|
||||||
rawstream << "Caught unhandled exception: " << e.what() << std::endl;
|
rawstream << "Caught unhandled exception: " << e.what() << std::endl;
|
||||||
rawstream << "[FAIL] ";
|
rawstream << "[FAIL] ";
|
||||||
num_tests_failed++;
|
num_tests_failed++;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
num_tests_run++;
|
num_tests_run++;
|
||||||
u64 tdiff = porting::getTimeMs() - t1;
|
u64 tdiff = porting::getTimeMs() - t1;
|
||||||
rawstream << name << " - " << tdiff << "ms" << std::endl;
|
rawstream << name << " - " << tdiff << "ms" << std::endl;
|
||||||
|
@ -80,13 +80,13 @@ void TestBan::testCreate()
|
|||||||
BanManager bm(m_testbm);
|
BanManager bm(m_testbm);
|
||||||
}
|
}
|
||||||
|
|
||||||
UASSERT(std::ifstream(m_testbm, std::ios::binary).is_open());
|
UASSERT(fs::IsFile(m_testbm));
|
||||||
|
|
||||||
// test manual save
|
// test manual save
|
||||||
{
|
{
|
||||||
BanManager bm(m_testbm2);
|
BanManager bm(m_testbm2);
|
||||||
bm.save();
|
bm.save();
|
||||||
UASSERT(std::ifstream(m_testbm2, std::ios::binary).is_open());
|
UASSERT(fs::IsFile(m_testbm2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ public:
|
|||||||
void testRemoveRelativePathComponent();
|
void testRemoveRelativePathComponent();
|
||||||
void testSafeWriteToFile();
|
void testSafeWriteToFile();
|
||||||
void testCopyFileContents();
|
void testCopyFileContents();
|
||||||
|
void testNonExist();
|
||||||
};
|
};
|
||||||
|
|
||||||
static TestFileSys g_test_instance;
|
static TestFileSys g_test_instance;
|
||||||
@ -54,6 +55,7 @@ void TestFileSys::runTests(IGameDef *gamedef)
|
|||||||
TEST(testRemoveRelativePathComponent);
|
TEST(testRemoveRelativePathComponent);
|
||||||
TEST(testSafeWriteToFile);
|
TEST(testSafeWriteToFile);
|
||||||
TEST(testCopyFileContents);
|
TEST(testCopyFileContents);
|
||||||
|
TEST(testNonExist);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -311,3 +313,27 @@ void TestFileSys::testCopyFileContents()
|
|||||||
UASSERT(fs::ReadFile(file2, contents_actual));
|
UASSERT(fs::ReadFile(file2, contents_actual));
|
||||||
UASSERTEQ(auto, contents_actual, test_data);
|
UASSERTEQ(auto, contents_actual, test_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestFileSys::testNonExist()
|
||||||
|
{
|
||||||
|
const auto path = getTestTempFile();
|
||||||
|
fs::DeleteSingleFileOrEmptyDirectory(path);
|
||||||
|
|
||||||
|
UASSERT(!fs::IsFile(path));
|
||||||
|
UASSERT(!fs::IsDir(path));
|
||||||
|
UASSERT(!fs::IsExecutable(path));
|
||||||
|
|
||||||
|
std::string s;
|
||||||
|
UASSERT(!fs::ReadFile(path, s));
|
||||||
|
UASSERT(s.empty());
|
||||||
|
|
||||||
|
UASSERT(!fs::Rename(path, getTestTempFile()));
|
||||||
|
|
||||||
|
std::filebuf buf;
|
||||||
|
// with logging enabled to test that code path
|
||||||
|
UASSERT(!fs::OpenStream(buf, path.c_str(), std::ios::in, false, true));
|
||||||
|
UASSERT(!buf.is_open());
|
||||||
|
|
||||||
|
auto ifs = open_ifstream(path.c_str(), false);
|
||||||
|
UASSERT(!ifs.good());
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user