diff --git a/CMakeLists.txt b/CMakeLists.txt index 75ae599cc3c..effdfff2e8b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,7 +163,7 @@ else() set(LOGROTATE_CREATE "\n\tcreate 644 ${ICINGA2_USER} ${ICINGA2_GROUP}") endif() -find_package(Boost ${BOOST_MIN_VERSION} COMPONENTS coroutine context date_time filesystem thread system program_options regex REQUIRED) +find_package(Boost ${BOOST_MIN_VERSION} COMPONENTS coroutine context date_time filesystem iostreams thread system program_options regex REQUIRED) # Boost.Coroutine2 (the successor of Boost.Coroutine) # (1) doesn't even exist in old Boost versions and 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..3081be79c6f --- /dev/null +++ b/lib/base/atomic-file.cpp @@ -0,0 +1,107 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#include "base/atomic-file.hpp" +#include "base/exception.hpp" +#include "base/utility.hpp" +#include + +#ifdef _WIN32 +# include +# include +#else /* _WIN32 */ +# include +# include +#endif /* _WIN32 */ + +using namespace icinga; + +AtomicFile::AtomicFile(String path, int mode) : m_Path(std::move(path)) +{ + m_TempFilename = m_Path + ".tmp.XXXXXX"; + +#ifdef _WIN32 + m_Fd = Utility::MksTemp(&m_TempFilename[0]); +#else /* _WIN32 */ + m_Fd = mkstemp(&m_TempFilename[0]); +#endif /* _WIN32 */ + + if (m_Fd < 0) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("mkstemp") + << boost::errinfo_errno(error) + << boost::errinfo_file_name(m_TempFilename)); + } + + try { + exceptions(failbit | badbit); + open(boost::iostreams::file_descriptor(m_Fd, boost::iostreams::never_close_handle)); + + if (chmod(m_TempFilename.CStr(), mode) < 0) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("chmod") + << boost::errinfo_errno(error) + << boost::errinfo_file_name(m_TempFilename)); + } + } catch (...) { + if (is_open()) { + close(); + } + + (void)::close(m_Fd); + (void)remove(m_TempFilename.CStr()); + throw; + } +} + +AtomicFile::~AtomicFile() +{ + if (is_open()) { + close(); + } + + if (m_Fd >= 0) { + (void)::close(m_Fd); + } + + if (!m_TempFilename.IsEmpty()) { + (void)remove(m_TempFilename.CStr()); + } +} + +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(); + (void)::close(m_Fd); + m_Fd = -1; + + 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..5ad79d914a1 --- /dev/null +++ b/lib/base/atomic-file.hpp @@ -0,0 +1,34 @@ +/* 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; + int m_Fd; +}; + +} + +#endif /* ATOMIC_FILE_H */ diff --git a/lib/base/utility.hpp b/lib/base/utility.hpp index 9ddb82dc4f8..6f9d03d447e 100644 --- a/lib/base/utility.hpp +++ b/lib/base/utility.hpp @@ -134,6 +134,10 @@ class Utility static String CreateTempFile(const String& path, int mode, std::fstream& fp); +#ifdef _WIN32 + static int MksTemp(char *tmpl); +#endif /* _WIN32 */ + #ifdef _WIN32 static String GetIcingaInstallPath(); static String GetIcingaDataPath(); @@ -185,10 +189,6 @@ class Utility private: Utility(); -#ifdef _WIN32 - static int MksTemp (char *tmpl); -#endif /* _WIN32 */ - #ifdef I2_DEBUG static double m_DebugTime; #endif /* I2_DEBUG */