Merge pull request #9466 from Icinga/flush-temp-files

Deduplicate and stabilize fragile filesystem transactions
This commit is contained in:
Julian Brost 2023-02-02 16:29:11 +01:00 committed by GitHub
commit c51037725a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 71 additions and 182 deletions

View File

@ -15,6 +15,13 @@
using namespace icinga; using namespace icinga;
void AtomicFile::Write(String path, int mode, const String& content)
{
AtomicFile af (path, mode);
af << content;
af.Commit();
}
AtomicFile::AtomicFile(String path, int mode) : m_Path(std::move(path)) AtomicFile::AtomicFile(String path, int mode) : m_Path(std::move(path))
{ {
m_TempFilename = m_Path + ".tmp.XXXXXX"; m_TempFilename = m_Path + ".tmp.XXXXXX";

View File

@ -18,9 +18,16 @@ namespace icinga
class AtomicFile : public boost::iostreams::stream<boost::iostreams::file_descriptor> class AtomicFile : public boost::iostreams::stream<boost::iostreams::file_descriptor>
{ {
public: public:
static void Write(String path, int mode, const String& content);
AtomicFile(String path, int mode); AtomicFile(String path, int mode);
~AtomicFile(); ~AtomicFile();
inline const String& GetTempFilename() const noexcept
{
return m_TempFilename;
}
void Commit(); void Commit();
private: private:

View File

@ -1,5 +1,6 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "base/atomic-file.hpp"
#include "base/scriptglobal.hpp" #include "base/scriptglobal.hpp"
#include "base/singleton.hpp" #include "base/singleton.hpp"
#include "base/logger.hpp" #include "base/logger.hpp"
@ -83,12 +84,7 @@ void ScriptGlobal::WriteToFile(const String& filename)
Log(LogInformation, "ScriptGlobal") Log(LogInformation, "ScriptGlobal")
<< "Dumping variables to file '" << filename << "'"; << "Dumping variables to file '" << filename << "'";
std::fstream fp; AtomicFile fp (filename, 0600);
String tempFilename = Utility::CreateTempFile(filename + ".XXXXXX", 0600, fp);
if (!fp)
BOOST_THROW_EXCEPTION(std::runtime_error("Could not open '" + tempFilename + "' file"));
StdioStream::Ptr sfp = new StdioStream(&fp, false); StdioStream::Ptr sfp = new StdioStream(&fp, false);
ObjectLock olock(m_Globals); ObjectLock olock(m_Globals);
@ -109,9 +105,6 @@ void ScriptGlobal::WriteToFile(const String& filename)
} }
sfp->Close(); sfp->Close();
fp.Commit();
fp.close();
Utility::RenameFile(tempFilename, filename);
} }

View File

