diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index f992018220d..18e884de2b6 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -17,6 +17,7 @@ set(base_SOURCES application.cpp application.hpp application-ti.hpp application-version.cpp application-environment.cpp array.cpp array.hpp array-script.cpp atomic.hpp + atomic-file.cpp atomic-file.hpp base64.cpp base64.hpp boolean.cpp boolean.hpp boolean-script.cpp bulker.hpp diff --git a/lib/base/atomic-file.cpp b/lib/base/atomic-file.cpp new file mode 100644 index 00000000000..139ce050750 --- /dev/null +++ b/lib/base/atomic-file.cpp @@ -0,0 +1,64 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#include "base/atomic-file.hpp" +#include "base/exception.hpp" +#include "base/utility.hpp" +#include + +#ifdef _WIN32 +# include +#else /* _WIN32 */ +# include +# include +#endif /* _WIN32 */ + +using namespace icinga; + +AtomicFile::AtomicFile(String path, int mode) : m_Path(std::move(path)) +{ + exceptions(failbit | badbit); + m_TempFilename = Utility::CreateTempFile(m_Path + ".tmp.XXXXXX", mode, *this); +} + +AtomicFile::~AtomicFile() +{ + if (!m_TempFilename.IsEmpty()) { + try { + Utility::Remove(m_TempFilename); + } catch (...) { + // A destructor must not throw + } + } +} + +void AtomicFile::Commit() +{ + flush(); + + auto h ((*this)->handle()); + +#ifdef _WIN32 + if (!FlushFileBuffers(h)) { + auto err (GetLastError()); + + BOOST_THROW_EXCEPTION(win32_error() + << boost::errinfo_api_function("FlushFileBuffers") + << errinfo_win32_error(err) + << boost::errinfo_file_name(m_TempFilename)); + } +#else /* _WIN32 */ + if (fsync(h)) { + auto err (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fsync") + << boost::errinfo_errno(err) + << boost::errinfo_file_name(m_TempFilename)); + } +#endif /* _WIN32 */ + + close(); + + Utility::RenameFile(m_TempFilename, m_Path); + m_TempFilename = ""; +} diff --git a/lib/base/atomic-file.hpp b/lib/base/atomic-file.hpp new file mode 100644 index 00000000000..a5c3238ec5f --- /dev/null +++ b/lib/base/atomic-file.hpp @@ -0,0 +1,33 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#ifndef ATOMIC_FILE_H +#define ATOMIC_FILE_H + +#include "base/string.hpp" +#include +#include + +namespace icinga +{ + +/** + * Atomically replaces a file's content. + * + * @ingroup base + */ +class AtomicFile : public boost::iostreams::stream +{ +public: + AtomicFile(String path, int mode); + ~AtomicFile(); + + void Commit(); + +private: + String m_Path; + String m_TempFilename; +}; + +} + +#endif /* ATOMIC_FILE_H */