From 2e108c577370151e9ee0b063de1963e05c4a3522 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Sat, 8 Oct 2022 15:19:11 +0100 Subject: [PATCH] [Minor] Add some more methods and tests to the file raii abstraction --- src/libutil/cxx/locked_file.cxx | 66 +++++++++++++++++++++++++++++++-- src/libutil/cxx/locked_file.hxx | 38 +++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/libutil/cxx/locked_file.cxx b/src/libutil/cxx/locked_file.cxx index 6abc4a1b0..bd25b0fc7 100644 --- a/src/libutil/cxx/locked_file.cxx +++ b/src/libutil/cxx/locked_file.cxx @@ -127,6 +127,40 @@ auto raii_locked_file::create_temp(const char *fname, int flags, int perms) -> t return ret; } +auto raii_locked_file::mkstemp(const char *pattern, int flags, int perms) -> tl::expected +{ + int oflags = flags; +#ifdef O_CLOEXEC + oflags |= O_CLOEXEC | O_CREAT | O_EXCL; +#endif + if (pattern == nullptr) { + return tl::make_unexpected("cannot open file; pattern is nullptr"); + } + + std::string mutable_pattern = pattern; + + auto fd = g_mkstemp_full(mutable_pattern.data(), oflags, perms); + + if (fd == -1) { + return tl::make_unexpected(fmt::format("cannot create file {}: {}", pattern, ::strerror(errno))); + } + + if (!rspamd_file_lock(fd, TRUE)) { + close(fd); + (void)unlink(mutable_pattern.c_str()); + return tl::make_unexpected(fmt::format("cannot lock file {}: {}", pattern, ::strerror(errno))); + } + + auto ret = raii_locked_file{mutable_pattern.c_str(), fd, true}; + + if (fstat(ret.fd, &ret.st) == -1) { + return tl::make_unexpected(fmt::format("cannot stat file {}: {}", + mutable_pattern.c_str(), ::strerror(errno))); + } + + return ret; +} + raii_mmaped_locked_file::raii_mmaped_locked_file(raii_locked_file &&_file, void *_map) : file(std::move(_file)), map(_map) { @@ -242,7 +276,7 @@ static auto test_write_file(const T& f, const std::string_view &buf) { (void)::lseek(fd, 0, SEEK_SET); return ::write(fd, buf.data(), buf.size()); } -auto random_fname() { +auto random_fname(std::string_view extension) { const auto *tmpdir = getenv("TMPDIR"); if (tmpdir == nullptr) { tmpdir = G_DIR_SEPARATOR_S "tmp"; @@ -254,16 +288,21 @@ auto random_fname() { unsigned char hexbuf[32]; rspamd_random_hex(hexbuf, sizeof(hexbuf)); out_fname.append((const char *)hexbuf, sizeof(hexbuf)); + if (!extension.empty()) { + out_fname.append("."); + out_fname.append(extension); + } return out_fname; } TEST_SUITE("loked files utils") { TEST_CASE("create and delete file") { - auto fname = random_fname(); + auto fname = random_fname("tmp"); { auto raii_locked_file = raii_locked_file::create_temp(fname.c_str(), O_RDONLY, 00600); CHECK(raii_locked_file.has_value()); + CHECK(raii_locked_file.value().get_extension() == "tmp"); CHECK(::access(fname.c_str(), R_OK) == 0); } // File must be deleted after this call @@ -284,10 +323,11 @@ TEST_CASE("create and delete file") { } TEST_CASE("check lock") { - auto fname = random_fname(); + auto fname = random_fname(""); { auto raii_locked_file = raii_locked_file::create_temp(fname.c_str(), O_RDONLY, 00600); CHECK(raii_locked_file.has_value()); + CHECK(raii_locked_file.value().get_extension() == ""); CHECK(::access(fname.c_str(), R_OK) == 0); auto raii_locked_file2 = raii_locked_file::open(fname.c_str(), O_RDONLY); CHECK(!raii_locked_file2.has_value()); @@ -300,6 +340,26 @@ TEST_CASE("check lock") { CHECK(serrno == ENOENT); } +TEST_CASE("tempfile") { + std::string tmpname; + { + auto raii_locked_file = raii_locked_file::mkstemp("/tmp//doctest-XXXXXXXX", + O_RDONLY, 00600); + CHECK(raii_locked_file.has_value()); + CHECK(raii_locked_file.value().get_dir() == "/tmp"); + CHECK(access(raii_locked_file.value().get_name().data(), R_OK) == 0); + auto raii_locked_file2 = raii_locked_file::open(raii_locked_file.value().get_name().data(), O_RDONLY); + CHECK(!raii_locked_file2.has_value()); + CHECK(access(raii_locked_file.value().get_name().data(), R_OK) == 0); + tmpname = raii_locked_file.value().get_name(); + } + // File must be deleted after this call + auto ret = ::access(tmpname.c_str(), R_OK); + auto serrno = errno; + CHECK(ret == -1); + CHECK(serrno == ENOENT); +} + } // TEST_SUITE } // namespace tests diff --git a/src/libutil/cxx/locked_file.hxx b/src/libutil/cxx/locked_file.hxx index 6ac4ffd2c..ce25b0a5a 100644 --- a/src/libutil/cxx/locked_file.hxx +++ b/src/libutil/cxx/locked_file.hxx @@ -17,6 +17,7 @@ #define RSPAMD_LOCKED_FILE_HXX #pragma once +#include "config.h" #include "contrib/expected/expected.hpp" #include #include @@ -32,6 +33,7 @@ struct raii_locked_file final { static auto open(const char *fname, int flags) -> tl::expected; static auto create(const char *fname, int flags, int perms) -> tl::expected; static auto create_temp(const char *fname, int flags, int perms) -> tl::expected; + static auto mkstemp(const char *pattern, int flags, int perms) -> tl::expected; auto get_fd() const -> int { return fd; @@ -41,6 +43,42 @@ struct raii_locked_file final { return st; }; + auto get_name() const -> std::string_view { + return std::string_view{fname}; + } + + auto get_dir() const -> std::string_view { + auto sep_pos = fname.rfind(G_DIR_SEPARATOR); + + if (sep_pos == std::string::npos) { + return std::string_view{fname}; + } + + while (sep_pos >= 1 && fname[sep_pos - 1] == G_DIR_SEPARATOR) { + sep_pos --; + } + + return std::string_view{fname.c_str(), sep_pos}; + } + + auto get_extension() const -> std::string_view { + auto sep_pos = fname.rfind(G_DIR_SEPARATOR); + + if (sep_pos == std::string::npos) { + sep_pos = 0; + } + + auto filename = std::string_view{fname.c_str() + sep_pos}; + auto dot_pos = filename.find('.'); + + if (dot_pos == std::string::npos) { + return std::string_view{}; + } + else { + return std::string_view{filename.data() + dot_pos + 1, filename.size() - dot_pos - 1}; + } + } + raii_locked_file& operator=(raii_locked_file &&other) noexcept { std::swap(fd, other.fd); std::swap(temp, other.temp); -- 2.39.5