@ -1,5 +1,6 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "base/atomic-file.hpp"
#include "base/utility.hpp" #include "base/utility.hpp"
#include "base/convert.hpp" #include "base/convert.hpp"
#include "base/application.hpp" #include "base/application.hpp"
@ -1443,16 +1444,7 @@ Value Utility::LoadJsonFile(const String& path)
void Utility::SaveJsonFile(const String& path, int mode, const Value& value) void Utility::SaveJsonFile(const String& path, int mode, const Value& value)
{ {
namespace fs = boost::filesystem; AtomicFile::Write(path, mode, JsonEncode(value));
std::fstream fp;
String tempFilename = Utility::CreateTempFile(path + ".XXXXXX", mode, fp);
fp.exceptions(std::ofstream::failbit | std::ofstream::badbit);
fp << JsonEncode(value);
fp.close();
RenameFile(tempFilename, path);
} }
static void HexEncode(char ch, std::ostream& os) static void HexEncode(char ch, std::ostream& os)
@ -1791,46 +1783,6 @@ String Utility::ValidateUTF8(const String& input)
return String(std::move(output)); return String(std::move(output));
} }
String Utility::CreateTempFile(const String& path, int mode, std::fstream& fp)
{
std::vector<char> targetPath(path.Begin(), path.End());
targetPath.push_back('\0');
int fd;
#ifndef _WIN32
fd = mkstemp(&targetPath[0]);
#else /* _WIN32 */
fd = MksTemp(&targetPath[0]);
#endif /*_WIN32*/
if (fd < 0) {
BOOST_THROW_EXCEPTION(posix_error()
<< boost::errinfo_api_function("mkstemp")
<< boost::errinfo_errno(errno)
<< boost::errinfo_file_name(path));
}
try {
fp.open(&targetPath[0], std::ios_base::trunc | std::ios_base::out);
} catch (const std::fstream::failure&) {
close(fd);
throw;
}
close(fd);
String resultPath = String(targetPath.begin(), targetPath.end() - 1);
if (chmod(resultPath.CStr(), mode) < 0) {
BOOST_THROW_EXCEPTION(posix_error()
<< boost::errinfo_api_function("chmod")
<< boost::errinfo_errno(errno)
<< boost::errinfo_file_name(resultPath));
}
return resultPath;
}
#ifdef _WIN32 #ifdef _WIN32
/* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright /* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright
* (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc. * (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc.

View File

@ -132,8 +132,6 @@ public:
static String ValidateUTF8(const String& input); static String ValidateUTF8(const String& input);
static String CreateTempFile(const String& path, int mode, std::fstream& fp);
#ifdef _WIN32 #ifdef _WIN32
static int MksTemp(char *tmpl); static int MksTemp(char *tmpl);
#endif /* _WIN32 */ #endif /* _WIN32 */

View File

@ -5,6 +5,7 @@
#include "cli/featureutility.hpp" #include "cli/featureutility.hpp"
#include "remote/apilistener.hpp" #include "remote/apilistener.hpp"
#include "remote/pkiutility.hpp" #include "remote/pkiutility.hpp"
#include "base/atomic-file.hpp"
#include "base/logger.hpp" #include "base/logger.hpp"
#include "base/console.hpp" #include "base/console.hpp"
#include "base/application.hpp" #include "base/application.hpp"
@ -160,8 +161,7 @@ bool ApiSetupUtility::SetupMasterApiUser()
NodeUtility::CreateBackupFile(apiUsersPath); NodeUtility::CreateBackupFile(apiUsersPath);
std::fstream fp; AtomicFile fp (apiUsersPath, 0644);
String tempFilename = Utility::CreateTempFile(apiUsersPath + ".XXXXXX", 0644, fp);
fp << "/**\n" fp << "/**\n"
<< " * The ApiUser objects are used for authentication against the API.\n" << " * The ApiUser objects are used for authentication against the API.\n"
@ -173,9 +173,7 @@ bool ApiSetupUtility::SetupMasterApiUser()
<< " permissions = [ \"*\" ]\n" << " permissions = [ \"*\" ]\n"
<< "}\n"; << "}\n";
fp.close(); fp.Commit();
Utility::RenameFile(tempFilename, apiUsersPath);
return true; return true;
} }

View File

