blob: ce9dff24a9aac10336a4e49cdfca9329b508e669 [file] [log] [blame]
William A. Kennington IIIaa4fcfc2022-08-23 16:35:10 -07001#include <fmt/format.h>
Patrick Williamsd1984dd2023-05-10 16:12:44 -05002#include <sys/stat.h>
3
William A. Kennington IIIaa4fcfc2022-08-23 16:35:10 -07004#include <stdplus/fd/atomic.hpp>
5#include <stdplus/fd/managed.hpp>
6#include <stdplus/util/cexec.hpp>
Patrick Williamsd1984dd2023-05-10 16:12:44 -05007
8#include <cstdlib>
9#include <filesystem>
William A. Kennington IIIaa4fcfc2022-08-23 16:35:10 -070010#include <system_error>
11#include <utility>
12
13namespace stdplus
14{
15namespace fd
16{
17
18static std::string makeTmpName(const std::filesystem::path& filename)
19{
20 auto name = filename.filename();
Patrick Williamsd1984dd2023-05-10 16:12:44 -050021 auto path = filename.parent_path() /
22 fmt::format(".{}.XXXXXX", name.native());
William A. Kennington IIIaa4fcfc2022-08-23 16:35:10 -070023 return path.native();
24}
25
26static int mktemp(std::string& tmpl)
27{
28 mode_t old = umask(0077);
29 int fd = mkstemp(tmpl.data());
30 umask(old);
31 return CHECK_ERRNO(fd, [&](int error) {
32 throw std::system_error(error, std::generic_category(),
33 fmt::format("mkstemp({})", tmpl));
34 });
35}
36
37AtomicWriter::AtomicWriter(const std::filesystem::path& filename, int mode,
38 std::string_view tmpl) :
39 filename(filename),
40 mode(mode),
41 tmpname(!tmpl.empty() ? std::string(tmpl) : makeTmpName(filename)),
42 fd(mktemp(tmpname))
Patrick Williamsd1984dd2023-05-10 16:12:44 -050043{}
William A. Kennington IIIaa4fcfc2022-08-23 16:35:10 -070044
45AtomicWriter::AtomicWriter(AtomicWriter&& other) :
46 filename(std::move(other.filename)), mode(other.mode),
47 tmpname(std::move(other.tmpname)), fd(std::move(other.fd))
48{
49 // We don't want to cleanup twice
50 other.tmpname.clear();
51}
52
53AtomicWriter& AtomicWriter::operator=(AtomicWriter&& other)
54{
55 if (this != &other)
56 {
57 filename = std::move(other.filename);
58 mode = other.mode;
59 tmpname = std::move(other.tmpname);
60 fd = std::move(other.fd);
61
62 // We don't want to cleanup twice
63 other.tmpname.clear();
64 }
65 return *this;
66}
67
68AtomicWriter::~AtomicWriter()
69{
70 cleanup();
71}
72
73void AtomicWriter::commit(bool allow_copy)
74{
75 try
76 {
77 CHECK_ERRNO(fsync(get()), "fsync");
78 CHECK_ERRNO(fchmod(get(), mode), "fchmod");
79 // We want the file to be closed before renaming it
80 {
81 auto ifd = std::move(fd);
82 }
83 try
84 {
85 std::filesystem::rename(tmpname, filename);
86 tmpname.clear();
87 }
88 catch (const std::filesystem::filesystem_error& e)
89 {
90 if (!allow_copy || e.code() != std::errc::cross_device_link)
91 {
92 throw;
93 }
94 std::filesystem::copy(tmpname, filename);
95 }
96 }
97 catch (...)
98 {
99 // We do this to ensure that any crashes that might happen before the
100 // destructor don't cause us to leave stale files.
101 cleanup();
102 throw;
103 }
104}
105
106int AtomicWriter::get() const
107{
108 return fd.get();
109}
110
111void AtomicWriter::cleanup() noexcept
112{
113 if (!tmpname.empty())
114 {
115 // Ensure the FD is closed prior to removing the file
116 {
117 auto ifd = std::move(fd);
118 }
119 std::error_code ec;
120 std::filesystem::remove(tmpname, ec);
121 tmpname.clear();
122 }
123}
124
125} // namespace fd
126} // namespace stdplus