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