@ -6,6 +6,7 @@
#include "cli/apisetuputility.hpp" #include "cli/apisetuputility.hpp"
#include "remote/apilistener.hpp" #include "remote/apilistener.hpp"
#include "remote/pkiutility.hpp" #include "remote/pkiutility.hpp"
#include "base/atomic-file.hpp"
#include "base/logger.hpp" #include "base/logger.hpp"
#include "base/console.hpp" #include "base/console.hpp"
#include "base/application.hpp" #include "base/application.hpp"
@ -172,8 +173,7 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v
String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf"; String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
NodeUtility::CreateBackupFile(apipath); NodeUtility::CreateBackupFile(apipath);
std::fstream fp; AtomicFile fp (apipath, 0644);
String tempApiPath = Utility::CreateTempFile(apipath + ".XXXXXX", 0644, fp);
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
@ -205,9 +205,7 @@ int NodeSetupCommand::SetupMaster(const boost::program_options::variables_map& v
<< " ticket_salt = TicketSalt\n" << " ticket_salt = TicketSalt\n"
<< "}\n"; << "}\n";
fp.close(); fp.Commit();
Utility::RenameFile(tempApiPath, apipath);
/* update constants.conf with NodeName = CN + TicketSalt = random value */ /* update constants.conf with NodeName = CN + TicketSalt = random value */
if (endpointName != Utility::GetFQDN()) { if (endpointName != Utility::GetFQDN()) {
@ -447,8 +445,7 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf"; String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
NodeUtility::CreateBackupFile(apipath); NodeUtility::CreateBackupFile(apipath);
std::fstream fp; AtomicFile fp (apipath, 0644);
String tempApiPath = Utility::CreateTempFile(apipath + ".XXXXXX", 0644, fp);
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
@ -479,9 +476,7 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
fp << "\n" fp << "\n"
<< "}\n"; << "}\n";
fp.close(); fp.Commit();
Utility::RenameFile(tempApiPath, apipath);
/* Generate zones configuration. */ /* Generate zones configuration. */
Log(LogInformation, "cli", "Generating zone and object configuration."); Log(LogInformation, "cli", "Generating zone and object configuration.");
@ -529,21 +524,17 @@ int NodeSetupCommand::SetupNode(const boost::program_options::variables_map& vm,
if (!ticket.IsEmpty()) { if (!ticket.IsEmpty()) {
String ticketPath = ApiListener::GetCertsDir() + "/ticket"; String ticketPath = ApiListener::GetCertsDir() + "/ticket";
AtomicFile af (ticketPath, 0600);
String tempTicketPath = Utility::CreateTempFile(ticketPath + ".XXXXXX", 0600, fp); if (!Utility::SetFileOwnership(af.GetTempFilename(), user, group)) {
if (!Utility::SetFileOwnership(tempTicketPath, user, group)) {
Log(LogWarning, "cli") Log(LogWarning, "cli")
<< "Cannot set ownership for user '" << user << "Cannot set ownership for user '" << user
<< "' group '" << group << "' group '" << group
<< "' on file '" << tempTicketPath << "'. Verify it yourself!"; << "' on file '" << ticketPath << "'. Verify it yourself!";
} }
fp << ticket; af << ticket;
af.Commit();
fp.close();
Utility::RenameFile(tempTicketPath, ticketPath);
} }
/* If no parent connection was made, the user must supply the ca.crt before restarting Icinga 2.*/ /* If no parent connection was made, the user must supply the ca.crt before restarting Icinga 2.*/

View File

@ -3,6 +3,7 @@
#include "cli/nodeutility.hpp" #include "cli/nodeutility.hpp"
#include "cli/clicommand.hpp" #include "cli/clicommand.hpp"
#include "cli/variableutility.hpp" #include "cli/variableutility.hpp"
#include "base/atomic-file.hpp"
#include "base/logger.hpp" #include "base/logger.hpp"
#include "base/application.hpp" #include "base/application.hpp"
#include "base/tlsutility.hpp" #include "base/tlsutility.hpp"
@ -161,8 +162,7 @@ bool NodeUtility::WriteNodeConfigObjects(const String& filename, const Array::Pt
<< "Cannot set ownership for user '" << user << "' group '" << group << "' on path '" << path << "'. Verify it yourself!"; << "Cannot set ownership for user '" << user << "' group '" << group << "' on path '" << path << "'. Verify it yourself!";
} }
std::fstream fp; AtomicFile fp (filename, 0644);
String tempFilename = Utility::CreateTempFile(filename + ".XXXXXX", 0644, fp);
fp << "/*\n"; fp << "/*\n";
fp << " * Generated by Icinga 2 node setup commands\n"; fp << " * Generated by Icinga 2 node setup commands\n";
@ -175,9 +175,7 @@ bool NodeUtility::WriteNodeConfigObjects(const String& filename, const Array::Pt
} }
fp << std::endl; fp << std::endl;
fp.close(); fp.Commit();
Utility::RenameFile(tempFilename, filename);
return true; return true;
} }
@ -295,8 +293,7 @@ bool NodeUtility::UpdateConfiguration(const String& value, bool include, bool re
NodeUtility::CreateBackupFile(configurationFile); NodeUtility::CreateBackupFile(configurationFile);
std::ifstream ifp(configurationFile.CStr()); std::ifstream ifp(configurationFile.CStr());
std::fstream ofp; AtomicFile ofp (configurationFile, 0644);
String tempFile = Utility::CreateTempFile(configurationFile + ".XXXXXX", 0644, ofp);
String affectedInclude = value; String affectedInclude = value;
@ -345,9 +342,7 @@ bool NodeUtility::UpdateConfiguration(const String& value, bool include, bool re
} }
ifp.close(); ifp.close();
ofp.close(); ofp.Commit();
Utility::RenameFile(tempFile, configurationFile);
return (found || include); return (found || include);
} }
@ -362,8 +357,7 @@ void NodeUtility::UpdateConstant(const String& name, const String& value)
NodeUtility::CreateBackupFile(constantsConfPath); NodeUtility::CreateBackupFile(constantsConfPath);
std::ifstream ifp(constantsConfPath.CStr()); std::ifstream ifp(constantsConfPath.CStr());
std::fstream ofp; AtomicFile ofp (constantsConfPath, 0644);
String tempFile = Utility::CreateTempFile(constantsConfPath + ".XXXXXX", 0644, ofp);
bool found = false; bool found = false;
@ -380,7 +374,5 @@ void NodeUtility::UpdateConstant(const String& name, const String& value)
ofp << "const " + name + " = \"" + value + "\"\n"; ofp << "const " + name + " = \"" + value + "\"\n";
ifp.close(); ifp.close();
ofp.close(); ofp.Commit();
Utility::RenameFile(tempFile, constantsConfPath);
} }

