Introduce proper error handling for file streams

This commit is contained in:
sfan5 2024-05-08 20:37:10 +02:00
parent c38e0d05bf
commit 39fd9b93c3
23 changed files with 235 additions and 149 deletions

View File

@ -278,9 +278,7 @@ void Client::scanModSubfolder(const std::string &mod_name, const std::string &mo
<< "\" as \"" << vfs_path << "\"." << std::endl;
std::string contents;
if (!fs::ReadFile(real_path, contents)) {
errorstream << "Client::scanModSubfolder(): Can't read file \""
<< real_path << "\"." << std::endl;
if (!fs::ReadFile(real_path, contents, true)) {
continue;
}
@ -1945,8 +1943,7 @@ void Client::makeScreenshot()
while (serial < SCREENSHOT_MAX_SERIAL_TRIES) {
filename = filename_base + (serial > 0 ? ("_" + itos(serial)) : "") + filename_ext;
std::ifstream tmp(filename.c_str());
if (!tmp.good())
if (!fs::PathExists(filename))
break; // File did not apparently exist, we'll go with it
serial++;
}

View File

@ -38,13 +38,9 @@ void FileCache::createDir()
bool FileCache::loadByPath(const std::string &path, std::ostream &os)
{
std::ifstream fis(path.c_str(), std::ios_base::binary);
if(!fis.good()){
verbosestream<<"FileCache: File not found in cache: "
<<path<<std::endl;
auto fis = open_ifstream(path.c_str(), true);
if (!fis.good())
return false;
}
bool bad = false;
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)
{
createDir();
std::ofstream file(path.c_str(), std::ios_base::binary |
std::ios_base::trunc);
if(!file.good())
{
errorstream<<"FileCache: Can't write to file at "
<<path<<std::endl;
auto file = open_ofstream(path.c_str(), true);
if (!file.good())
return false;
}
file << data;
file.close();
@ -101,8 +92,7 @@ bool FileCache::load(const std::string &name, std::ostream &os)
bool FileCache::exists(const std::string &name)
{
std::string path = m_dir + DIR_DELIM + name;
std::ifstream fis(path.c_str(), std::ios_base::binary);
return fis.good();
return fs::PathExists(path);
}
bool FileCache::updateCopyFile(const std::string &name, const std::string &src_path)

View File

@ -160,10 +160,10 @@ public:
private:
StringMap m_programs;
std::string readFile(const std::string &path)
inline std::string readFile(const std::string &path)
{
std::string ret;
if (!fs::ReadFile(path, ret))
if (!fs::ReadFile(path, ret, true))
ret.clear();
return ret;
}

View File

@ -710,7 +710,8 @@ std::string ShadowRenderer::readShaderFile(const std::string &path)
prefix.append("#line 0\n");
std::string content;
fs::ReadFile(path, content);
if (!fs::ReadFile(path, content, true))
return "";
return prefix + content;
}

View File

@ -26,35 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
ContentType getContentType(const std::string &path)
{
std::ifstream modpack_is((path + DIR_DELIM + "modpack.txt").c_str());
if (modpack_is.good()) {
modpack_is.close();
if (fs::IsFile(path + DIR_DELIM "modpack.txt") || fs::IsFile(path + DIR_DELIM "modpack.conf"))
return ContentType::MODPACK;
}
std::ifstream modpack2_is((path + DIR_DELIM + "modpack.conf").c_str());
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();
if (fs::IsFile(path + DIR_DELIM "init.lua"))
return ContentType::MOD;
}
std::ifstream game_is((path + DIR_DELIM + "game.conf").c_str());
if (game_is.good()) {
game_is.close();
if (fs::IsFile(path + DIR_DELIM "game.conf"))
return ContentType::GAME;
}
std::ifstream txp_is((path + DIR_DELIM + "texture_pack.conf").c_str());
if (txp_is.good()) {
txp_is.close();
if (fs::IsFile(path + DIR_DELIM "texture_pack.conf"))
return ContentType::TXP;
}
return ContentType::UNKNOWN;
}
@ -66,19 +48,19 @@ void parseContentInfo(ContentSpec &spec)
switch (getContentType(spec.path)) {
case ContentType::MOD:
spec.type = "mod";
conf_path = spec.path + DIR_DELIM + "mod.conf";
conf_path = spec.path + DIR_DELIM "mod.conf";
break;
case ContentType::MODPACK:
spec.type = "modpack";
conf_path = spec.path + DIR_DELIM + "modpack.conf";
conf_path = spec.path + DIR_DELIM "modpack.conf";
break;
case ContentType::GAME:
spec.type = "game";
conf_path = spec.path + DIR_DELIM + "game.conf";
conf_path = spec.path + DIR_DELIM "game.conf";
break;
case ContentType::TXP:
spec.type = "txp";
conf_path = spec.path + DIR_DELIM + "texture_pack.conf";
conf_path = spec.path + DIR_DELIM "texture_pack.conf";
break;
default:
spec.type = "unknown";
@ -124,8 +106,6 @@ void parseContentInfo(ContentSpec &spec)
spec.textdomain = spec.name;
if (spec.desc.empty()) {
std::ifstream is((spec.path + DIR_DELIM + "description.txt").c_str());
spec.desc = std::string((std::istreambuf_iterator<char>(is)),
std::istreambuf_iterator<char>());
fs::ReadFile(spec.path + DIR_DELIM + "description.txt", spec.desc);
}
}

View File

@ -165,11 +165,9 @@ void PlayerDatabaseFiles::savePlayer(RemotePlayer *player)
}
// Open and deserialize file to check player name
std::ifstream is(path.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << "Failed to open " << path << std::endl;
auto is = open_ifstream(path.c_str(), true);
if (!is.good())
return;
}
deSerialize(&testplayer, is, path, NULL);
is.close();
@ -205,7 +203,7 @@ bool PlayerDatabaseFiles::removePlayer(const std::string &name)
RemotePlayer temp_player("", NULL);
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
// 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())
continue;
@ -231,7 +229,7 @@ bool PlayerDatabaseFiles::loadPlayer(RemotePlayer *player, PlayerSAO *sao)
const std::string player_to_load = player->getName();
for (u32 i = 0; i < PLAYER_FILE_ALTERNATE_TRIES; i++) {
// 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())
continue;
@ -260,7 +258,7 @@ void PlayerDatabaseFiles::listPlayers(std::vector<std::string> &res)
const std::string &filename = it->name;
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())
continue;
@ -332,7 +330,7 @@ void AuthDatabaseFiles::reload()
bool AuthDatabaseFiles::readAuthFile()
{
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()) {
return false;
}
@ -528,8 +526,7 @@ Json::Value *ModStorageDatabaseFiles::getOrCreateJson(const std::string &modname
std::string path = m_storage_dir + DIR_DELIM + modname;
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;
builder.settings_["collectComments"] = false;
std::string errs;

View File

@ -41,6 +41,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#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
{
@ -849,40 +856,44 @@ const char *GetFilenameFromPath(const char *path)
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
bool tmp_success = false;
// Write data to a temporary file
std::string write_error;
#ifdef _WIN32
// We've observed behavior suggesting that the MSVC implementation of std::ofstream::flush doesn't
// actually flush, so we use win32 APIs.
HANDLE tmp_handle = CreateFile(
tmp_file.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (tmp_handle == INVALID_HANDLE_VALUE) {
HANDLE handle = CreateFile(tmp_file.c_str(), GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
errorstream << "Failed to open file: " << LAST_OS_ERROR() << std::endl;
return false;
}
DWORD bytes_written;
tmp_success = (WriteFile(tmp_handle, content.data(), content.size(), &bytes_written, nullptr) &&
FlushFileBuffers(tmp_handle));
CloseHandle(tmp_handle);
if (!WriteFile(handle, content.data(), content.size(), &bytes_written, nullptr))
write_error = LAST_OS_ERROR();
else if (!FlushFileBuffers(handle))
write_error = LAST_OS_ERROR();
CloseHandle(handle);
#else
std::ofstream os(tmp_file.c_str(), std::ios::binary);
if (!os.good()) {
auto os = open_ofstream(tmp_file.c_str(), true);
if (!os.good())
return false;
}
os << content;
os.flush();
os << content << std::flush;
os.close();
tmp_success = !os.fail();
if (os.fail())
write_error = "iostream fail";
#endif
if (!tmp_success) {
if (!write_error.empty()) {
errorstream << "Failed to write file: " << write_error << std::endl;
remove(tmp_file.c_str());
return false;
}
bool rename_success = false;
std::string rename_error;
// Move the finished temporary file over the real file
#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.
// 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++) {
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);
if (rename_success)
if (ok) {
rename_error.clear();
break;
}
rename_error = LAST_OS_ERROR();
sleep_ms(1);
}
#else
// 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
// 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
if (!rename_success) {
warningstream << "Failed to write to file: " << path.c_str() << std::endl;
// Remove the temporary file because moving it over the target file
// failed.
if (!rename_error.empty()) {
errorstream << "Failed to overwrite \"" << path << "\": " << rename_error << std::endl;
remove(tmp_file.c_str());
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));
std::ofstream os(fullpath.c_str(), std::ios::binary);
auto os = open_ofstream(fullpath.c_str(), true);
if (!os.good())
return false;
@ -976,12 +990,11 @@ bool extractZipFile(io::IFileSystem *fs, const char *filename, const std::string
}
#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);
if (!is.good()) {
auto is = open_ifstream(path.c_str(), log_error, std::ios::ate);
if (!is.good())
return false;
}
auto size = is.tellg();
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;
}
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

View File

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <string>
#include <string_view>
#include <vector>
#include <fstream>
#ifdef _WIN32
#define DIR_DELIM "\\"
@ -148,14 +149,74 @@ std::string AbsolutePath(const std::string &path);
// delimiter is found.
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);
#ifndef SERVER
bool extractZipFile(irr::io::IFileSystem *fs, const char *filename, const std::string &destination);
#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);
/**
* 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
// 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;
}

View File

@ -147,18 +147,15 @@ static void MSVC_LocaleWorkaround(int argc, char* argv[])
exit(0);
// NOTREACHED
} else {
char buffer[1024];
auto e = GetLastError();
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(),
MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), buffer,
sizeof(buffer) - 1, NULL);
errorstream << "*******************************************************" << std::endl;
errorstream << "CMD: " << app_name << std::endl;
errorstream << "Failed to restart with current locale: " << std::endl;
errorstream << buffer;
errorstream << "Expect language to be broken!" << std::endl;
errorstream << "*******************************************************" << std::endl;
errorstream
<< "*******************************************************" << '\n'
<< "CMD: " << app_name << '\n';
<< "Failed to restart with current locale: ";
<< porting::ConvertError(e) << '\n';
<< "Expect language to be broken!" << '\n';
<< "*******************************************************" << std::endl;
}
}

View File

@ -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)
{
#if USE_CURL
std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
if (!target_file.good()) {
auto target_file = open_ofstream(target.c_str(), true);
if (!target_file.good())
return false;
}
HTTPFetchRequest fetch_request;
HTTPFetchResult fetch_result;

View File

@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "exceptions.h"
#include "util/numeric.h"
#include "log.h"
#include "filesys.h"
#ifdef __ANDROID__
#include <android/log.h>
@ -36,7 +37,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <sstream>
#include <iostream>
#include <algorithm>
#include <cerrno>
#include <cstring>
class LevelTarget : public LogTarget {
@ -299,25 +299,24 @@ void FileLogOutput::setFile(const std::string &filename, s64 file_size_max)
bool is_too_large = false;
if (file_size_max > 0) {
std::ifstream ifile(filename, std::ios::binary | std::ios::ate);
is_too_large = ifile.tellg() > file_size_max;
ifile.close();
if (ifile.good())
is_too_large = ifile.tellg() > file_size_max;
}
if (is_too_large) {
std::string filename_secondary = filename + ".1";
actionstream << "The log file grew too big; it is moved to " <<
filename_secondary << std::endl;
remove(filename_secondary.c_str());
rename(filename.c_str(), filename_secondary.c_str());
fs::DeleteSingleFileOrEmptyDirectory(filename_secondary);
fs::Rename(filename, filename_secondary);
}
m_stream.open(filename, std::ios::app | std::ios::ate);
if (!m_stream.good())
throw FileNotGoodException("Failed to open log file " +
filename + ": " + strerror(errno));
// Intentionally not using open_ofstream() to keep the text mode
if (!fs::OpenStream(*m_stream.rdbuf(), filename.c_str(), std::ios::out | std::ios::app, true, false))
throw FileNotGoodException("Failed to open log file");
m_stream << "\n\n"
"-------------" << std::endl <<
" Separator" << std::endl <<
"-------------\n" <<
" Separator\n" <<
"-------------\n" << std::endl;
}

View File

@ -96,13 +96,9 @@ bool MapSettingsManager::setMapSettingNoiseParams(
bool MapSettingsManager::loadMapMeta()
{
std::ifstream is(m_map_meta_path.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << "loadMapMeta: could not open "
<< m_map_meta_path << std::endl;
auto is = open_ifstream(m_map_meta_path.c_str(), true);
if (!is.good())
return false;
}
if (!m_map_settings->parseConfigLines(is)) {
errorstream << "loadMapMeta: Format error. '[end_of_params]' missing?" << std::endl;

View File

@ -485,12 +485,9 @@ bool Schematic::serializeToLua(std::ostream *os, bool use_comments,
bool Schematic::loadSchematicFromFile(const std::string &filename,
const NodeDefManager *ndef, StringMap *replace_names)
{
std::ifstream is(filename.c_str(), std::ios_base::binary);
if (!is.good()) {
errorstream << __FUNCTION__ << ": unable to open file '"
<< filename << "'" << std::endl;
auto is = open_ifstream(filename.c_str(), true);
if (!is.good())
return false;
}
if (!m_ndef)
m_ndef = ndef;

View File

@ -582,7 +582,7 @@ static void createCacheDirTag()
if (fs::PathExists(path))
return;
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())
return;
ofs << "Signature: 8a477f597d28d172789f06886806bc55\n"
@ -800,6 +800,21 @@ std::string QuoteArgv(const std::string &arg)
ret.push_back('"');
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
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...)

View File

@ -293,6 +293,9 @@ void attachOrCreateConsole();
#ifdef _WIN32
// Quotes an argument for use in a CreateProcess() commandline (not cmd.exe!!)
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
int mt_snprintf(char *buf, const size_t buf_size, const char *fmt, ...);

View File

@ -298,7 +298,7 @@ int LuaAreaStore::l_from_file(lua_State *L)
const char *filename = luaL_checkstring(L, 2);
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);
}

View File

@ -2545,9 +2545,7 @@ bool Server::addMediaFile(const std::string &filename,
// Read data
std::string filedata;
if (!fs::ReadFile(filepath, filedata)) {
errorstream << "Server::addMediaFile(): Failed to open \""
<< filename << "\" for reading" << std::endl;
if (!fs::ReadFile(filepath, filedata, true)) {
return false;
}
@ -2715,9 +2713,7 @@ void Server::sendRequestedMedia(session_t peer_id,
// Read data
std::string data;
if (!fs::ReadFile(m.path, data)) {
errorstream << "Server::sendRequestedMedia(): Failed to read \""
<< name << "\"" << std::endl;
if (!fs::ReadFile(m.path, data, true)) {
continue;
}
file_size_bunch_total += data.size();
@ -3618,7 +3614,7 @@ namespace {
auto filepath = fs::CreateTempFile();
if (filepath.empty())
return "";
std::ofstream os(filepath, std::ios::binary);
auto os = open_ofstream(filepath.c_str(), true);
if (!os.good())
return "";
os << content;
@ -4200,7 +4196,7 @@ Translations *Server::getTranslationLanguage(const std::string &lang_code)
for (const auto &i : m_media) {
if (str_ends_with(i.first, suffix)) {
std::string data;
if (fs::ReadFile(i.second.path, data)) {
if (fs::ReadFile(i.second.path, data, true)) {
translations->loadTranslation(data);
}
}

View File

@ -48,9 +48,8 @@ void BanManager::load()
{
MutexAutoLock lock(m_mutex);
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()) {
infostream<<"BanManager: failed loading from "<<m_banfilepath<<std::endl;
throw SerializationError("BanManager::load(): Couldn't open file");
}

View File

@ -392,11 +392,8 @@ bool Settings::updateConfigFile(const char *filename)
if (!was_modified)
return true;
if (!fs::safeWriteToFile(filename, os.str())) {
errorstream << "Error writing configuration file: \""
<< filename << "\"" << std::endl;
if (!fs::safeWriteToFile(filename, os.str()))
return false;
}
return true;
}

View File

@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "texture_override.h"
#include "log.h"
#include "filesys.h"
#include "util/string.h"
#include <algorithm>
#include <fstream>
@ -48,7 +49,7 @@ static const std::map<std::string, OverrideTarget> override_LUT = {
TextureOverrideSource::TextureOverrideSource(const std::string &filepath)
{
std::ifstream infile(filepath.c_str());
auto infile = open_ifstream(filepath.c_str(), false);
std::string line;
int line_index = 0;
while (std::getline(infile, line)) {

View File

@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "modchannels.h"
#include "util/numeric.h"
#include "porting.h"
#include "debug.h"
content_t t_CONTENT_STONE;
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 << "[FAIL] ";
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 << "[FAIL] ";
num_tests_failed++;
}
#endif
num_tests_run++;
u64 tdiff = porting::getTimeMs() - t1;
rawstream << name << " - " << tdiff << "ms" << std::endl;

View File

@ -80,13 +80,13 @@ void TestBan::testCreate()
BanManager bm(m_testbm);
}
UASSERT(std::ifstream(m_testbm, std::ios::binary).is_open());
UASSERT(fs::IsFile(m_testbm));
// test manual save
{
BanManager bm(m_testbm2);
bm.save();
UASSERT(std::ifstream(m_testbm2, std::ios::binary).is_open());
UASSERT(fs::IsFile(m_testbm2));
}
}

View File

@ -41,6 +41,7 @@ public:
void testRemoveRelativePathComponent();
void testSafeWriteToFile();
void testCopyFileContents();
void testNonExist();
};
static TestFileSys g_test_instance;
@ -54,6 +55,7 @@ void TestFileSys::runTests(IGameDef *gamedef)
TEST(testRemoveRelativePathComponent);
TEST(testSafeWriteToFile);
TEST(testCopyFileContents);
TEST(testNonExist);
}
////////////////////////////////////////////////////////////////////////////////
@ -311,3 +313,27 @@ void TestFileSys::testCopyFileContents()
UASSERT(fs::ReadFile(file2, contents_actual));
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());
}