blob: 6b7530cb19f7584ea713d2836e0864295ac61a57 [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) :
Patrick Williamsd8e0af52024-08-16 15:21:12 -040040 filename(filename), mode(mode),
William A. Kennington IIIaa4fcfc2022-08-23 16:35:10 -070041 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