View File

@ -6,6 +6,7 @@
#include "cli/apisetuputility.hpp" #include "cli/apisetuputility.hpp"
#include "remote/apilistener.hpp" #include "remote/apilistener.hpp"
#include "remote/pkiutility.hpp" #include "remote/pkiutility.hpp"
#include "base/atomic-file.hpp"
#include "base/logger.hpp" #include "base/logger.hpp"
#include "base/console.hpp" #include "base/console.hpp"
#include "base/application.hpp" #include "base/application.hpp"
@ -451,8 +452,7 @@ wizard_ticket:
String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf"; String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
NodeUtility::CreateBackupFile(apiConfPath); NodeUtility::CreateBackupFile(apiConfPath);
std::fstream fp; AtomicFile fp (apiConfPath, 0644);
String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp);
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
@ -468,9 +468,7 @@ wizard_ticket:
fp << "}\n"; fp << "}\n";
fp.close(); fp.Commit();
Utility::RenameFile(tempApiConfPath, apiConfPath);
/* Zones configuration. */ /* Zones configuration. */
Log(LogInformation, "cli", "Generating local zones.conf."); Log(LogInformation, "cli", "Generating local zones.conf.");
@ -555,21 +553,17 @@ wizard_global_zone_loop_start:
if (!ticket.IsEmpty()) { if (!ticket.IsEmpty()) {
String ticketPath = ApiListener::GetCertsDir() + "/ticket"; String ticketPath = ApiListener::GetCertsDir() + "/ticket";
AtomicFile af (ticketPath, 0600);
String tempTicketPath = Utility::CreateTempFile(ticketPath + ".XXXXXX", 0600, fp); if (!Utility::SetFileOwnership(af.GetTempFilename(), user, group)) {
if (!Utility::SetFileOwnership(tempTicketPath, user, group)) {
Log(LogWarning, "cli") Log(LogWarning, "cli")
<< "Cannot set ownership for user '" << user << "Cannot set ownership for user '" << user
<< "' group '" << group << "' group '" << group
<< "' on file '" << tempTicketPath << "'. Verify it yourself!"; << "' on file '" << ticketPath << "'. Verify it yourself!";
} }
fp << ticket; af << ticket;
af.Commit();
fp.close();
Utility::RenameFile(tempTicketPath, ticketPath);
} }
/* If no parent connection was made, the user must supply the ca.crt before restarting Icinga 2.*/ /* If no parent connection was made, the user must supply the ca.crt before restarting Icinga 2.*/
@ -745,8 +739,7 @@ wizard_global_zone_loop_start:
String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf"; String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
NodeUtility::CreateBackupFile(apiConfPath); NodeUtility::CreateBackupFile(apiConfPath);
std::fstream fp; AtomicFile fp (apiConfPath, 0644);
String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp);
fp << "/**\n" fp << "/**\n"
<< " * The API listener is used for distributed monitoring setups.\n" << " * The API listener is used for distributed monitoring setups.\n"
@ -762,9 +755,7 @@ wizard_global_zone_loop_start:
<< " ticket_salt = TicketSalt\n" << " ticket_salt = TicketSalt\n"
<< "}\n"; << "}\n";
fp.close(); fp.Commit();
Utility::RenameFile(tempApiConfPath, apiConfPath);
/* update constants.conf with NodeName = CN + TicketSalt = random value */ /* update constants.conf with NodeName = CN + TicketSalt = random value */
if (cn != Utility::GetFQDN()) { if (cn != Utility::GetFQDN()) {

View File

@ -13,6 +13,7 @@
#include "icinga/compatutility.hpp" #include "icinga/compatutility.hpp"
#include "icinga/pluginutility.hpp" #include "icinga/pluginutility.hpp"
#include "icinga/dependency.hpp" #include "icinga/dependency.hpp"
#include "base/atomic-file.hpp"
#include "base/configtype.hpp" #include "base/configtype.hpp"
#include "base/objectlock.hpp" #include "base/objectlock.hpp"
#include "base/json.hpp" #include "base/json.hpp"
@ -544,8 +545,7 @@ void StatusDataWriter::UpdateObjectsCache()
/* Use the compat path here from the .ti generated class. */ /* Use the compat path here from the .ti generated class. */
String objectsPath = GetObjectsPath(); String objectsPath = GetObjectsPath();
std::fstream objectfp; AtomicFile objectfp (objectsPath, 0644);
String tempObjectsPath = Utility::CreateTempFile(objectsPath + ".XXXXXX", 0644, objectfp);
objectfp << std::fixed; objectfp << std::fixed;
@ -760,9 +760,7 @@ void StatusDataWriter::UpdateObjectsCache()
} }
} }
objectfp.close(); objectfp.Commit();
Utility::RenameFile(tempObjectsPath, objectsPath);
} }
/** /**
@ -779,8 +777,7 @@ void StatusDataWriter::StatusTimerHandler()
String statusPath = GetStatusPath(); String statusPath = GetStatusPath();
std::fstream statusfp; AtomicFile statusfp (statusPath, 0644);
String tempStatusPath = Utility::CreateTempFile(statusPath + ".XXXXXX", 0644, statusfp);
statusfp << std::fixed; statusfp << std::fixed;
@ -833,9 +830,7 @@ void StatusDataWriter::StatusTimerHandler()
} }
} }
statusfp.close(); statusfp.Commit();
Utility::RenameFile(tempStatusPath, statusPath);
Log(LogNotice, "StatusDataWriter") Log(LogNotice, "StatusDataWriter")
<< "Writing status.dat file took " << Utility::FormatDuration(Utility::GetTime() - start); << "Writing status.dat file took " << Utility::FormatDuration(Utility::GetTime() - start);

View File

@ -17,20 +17,12 @@ ConfigCompilerContext *ConfigCompilerContext::GetInstance()
void ConfigCompilerContext::OpenObjectsFile(const String& filename) void ConfigCompilerContext::OpenObjectsFile(const String& filename)
{ {
m_ObjectsPath = filename;
auto *fp = new std::fstream();
try { try {
m_ObjectsTempFile = Utility::CreateTempFile(filename + ".XXXXXX", 0600, *fp); m_ObjectsFP = std::make_unique<AtomicFile>(filename, 0600);
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
Log(LogCritical, "cli", "Could not create temporary objects file: " + DiagnosticInformation(ex, false)); Log(LogCritical, "cli", "Could not create temporary objects file: " + DiagnosticInformation(ex, false));
Application::Exit(1); Application::Exit(1);
} }
if (!*fp)
BOOST_THROW_EXCEPTION(std::runtime_error("Could not open '" + m_ObjectsTempFile + "' file"));
m_ObjectsFP = fp;
} }
void ConfigCompilerContext::WriteObject(const Dictionary::Ptr& object) void ConfigCompilerContext::WriteObject(const Dictionary::Ptr& object)
@ -51,14 +43,7 @@ void ConfigCompilerContext::CancelObjectsFile()
if (!m_ObjectsFP) if (!m_ObjectsFP)
return; return;
delete m_ObjectsFP; m_ObjectsFP.reset(nullptr);
m_ObjectsFP = nullptr;
#ifdef _WIN32
_unlink(m_ObjectsTempFile.CStr());
#else /* _WIN32 */
unlink(m_ObjectsTempFile.CStr());
#endif /* _WIN32 */
} }
void ConfigCompilerContext::FinishObjectsFile() void ConfigCompilerContext::FinishObjectsFile()
@ -66,9 +51,7 @@ void ConfigCompilerContext::FinishObjectsFile()
if (!m_ObjectsFP) if (!m_ObjectsFP)
return; return;
delete m_ObjectsFP; m_ObjectsFP->Commit();
m_ObjectsFP = nullptr; m_ObjectsFP.reset(nullptr);
Utility::RenameFile(m_ObjectsTempFile, m_ObjectsPath);
} }

