diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-06-30 07:51:23 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-06-30 07:51:23 +0100 |
| commit | 034cd404a129103a8dd7747e6bd00ffd5550da93 (patch) | |
| tree | d27946517d4d9333abd26ac50bbd4a436093e2ce /nihil.posix/tempfile.ccm | |
| parent | 3e7902f7d790a486d3d9cb978df193f07f3a6ad9 (diff) | |
| download | nihil-034cd404a129103a8dd7747e6bd00ffd5550da93.tar.gz nihil-034cd404a129103a8dd7747e6bd00ffd5550da93.tar.bz2 | |
refactoring
Diffstat (limited to 'nihil.posix/tempfile.ccm')
| -rw-r--r-- | nihil.posix/tempfile.ccm | 162 |
1 files changed, 114 insertions, 48 deletions
diff --git a/nihil.posix/tempfile.ccm b/nihil.posix/tempfile.ccm index 82f3be4..e1510e5 100644 --- a/nihil.posix/tempfile.ccm +++ b/nihil.posix/tempfile.ccm @@ -1,16 +1,13 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -/* - * tempfile: create a temporary file. - */ +// tempfile: create a temporary file. +#include <algorithm> #include <cstdint> #include <expected> #include <filesystem> +#include <random> #include <string> export module nihil.posix:tempfile; @@ -18,70 +15,139 @@ export module nihil.posix:tempfile; import nihil.error; import nihil.flagset; import :fd; +import :getenv; +import :open; namespace nihil { -struct tempfile_flags_tag {}; -export using tempfile_flags_t = flagset<std::uint8_t, tempfile_flags_tag>; +struct tempfile_flags_tag +{ +}; +export using tempfile_flags = flagset<std::uint8_t, tempfile_flags_tag>; // No flags. -export inline constexpr auto tempfile_none = tempfile_flags_t(); +export inline constexpr auto tempfile_none = tempfile_flags(); // Unlink the tempfile immediately after creating it -export inline constexpr auto tempfile_unlink = tempfile_flags_t::bit<0>(); - -export struct temporary_file final { - /* - * Fetch the file's fd. - */ - [[nodiscard]] auto fd(this temporary_file &) -> nihil::fd &; - - /* - * Fetch the name of this file. If tempfile_unlink was specified, - * throws std::logic_error. - */ - [[nodiscard]] auto path(this temporary_file const &) - -> std::filesystem::path const &; - - /* - * Release this temporary file, causing it to be deleted immediately. - * Throws std::logic_error if the file has already been released. - */ - auto release(this temporary_file &) -> void; - - /* - * Destructor; unlink the file if we didn't already. - */ - ~temporary_file(); +export inline constexpr auto tempfile_unlink = tempfile_flags::bit<0>(); + +export struct temporary_file final +{ + // Fetch the file's fd. + [[nodiscard]] auto fd(this temporary_file &self) -> nihil::fd & + { + if (!self.m_fd) + throw std::logic_error("fd() called on empty temporary_file"); + + return self.m_fd; + } + + // Fetch the name of this file. If tempfile_unlink was specified, + // throws std::logic_error. + [[nodiscard]] auto path(this temporary_file const &self) -> std::filesystem::path const & + { + if (self.m_path.empty()) + throw std::logic_error("path() called on unlinked temporary_file"); + + return self.m_path; + } + + // Release this temporary file, causing it to be closed and deleted immediately + // Throws std::logic_error if the file has already been released. + auto release(this temporary_file &self) -> void + { + if (!self.m_fd) + throw std::logic_error("release() called on already-released tempfile"); + + if (!self.m_path.empty()) { + auto ec = std::error_code(); // ignore errors + remove(self.path(), ec); + + self.m_path.clear(); + } + + std::ignore = self.m_fd.close(); + } + + // Destructor; unlink the file if we didn't already. + ~temporary_file() // NOLINT + { + if (m_fd) + release(); + } // Not copyable. temporary_file(temporary_file const &) = delete; // Movable. - temporary_file(temporary_file &&other) noexcept; + temporary_file(temporary_file &&other) noexcept = default; // Not assignable. - auto operator=(this temporary_file &, temporary_file const &) - -> temporary_file & = delete; - auto operator=(this temporary_file &, temporary_file &&) noexcept - -> temporary_file & = delete; + auto operator=(this temporary_file &, temporary_file const &) -> temporary_file & = delete; + auto + operator=(this temporary_file &, temporary_file &&) noexcept -> temporary_file & = delete; private: // The file descriptor for the file. - nihil::fd m_fd; - std::filesystem::path m_path; + nihil::fd m_fd; + std::filesystem::path m_path; + + temporary_file(nihil::fd &&fd, std::filesystem::path path) noexcept + : m_fd(std::move(fd)) + , m_path(std::move(path)) + { + } - temporary_file(nihil::fd &&fd, std::filesystem::path) noexcept; - temporary_file(nihil::fd &&fd) noexcept; + explicit temporary_file(nihil::fd &&fd) noexcept + : m_fd(std::move(fd)) + { + } - friend auto tempfile(tempfile_flags_t flags) - -> std::expected<temporary_file, error>; + friend auto tempfile(tempfile_flags flags) -> std::expected<temporary_file, error>; }; /* * Create a temporary file and return it. */ -export [[nodiscard]] auto tempfile(tempfile_flags_t flags = tempfile_none) - -> std::expected<temporary_file, error>; +export [[nodiscard]] auto +tempfile(tempfile_flags flags = tempfile_none) -> std::expected<temporary_file, error> +{ + auto rng = std::default_random_engine(std::random_device{}()); + + auto random_name = [&] -> std::string { + auto constexpr length = std::size_t{12}; + auto constexpr randchars = std::string_view("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"); + + auto dist = std::uniform_int_distribution<>(0, randchars.size() - 1); + auto ret = std::string(length, 0); + std::ranges::generate_n(ret.begin(), length, + [&] -> char { return randchars[dist(rng)]; }); + return ret; + }; + + auto dir = std::filesystem::path(getenv("TMPDIR").value_or("/tmp")); // NOLINT + + // Keep trying until we don't get EEXIST. + for (;;) { + auto filename = dir / (random_name() + ".tmp"); + auto fd = + nihil::open(filename, open_readwrite | open_create | open_exclusive, 0600); + if (!fd) { + if (fd.error() == std::errc::file_exists) + continue; + return std::unexpected(fd.error()); + } + + if (flags & tempfile_unlink) { + auto ec = std::error_code(); + remove(filename, ec); + return temporary_file(std::move(*fd)); + } + + return temporary_file(std::move(*fd), std::move(filename)); + } +} } // namespace nihil |
