fd/atomic: Better separate this orthogonal functionality
Nothing is using the old interface, so we can move it for better
separation of responisbilities.
Change-Id: I3b6e429b106aa22e58df25e0801b60af0fbd6d70
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/fd/atomic.cpp b/src/fd/atomic.cpp
new file mode 100644
index 0000000..df200e6
--- /dev/null
+++ b/src/fd/atomic.cpp
@@ -0,0 +1,125 @@
+#include <cstdlib>
+#include <filesystem>
+#include <fmt/format.h>
+#include <stdplus/fd/atomic.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <sys/stat.h>
+#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() / fmt::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(),
+ fmt::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