cvar: make config saves atomic and recover from malformed files

This commit is contained in:
mchughalex 2026-07-19 11:01:24 +01:00
parent a8e28405f9
commit fd11edea20

View file

@ -156,13 +156,35 @@ bool WriteConfigFile(const std::filesystem::path& config_path, const toml::table
} }
} }
std::ofstream file(config_path); // Write to a temporary sibling and rename it over the target so an
// interrupted write (crash, power loss, full disk) cannot leave a truncated
// config file behind.
std::filesystem::path temp_path = config_path;
temp_path += ".tmp";
{
std::ofstream file(temp_path, std::ios::trunc);
if (!file) { if (!file) {
REXLOG_ERROR("SaveConfig: failed to open {}", config_path.string()); REXLOG_ERROR("SaveConfig: failed to open {}", temp_path.string());
return false; return false;
} }
file << "# Auto-generated cvar configuration\n"; file << "# Auto-generated cvar configuration\n";
file << config; file << config;
file.flush();
if (!file) {
REXLOG_ERROR("SaveConfig: failed to write {}", temp_path.string());
std::error_code ec;
std::filesystem::remove(temp_path, ec);
return false;
}
}
std::error_code ec;
std::filesystem::rename(temp_path, config_path, ec);
if (ec) {
REXLOG_ERROR("SaveConfig: failed to replace {}: {}", config_path.string(), ec.message());
std::error_code remove_ec;
std::filesystem::remove(temp_path, remove_ec);
return false;
}
return true; return true;
} }
@ -656,9 +678,18 @@ void SaveConfigValues(const std::filesystem::path& config_path,
void SaveConfigValues(const std::filesystem::path& config_path, void SaveConfigValues(const std::filesystem::path& config_path,
const std::vector<std::string_view>& names) { const std::vector<std::string_view>& names) {
try { try {
// Merge into the existing file so keys saved by other callers survive. A
// malformed existing file must not abort the save: it would never load
// anyway, and rewriting it from the current cvar values is the only way
// persistence can recover.
toml::table config; toml::table config;
if (std::filesystem::exists(config_path)) { if (std::filesystem::exists(config_path)) {
try {
config = toml::parse_file(config_path.string()); config = toml::parse_file(config_path.string());
} catch (const toml::parse_error& err) {
REXLOG_WARN("SaveConfigValues: existing config {} is malformed ({}), rewriting it",
config_path.string(), err.description());
}
} }
{ {