blob: 52997890eec1aebe0306a72c7a2d2c1d99afa216 [file] [log] [blame]
#include <sys/stat.h>
#include <unistd.h>
#include <stdplus/fd/atomic.hpp>
#include <stdplus/fd/managed.hpp>
#include <stdplus/util/cexec.hpp>
#include <cstdlib>
#include <filesystem>
#include <format>
#include <system_error>
#include <utility>
namespace stdplus
{
namespace fd
{
static std::string makeTmpName(const std::filesystem::path& filename)
{
auto name = filename.filename();
auto path = filename.parent_path() /
std::format(".{}.XXXXXX", name.native());
return path.native();
}
static int mktemp(std::string& tmpl)
{
mode_t old = umask(0077);
int fd = mkstemp(tmpl.data());
umask(old);
return CHECK_ERRNO(fd, [&](int error) {
throw std::system_error(error, std::generic_category(),
std::format("mkstemp({})", tmpl));
});
}
AtomicWriter::AtomicWriter(const std::filesystem::path& filename, int mode,
std::string_view tmpl) :
filename(filename),
mode(mode),
tmpname(!tmpl.empty() ? std::string(tmpl) : makeTmpName(filename)),
fd(mktemp(tmpname))
{}
AtomicWriter::AtomicWriter(AtomicWriter&& other) :
filename(std::move(other.filename)), mode(other.mode),
tmpname(std::move(other.tmpname)), fd(std::move(other.fd))
{
// We don't want to cleanup twice
other.tmpname.clear();
}
AtomicWriter& AtomicWriter::operator=(AtomicWriter&& other)
{
if (this != &other)
{
filename = std::move(other.filename);
mode = other.mode;
tmpname = std::move(other.tmpname);
fd = std::move(other.fd);
// We don't want to cleanup twice
other.tmpname.clear();
}
return *this;
}
AtomicWriter::~AtomicWriter()
{
cleanup();
}
void AtomicWriter::commit(bool allow_copy)
{
try
{
CHECK_ERRNO(fsync(get()), "fsync");
CHECK_ERRNO(fchmod(get(), mode), "fchmod");
// We want the file to be closed before renaming it
{
auto ifd = std::move(fd);
}
try
{
std::filesystem::rename(tmpname, filename);
tmpname.clear();
}
catch (const std::filesystem::filesystem_error& e)
{
if (!allow_copy || e.code() != std::errc::cross_device_link)
{
throw;
}
std::filesystem::copy(tmpname, filename);
}
}
catch (...)
{
// We do this to ensure that any crashes that might happen before the
// destructor don't cause us to leave stale files.
cleanup();
throw;
}
}
int AtomicWriter::get() const
{
return fd.get();
}
void AtomicWriter::cleanup() noexcept
{
if (!tmpname.empty())
{
// Ensure the FD is closed prior to removing the file
{
auto ifd = std::move(fd);
}
std::error_code ec;
std::filesystem::remove(tmpname, ec);
tmpname.clear();
}
}
} // namespace fd
} // namespace stdplus