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
diff --git a/src/fd/fmt.cpp b/src/fd/fmt.cpp
index 0eeb6ae..94d3bc1 100644
--- a/src/fd/fmt.cpp
+++ b/src/fd/fmt.cpp
@@ -1,8 +1,5 @@
-#include <cstdlib>
#include <stdplus/fd/fmt.hpp>
#include <stdplus/fd/ops.hpp>
-#include <stdplus/util/cexec.hpp>
-#include <sys/stat.h>
namespace stdplus
{
@@ -35,41 +32,5 @@
}
}
-FormatToFile::FormatToFile(std::string_view tmpl) :
- tmpname(tmpl),
- fd(CHECK_ERRNO(mkstemp(tmpname.data()),
- [&](int error) {
- auto msg = fmt::format("mkstemp({})", tmpname);
- tmpname.clear();
- throw std::system_error(error, std::generic_category(),
- msg);
- })),
- buf(fd)
-{
-}
-
-FormatToFile::~FormatToFile()
-{
- if (!tmpname.empty())
- {
- std::error_code ec;
- std::filesystem::remove(tmpname, ec);
- }
-}
-
-void FormatToFile::commit(const std::filesystem::path& out, int mode)
-{
- {
- buf.flush();
- auto ifd = std::move(fd);
- }
- CHECK_ERRNO(chmod(tmpname.c_str(), mode), [&](int error) {
- throw std::system_error(error, std::generic_category(),
- fmt::format("chmod({}, {})", tmpname, mode));
- });
- std::filesystem::rename(tmpname, out);
- tmpname.clear();
-}
-
} // namespace fd
} // namespace stdplus
diff --git a/src/meson.build b/src/meson.build
index f937dc7..91fecb5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -24,6 +24,7 @@
if has_fd
stdplus_srcs += [
+ 'fd/atomic.cpp',
'fd/create.cpp',
'fd/dupable.cpp',
'fd/fmt.cpp',