fd/fmt: Add storage consistent formatted output
This makes it trivial for a caller to write to files piecewise while
still guaranteeing that the output is always consistent. It performs
buffered writes to a tmpfile and only once successful does it swap out
for the resulting file.
Change-Id: I7e733a283ee60a47ddc6923cd9579fa49a7c5434
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/fd/fmt.cpp b/src/fd/fmt.cpp
index fc6392d..217949d 100644
--- a/src/fd/fmt.cpp
+++ b/src/fd/fmt.cpp
@@ -1,5 +1,8 @@
+#include <cstdlib>
#include <stdplus/fd/fmt.hpp>
#include <stdplus/fd/ops.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <sys/stat.h>
namespace stdplus
{
@@ -32,5 +35,41 @@
}
}
+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