From c3f1dd7ace14f00c2fa16a76bf47d171250966fe Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 26 Feb 2026 14:57:27 -0400 Subject: [PATCH] Upgrade Core to `120af1f2c2992fca1d193aada8441a6717c62193` Signed-off-by: Juan Cruz Viotti --- DEPENDENCIES | 2 +- .../include/sourcemeta/core/jsonpointer.h | 2 +- .../include/sourcemeta/core/jsonschema.h | 4 +- .../core/uri/include/sourcemeta/core/uri.h | 6 +- vendor/core/src/lang/io/CMakeLists.txt | 4 +- .../src/lang/io/include/sourcemeta/core/io.h | 34 ++++++++ .../io/include/sourcemeta/core/io_temporary.h | 52 +++++++++++++ vendor/core/src/lang/io/io.cc | 77 ++++++++++++++++++- vendor/core/src/lang/io/io_temporary.cc | 66 ++++++++++++++++ .../include/sourcemeta/core/process_error.h | 4 +- vendor/core/src/lang/process/spawn.cc | 15 +++- 11 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 vendor/core/src/lang/io/include/sourcemeta/core/io_temporary.h create mode 100644 vendor/core/src/lang/io/io_temporary.cc diff --git a/DEPENDENCIES b/DEPENDENCIES index 577e229b..ac4e18d8 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,6 +1,6 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 uwebsockets https://github.com/uNetworking/uWebSockets v20.75.0 -core https://github.com/sourcemeta/core 932001d6c7966143355a39701a6d248ae6d91ac2 +core https://github.com/sourcemeta/core 120af1f2c2992fca1d193aada8441a6717c62193 blaze https://github.com/sourcemeta/blaze e4d790d56981aa746702a4041a205cc3afdfcfb1 jsonbinpack https://github.com/sourcemeta/jsonbinpack c9038f1ca016890cf0dc6929e668b77784f91332 hydra https://github.com/sourcemeta/hydra 2f46a5e2d1b24fd1bdd2d4b98e71646403a6bdce diff --git a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h index 7682ae53..2f2e16c0 100644 --- a/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h +++ b/vendor/core/src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h @@ -415,7 +415,7 @@ auto to_pointer(const std::basic_string std::optional; /// @ingroup jsonschema -/// A default schema walker with support for a wide range of drafs +/// A default schema walker with support for a wide range of drafts SOURCEMETA_CORE_JSONSCHEMA_EXPORT auto schema_walker(const std::string_view keyword, const Vocabularies &vocabularies) @@ -408,7 +408,7 @@ auto wrap(std::string_view identifier) -> JSON; /// @ingroup jsonschema /// /// Wrap a schema to only access one of its subschemas. This is useful if you -/// want to perform validation only a specific part of the schemaw without +/// want to perform validation on only a specific part of the schema without /// having to reinvent the wheel. For example: /// /// ```cpp diff --git a/vendor/core/src/core/uri/include/sourcemeta/core/uri.h b/vendor/core/src/core/uri/include/sourcemeta/core/uri.h index a4bae290..cce65752 100644 --- a/vendor/core/src/core/uri/include/sourcemeta/core/uri.h +++ b/vendor/core/src/core/uri/include/sourcemeta/core/uri.h @@ -335,7 +335,7 @@ class SOURCEMETA_CORE_URI_EXPORT URI { /// /// const sourcemeta::core::URI /// uri{"https://www.sourcemeta.com/foo#bar"}; - /// assert(uri.recompose_without_fragment().has_value()"); + /// assert(uri.recompose_without_fragment().has_value()); /// assert(uri.recompose_without_fragment().value() == /// "https://sourcemeta.com/foo"); /// ``` @@ -349,7 +349,7 @@ class SOURCEMETA_CORE_URI_EXPORT URI { /// #include /// /// sourcemeta::core::URI uri{"hTtP://exAmpLe.com:80/TEST"}; - /// uri.canonicalize(): + /// uri.canonicalize(); /// assert(uri.recompose() == "http://example.com/TEST"); /// ``` auto canonicalize() -> URI &; @@ -417,7 +417,7 @@ class SOURCEMETA_CORE_URI_EXPORT URI { /// /// const sourcemeta::core::URI uri{"https://user:@host"}; /// assert(uri.userinfo().has_value()); - /// assert(uri.userinfo().value() == "user:); + /// assert(uri.userinfo().value() == "user:"); /// ``` /// /// As mentioned in RFC 3986, the format "user:password" is deprecated. diff --git a/vendor/core/src/lang/io/CMakeLists.txt b/vendor/core/src/lang/io/CMakeLists.txt index a890265e..b2ce0559 100644 --- a/vendor/core/src/lang/io/CMakeLists.txt +++ b/vendor/core/src/lang/io/CMakeLists.txt @@ -1,6 +1,6 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME io - PRIVATE_HEADERS error.h fileview.h - SOURCES io.cc io_fileview.cc) + PRIVATE_HEADERS error.h fileview.h temporary.h + SOURCES io.cc io_fileview.cc io_temporary.cc) if(SOURCEMETA_CORE_INSTALL) sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME io) diff --git a/vendor/core/src/lang/io/include/sourcemeta/core/io.h b/vendor/core/src/lang/io/include/sourcemeta/core/io.h index ae03a233..aa949f94 100644 --- a/vendor/core/src/lang/io/include/sourcemeta/core/io.h +++ b/vendor/core/src/lang/io/include/sourcemeta/core/io.h @@ -8,6 +8,7 @@ // NOLINTBEGIN(misc-include-cleaner) #include #include +#include // NOLINTEND(misc-include-cleaner) #include // assert @@ -99,6 +100,39 @@ auto read_file(const std::filesystem::path &path) return stream; } +/// @ingroup io +/// +/// Recursively mirror a directory tree using hard links for regular files. +/// Directories are created, regular files are hard-linked. Both paths must +/// reside on the same filesystem. The destination must not be inside the +/// source tree, as that would cause infinite recursion. +/// +/// ```cpp +/// #include +/// +/// sourcemeta::core::hardlink_directory("/source", "/destination"); +/// ``` +SOURCEMETA_CORE_IO_EXPORT +auto hardlink_directory(const std::filesystem::path &source, + const std::filesystem::path &destination) -> void; + +/// @ingroup io +/// +/// Replace one directory with another, guaranteeing an atomic swap when +/// possible. Both directories must reside on the same filesystem and the +/// original path must not be a bare filename (it must have a parent +/// component). If the original does not exist, the replacement is simply +/// renamed into place. +/// +/// ```cpp +/// #include +/// +/// sourcemeta::core::atomic_directory_replace("/output", "/staging"); +/// ``` +SOURCEMETA_CORE_IO_EXPORT +auto atomic_directory_replace(const std::filesystem::path &original, + const std::filesystem::path &replacement) -> void; + /// @ingroup io /// /// Flush an existing file to disk, beyond just to the operating system. For diff --git a/vendor/core/src/lang/io/include/sourcemeta/core/io_temporary.h b/vendor/core/src/lang/io/include/sourcemeta/core/io_temporary.h new file mode 100644 index 00000000..a6049c3e --- /dev/null +++ b/vendor/core/src/lang/io/include/sourcemeta/core/io_temporary.h @@ -0,0 +1,52 @@ +#ifndef SOURCEMETA_CORE_IO_TEMPORARY_H_ +#define SOURCEMETA_CORE_IO_TEMPORARY_H_ + +#ifndef SOURCEMETA_CORE_IO_EXPORT +#include +#endif + +#include // std::filesystem::path +#include // std::string_view + +namespace sourcemeta::core { + +/// @ingroup io +/// An RAII class that creates a uniquely-named temporary directory on +/// construction and removes it on destruction. +/// +/// ```cpp +/// #include +/// #include +/// +/// sourcemeta::core::TemporaryDirectory staging{"/tmp", ".my-prefix-"}; +/// assert(std::filesystem::exists(staging.path())); +/// ``` +class SOURCEMETA_CORE_IO_EXPORT TemporaryDirectory { +public: + TemporaryDirectory(const std::filesystem::path &parent, + const std::string_view prefix); + ~TemporaryDirectory(); + + TemporaryDirectory(const TemporaryDirectory &) = delete; + auto operator=(const TemporaryDirectory &) -> TemporaryDirectory & = delete; + TemporaryDirectory(TemporaryDirectory &&) = delete; + auto operator=(TemporaryDirectory &&) -> TemporaryDirectory & = delete; + + [[nodiscard]] auto path() const noexcept -> const std::filesystem::path &; + +private: +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251) +#endif + std::filesystem::path path_; +#if defined(_MSC_VER) +#pragma warning(default : 4251) +#endif +}; + +} // namespace sourcemeta::core + +#endif diff --git a/vendor/core/src/lang/io/io.cc b/vendor/core/src/lang/io/io.cc index 0c09bce8..cea7b373 100644 --- a/vendor/core/src/lang/io/io.cc +++ b/vendor/core/src/lang/io/io.cc @@ -6,8 +6,14 @@ #include // HANDLE, CreateFileW, FlushFileBuffers, CloseHandle #else #include // errno (for error codes) -#include // open, O_RDWR +#include // open, O_RDWR, AT_FDCWD #include // close, fsync +#if defined(__linux__) +#include // RENAME_EXCHANGE +#include // renameat2 +#elif defined(__APPLE__) +#include // renameatx_np, RENAME_SWAP +#endif #endif namespace sourcemeta::core { @@ -47,6 +53,75 @@ auto starts_with(const std::filesystem::path &path, return true; } +auto hardlink_directory(const std::filesystem::path &source, + const std::filesystem::path &destination) -> void { + assert(std::filesystem::is_directory(source)); + assert(!std::filesystem::exists(destination) || + std::filesystem::is_directory(destination)); + assert(!starts_with(destination, source)); + std::filesystem::create_directories(destination); + for (const auto &entry : + std::filesystem::recursive_directory_iterator{source}) { + const auto target{destination / + std::filesystem::relative(entry.path(), source)}; + if (entry.is_directory()) { + std::filesystem::create_directories(target); + } else if (entry.is_regular_file()) { + std::filesystem::create_hard_link(entry.path(), target); + } + } +} + +auto atomic_directory_replace(const std::filesystem::path &original, + const std::filesystem::path &replacement) + -> void { + assert(std::filesystem::is_directory(replacement)); + assert(!std::filesystem::exists(original) || + std::filesystem::is_directory(original)); + assert(!original.parent_path().empty()); + + if (!std::filesystem::exists(original)) { + std::filesystem::rename(replacement, original); + return; + } + + // Atomic swap via renameat2 with RENAME_EXCHANGE +#if defined(__linux__) + if (renameat2(AT_FDCWD, replacement.c_str(), AT_FDCWD, original.c_str(), + RENAME_EXCHANGE) != 0) { + throw std::filesystem::filesystem_error{ + "failed to atomically swap directories", replacement, original, + std::error_code{errno, std::generic_category()}}; + } + + std::filesystem::remove_all(replacement); + // Atomic swap via renameatx_np with RENAME_SWAP +#elif defined(__APPLE__) + if (renameatx_np(AT_FDCWD, replacement.c_str(), AT_FDCWD, original.c_str(), + RENAME_SWAP) != 0) { + throw std::filesystem::filesystem_error{ + "failed to atomically swap directories", replacement, original, + std::error_code{errno, std::generic_category()}}; + } + + std::filesystem::remove_all(replacement); +#else + // Non-atomic fallback: two-rename approach with rollback. + + // Note we cannot safely use the temporary directory of the system as it + // might be in another volume + TemporaryDirectory backup{original.parent_path(), ".backup-"}; + std::filesystem::remove(backup.path()); + std::filesystem::rename(original, backup.path()); + try { + std::filesystem::rename(replacement, original); + } catch (...) { + std::filesystem::rename(backup.path(), original); + throw; + } +#endif +} + auto flush(const std::filesystem::path &path) -> void { #if defined(_WIN32) HANDLE hFile = diff --git a/vendor/core/src/lang/io/io_temporary.cc b/vendor/core/src/lang/io/io_temporary.cc new file mode 100644 index 00000000..66f67b01 --- /dev/null +++ b/vendor/core/src/lang/io/io_temporary.cc @@ -0,0 +1,66 @@ +#include + +#include // assert +#include // std::filesystem +#include // std::string +#include // std::error_code, std::generic_category, std::make_error_code + +#if defined(_WIN32) +#include // _mktemp_s +#else +#include // errno +#include // mkdtemp +#endif + +namespace sourcemeta::core { + +TemporaryDirectory::TemporaryDirectory(const std::filesystem::path &parent, + const std::string_view prefix) { + assert(!prefix.empty()); + assert(prefix.find('/') == std::string_view::npos); + assert(prefix.find('\\') == std::string_view::npos); + if (std::filesystem::exists(parent) && + !std::filesystem::is_directory(parent)) { + throw std::filesystem::filesystem_error{ + "parent path exists but is not a directory", parent, + std::make_error_code(std::errc::not_a_directory)}; + } + + std::filesystem::create_directories(parent); + auto name{(parent / std::string{prefix}).string() + "XXXXXX"}; + +#if defined(_WIN32) + const auto error{_mktemp_s(name.data(), name.size() + 1)}; + if (error != 0) { + throw std::filesystem::filesystem_error{ + "failed to create temporary directory", parent, + std::error_code{error, std::generic_category()}}; + } + + if (!std::filesystem::create_directory(name)) { + throw std::filesystem::filesystem_error{ + "failed to create temporary directory", parent, + std::make_error_code(std::errc::file_exists)}; + } +#else + if (mkdtemp(name.data()) == nullptr) { + throw std::filesystem::filesystem_error{ + "failed to create temporary directory", parent, + std::error_code{errno, std::generic_category()}}; + } +#endif + + this->path_ = std::filesystem::path{name}; +} + +TemporaryDirectory::~TemporaryDirectory() { + std::error_code error; + std::filesystem::remove_all(this->path_, error); +} + +auto TemporaryDirectory::path() const noexcept + -> const std::filesystem::path & { + return this->path_; +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/lang/process/include/sourcemeta/core/process_error.h b/vendor/core/src/lang/process/include/sourcemeta/core/process_error.h index 5de68d01..cb8630d8 100644 --- a/vendor/core/src/lang/process/include/sourcemeta/core/process_error.h +++ b/vendor/core/src/lang/process/include/sourcemeta/core/process_error.h @@ -24,10 +24,10 @@ namespace sourcemeta::core { /// @ingroup process /// An executable program could not be found -class SOURCEMETA_CORE_PROCESS_EXPORT ProcessProgramNotNotFoundError +class SOURCEMETA_CORE_PROCESS_EXPORT ProcessProgramNotFoundError : public std::exception { public: - ProcessProgramNotNotFoundError(const std::string_view program) + ProcessProgramNotFoundError(const std::string_view program) : program_{program} {} [[nodiscard]] auto what() const noexcept -> const char * override { diff --git a/vendor/core/src/lang/process/spawn.cc b/vendor/core/src/lang/process/spawn.cc index 5209f1af..d5a545b8 100644 --- a/vendor/core/src/lang/process/spawn.cc +++ b/vendor/core/src/lang/process/spawn.cc @@ -1,6 +1,7 @@ #include #include // assert +#include // ENOENT #include // std::filesystem #include // std::initializer_list #include // std::span @@ -70,7 +71,13 @@ auto spawn(const std::string &program, ); if (!success) { - throw ProcessProgramNotNotFoundError{program}; + const DWORD error_code{GetLastError()}; + if (error_code == ERROR_FILE_NOT_FOUND || + error_code == ERROR_PATH_NOT_FOUND) { + throw ProcessProgramNotFoundError{program}; + } + + throw ProcessSpawnError{program, arguments}; } WaitForSingleObject(process_info.hProcess, INFINITE); @@ -126,7 +133,11 @@ auto spawn(const std::string &program, #endif if (spawn_result != 0) { - throw ProcessProgramNotNotFoundError{program}; + if (spawn_result == ENOENT) { + throw ProcessProgramNotFoundError{program}; + } + + throw ProcessSpawnError{program, arguments}; } int status;