Introduce AtomicFile

This commit is contained in:
Alexander A. Klimov 2022-07-21 17:53:08 +02:00
parent 913566cbfa
commit 600fb0e3c2
5 changed files with 156 additions and 5 deletions

View File

@ -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

View File

@ -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

116
lib/base/atomic-file.cpp Normal file
View File

@ -0,0 +1,116 @@
/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */
#include "base/atomic-file.hpp"
#include "base/exception.hpp"
#include "base/utility.hpp"
#include <utility>
#ifdef _WIN32
# include <io.h>
# include <windows.h>
#else /* _WIN32 */
# include <errno.h>
# include <unistd.h>
#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,
// Rationale: https://github.com/boostorg/iostreams/issues/152
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)unlink(m_TempFilename.CStr());
throw;
}
}
AtomicFile::~AtomicFile()
{
if (is_open()) {
try {
close();
} catch (...) {
// Destructor must not throw
}
}
if (m_Fd >= 0) {
(void)::close(m_Fd);
}
if (!m_TempFilename.IsEmpty()) {
(void)unlink(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 = "";
}

34
lib/base/atomic-file.hpp Normal file
View File

@ -0,0 +1,34 @@
/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */
#ifndef ATOMIC_FILE_H
#define ATOMIC_FILE_H
#include "base/string.hpp"
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
namespace icinga
{
/**
* Atomically replaces a file's content.
*
* @ingroup base
*/
class AtomicFile : public boost::iostreams::stream<boost::iostreams::file_descriptor>
{
public:
AtomicFile(String path, int mode);
~AtomicFile();
void Commit();
private:
String m_Path;
String m_TempFilename;
int m_Fd;
};
}
#endif /* ATOMIC_FILE_H */

View File

@ -134,6 +134,10 @@ public:
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 @@ public:
private:
Utility();
#ifdef _WIN32
static int MksTemp (char *tmpl);
#endif /* _WIN32 */
#ifdef I2_DEBUG
static double m_DebugTime;
#endif /* I2_DEBUG */