View File

@ -4,8 +4,10 @@
#define CONFIGCOMPILERCONTEXT_H #define CONFIGCOMPILERCONTEXT_H
#include "config/i2-config.hpp" #include "config/i2-config.hpp"
#include "base/atomic-file.hpp"
#include "base/dictionary.hpp" #include "base/dictionary.hpp"
#include <fstream> #include <fstream>
#include <memory>
#include <mutex> #include <mutex>
namespace icinga namespace icinga
@ -24,15 +26,13 @@ public:
inline bool IsOpen() const noexcept inline bool IsOpen() const noexcept
{ {
return m_ObjectsFP; return (bool)m_ObjectsFP;
} }
static ConfigCompilerContext *GetInstance(); static ConfigCompilerContext *GetInstance();
private: private:
String m_ObjectsPath; std::unique_ptr<AtomicFile> m_ObjectsFP;
String m_ObjectsTempFile;
std::fstream *m_ObjectsFP{nullptr};
mutable std::mutex m_Mutex; mutable std::mutex m_Mutex;
}; };

View File

@ -5,6 +5,7 @@
#include "icinga/cib.hpp" #include "icinga/cib.hpp"
#include "icinga/macroprocessor.hpp" #include "icinga/macroprocessor.hpp"
#include "config/configcompiler.hpp" #include "config/configcompiler.hpp"
#include "base/atomic-file.hpp"
#include "base/configwriter.hpp" #include "base/configwriter.hpp"
#include "base/configtype.hpp" #include "base/configtype.hpp"
#include "base/exception.hpp" #include "base/exception.hpp"
@ -125,7 +126,7 @@ void IcingaApplication::OnShutdown()
DumpProgramState(); DumpProgramState();
} }
static void PersistModAttrHelper(std::fstream& fp, ConfigObject::Ptr& previousObject, const ConfigObject::Ptr& object, const String& attr, const Value& value) static void PersistModAttrHelper(AtomicFile& fp, ConfigObject::Ptr& previousObject, const ConfigObject::Ptr& object, const String& attr, const Value& value)
{ {
if (object != previousObject) { if (object != previousObject) {
if (previousObject) { if (previousObject) {
@ -174,9 +175,7 @@ void IcingaApplication::DumpModifiedAttributes()
Log(LogWarning, "IcingaApplication") << DiagnosticInformation(ex); Log(LogWarning, "IcingaApplication") << DiagnosticInformation(ex);
} }
std::fstream fp; AtomicFile fp (path, 0644);
String tempFilename = Utility::CreateTempFile(path + ".tmp.XXXXXX", 0644, fp);
fp.exceptions(std::ofstream::failbit | std::ofstream::badbit);
ConfigObject::Ptr previousObject; ConfigObject::Ptr previousObject;
ConfigObject::DumpModifiedAttributes([&fp, &previousObject](const ConfigObject::Ptr& object, const String& attr, const Value& value) { ConfigObject::DumpModifiedAttributes([&fp, &previousObject](const ConfigObject::Ptr& object, const String& attr, const Value& value) {
@ -189,9 +188,7 @@ void IcingaApplication::DumpModifiedAttributes()
ConfigWriter::EmitRaw(fp, "\n}\n"); ConfigWriter::EmitRaw(fp, "\n}\n");
} }
fp.close(); fp.Commit();
Utility::RenameFile(tempFilename, path);
} }
IcingaApplication::Ptr IcingaApplication::GetInstance() IcingaApplication::Ptr IcingaApplication::GetInstance()

View File

@ -8,6 +8,7 @@
#include "remote/apifunction.hpp" #include "remote/apifunction.hpp"
#include "remote/configpackageutility.hpp" #include "remote/configpackageutility.hpp"
#include "remote/configobjectutility.hpp" #include "remote/configobjectutility.hpp"
#include "base/atomic-file.hpp"
#include "base/convert.hpp" #include "base/convert.hpp"
#include "base/defer.hpp" #include "base/defer.hpp"
#include "base/io-engine.hpp" #include "base/io-engine.hpp"
@ -324,14 +325,7 @@ void ApiListener::RenewOwnCert()
return; return;
} }
std::fstream certfp; AtomicFile::Write(certPath, 0644, CertificateToString(cert));
auto tempCertPath (Utility::CreateTempFile(certPath + ".XXXXXX", 0644, certfp));
certfp.exceptions(std::ofstream::failbit | std::ofstream::badbit);
certfp << CertificateToString(cert);
certfp.close();
Utility::RenameFile(tempCertPath, certPath);
UpdateSSLContext(); UpdateSSLContext();
} }

