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