View File

@ -4,6 +4,7 @@
#include "remote/apilistener.hpp" #include "remote/apilistener.hpp"
#include "remote/apifunction.hpp" #include "remote/apifunction.hpp"
#include "remote/jsonrpc.hpp" #include "remote/jsonrpc.hpp"
#include "base/atomic-file.hpp"
#include "base/configtype.hpp" #include "base/configtype.hpp"
#include "base/objectlock.hpp" #include "base/objectlock.hpp"
#include "base/utility.hpp" #include "base/utility.hpp"
@ -368,12 +369,7 @@ Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionar
Log(LogInformation, "JsonRpcConnection") Log(LogInformation, "JsonRpcConnection")
<< "Updating CA certificate in '" << caPath << "'."; << "Updating CA certificate in '" << caPath << "'.";
std::fstream cafp; AtomicFile::Write(caPath, 0644, ca);
String tempCaPath = Utility::CreateTempFile(caPath + ".XXXXXX", 0644, cafp);
cafp << ca;
cafp.close();
Utility::RenameFile(tempCaPath, caPath);
/* Update signed certificate. */ /* Update signed certificate. */
String certPath = listener->GetDefaultCertPath(); String certPath = listener->GetDefaultCertPath();
@ -381,12 +377,7 @@ Value UpdateCertificateHandler(const MessageOrigin::Ptr& origin, const Dictionar
Log(LogInformation, "JsonRpcConnection") Log(LogInformation, "JsonRpcConnection")
<< "Updating client certificate for CN '" << cn << "' in '" << certPath << "'."; << "Updating client certificate for CN '" << cn << "' in '" << certPath << "'.";
std::fstream certfp; AtomicFile::Write(certPath, 0644, cert);
String tempCertPath = Utility::CreateTempFile(certPath + ".XXXXXX", 0644, certfp);
certfp << cert;
certfp.close();
Utility::RenameFile(tempCertPath, certPath);
/* Remove ticket for successful signing request. */ /* Remove ticket for successful signing request. */
String ticketPath = ApiListener::GetCertsDir() + "/ticket"; String ticketPath = ApiListener::GetCertsDir() + "/ticket";