From 86df100a83cd74448f612916acc3e416ba7f7817 Mon Sep 17 00:00:00 2001 From: Shinichi Umegane Date: Wed, 25 Sep 2024 16:17:04 +0900 Subject: [PATCH] Refined exception handling and noexcept specifications for public APIs - Reviewed all public APIs to ensure the correct exception specifications. - Corrected APIs that were missing or incorrectly specifying noexcept. - Updated documentation to clearly state which exceptions are thrown by each API. - Fixed several areas where exceptions were not being handled properly, ensuring robust error handling. --- fcheck.cpp | 67 +++++++++++++++++ include/limestone/api/backup_detail.h | 20 ++--- include/limestone/api/configuration.h | 4 +- include/limestone/api/cursor.h | 3 +- include/limestone/api/datastore.h | 16 +++- include/limestone/api/file_set_entry.h | 6 +- include/limestone/api/limestone_exception.h | 12 +-- include/limestone/api/log_channel.h | 10 ++- include/limestone/api/snapshot.h | 1 + src/limestone/backup_detail.cpp | 4 +- src/limestone/configuration.cpp | 2 +- src/limestone/cursor.cpp | 7 +- src/limestone/datastore.cpp | 5 +- src/limestone/datastore_restore.cpp | 83 ++++++++++++++------- 14 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 fcheck.cpp diff --git a/fcheck.cpp b/fcheck.cpp new file mode 100644 index 00000000..4c54a6e6 --- /dev/null +++ b/fcheck.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace clang; +using namespace clang::tooling; + +class FunctionVisitor : public RecursiveASTVisitor { +public: + explicit FunctionVisitor(ASTContext *context) : context(context) {} + + bool VisitFunctionDecl(FunctionDecl *func) { + // 関数名を取得 + std::string funcName = func->getNameInfo().getName().getAsString(); + + // noexcept が指定されているかどうかを確認 + bool isNoexcept = (func->getExceptionSpecType() == EST_BasicNoexcept); + + // 結果を出力 + std::cout << "Function: " << funcName + << " - noexcept: " << (isNoexcept ? "true" : "false") + << std::endl; + + return true; + } + +private: + ASTContext *context; +}; + +class FunctionASTConsumer : public ASTConsumer { +public: + explicit FunctionASTConsumer(ASTContext *context) : visitor(context) {} + + virtual void HandleTranslationUnit(ASTContext &context) override { + visitor.TraverseDecl(context.getTranslationUnitDecl()); + } + +private: + FunctionVisitor visitor; +}; + +class FunctionFrontendAction : public ASTFrontendAction { +public: + virtual std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef file) override { + return std::make_unique(&CI.getASTContext()); + } +}; + +static llvm::cl::OptionCategory MyToolCategory("my-tool options"); + +int main(int argc, const char **argv) { + auto optionsParser = CommonOptionsParser::create(argc, argv, MyToolCategory); + if (!optionsParser) { + llvm::errs() << "Error creating CommonOptionsParser\n"; + return 1; + } + + ClangTool tool(optionsParser->getCompilations(), optionsParser->getSourcePathList()); + return tool.run(newFrontendActionFactory().get()); +} + diff --git a/include/limestone/api/backup_detail.h b/include/limestone/api/backup_detail.h index a75ea6a4..7f54606c 100644 --- a/include/limestone/api/backup_detail.h +++ b/include/limestone/api/backup_detail.h @@ -57,13 +57,13 @@ class backup_detail { * @param is_detached Indicates whether the target file can be moved. * If true, the command may or may not move this file. */ - entry(boost::filesystem::path source_path, boost::filesystem::path destination_path, bool is_mutable, bool is_detached) + entry(boost::filesystem::path source_path, boost::filesystem::path destination_path, bool is_mutable, bool is_detached) noexcept : source_path_(std::move(source_path)), destination_path_(std::move(destination_path)), is_mutable_(is_mutable), is_detached_(is_detached) {} - [[nodiscard]] boost::filesystem::path source_path() const { return source_path_; } - [[nodiscard]] boost::filesystem::path destination_path() const { return destination_path_; } - [[nodiscard]] bool is_mutable() const { return is_mutable_; } - [[nodiscard]] bool is_detached() const { return is_detached_; } + [[nodiscard]] boost::filesystem::path source_path() const noexcept { return source_path_; } + [[nodiscard]] boost::filesystem::path destination_path() const noexcept { return destination_path_; } + [[nodiscard]] bool is_mutable() const noexcept { return is_mutable_; } + [[nodiscard]] bool is_detached() const noexcept { return is_detached_; } private: boost::filesystem::path source_path_ {}; boost::filesystem::path destination_path_ {}; @@ -71,7 +71,7 @@ class backup_detail { bool is_detached_ {}; }; - std::string_view configuration_id() { + std::string_view configuration_id() noexcept { return configuration_id_; } @@ -79,24 +79,24 @@ class backup_detail { * @brief returns minimum epoch of log files * @note for LOG-0, always returns 0 */ - [[nodiscard]] epoch_id_type log_start() const { + [[nodiscard]] epoch_id_type log_start() const noexcept{ return 0; } /** * @brief returns maximum epoch of log files */ - [[nodiscard]] epoch_id_type log_finish() const; + [[nodiscard]] epoch_id_type log_finish() const noexcept; /** * @brief returns maximum epoch that is included in dataabase image * @note for LOG-0, always returns nullopt of std::optional */ - [[nodiscard]] std::optional image_finish() const { + [[nodiscard]] std::optional image_finish() const noexcept{ return std::nullopt; } - const std::vector& entries() { + const std::vector& entries() noexcept { return entries_; } diff --git a/include/limestone/api/configuration.h b/include/limestone/api/configuration.h index cf6e8bf3..b1d52e80 100644 --- a/include/limestone/api/configuration.h +++ b/include/limestone/api/configuration.h @@ -36,7 +36,7 @@ class configuration { /** * @brief create empty object */ - configuration(); + configuration() noexcept; /** * @brief create a object @@ -56,7 +56,7 @@ class configuration { * @brief setter for recover_max_parallelism * @param recover_max_parallelism the number of recover_max_parallelism */ - void set_recover_max_parallelism(int recover_max_parallelism) { + void set_recover_max_parallelism(int recover_max_parallelism) noexcept{ recover_max_parallelism_ = recover_max_parallelism; } diff --git a/include/limestone/api/cursor.h b/include/limestone/api/cursor.h index 1597ab2b..6cf430d6 100644 --- a/include/limestone/api/cursor.h +++ b/include/limestone/api/cursor.h @@ -47,6 +47,7 @@ class cursor { /** * @brief change the current cursor to point to the next entry * @attention this function is not thread-safe. + * @exception limestone_exception if an error occurs while reading the log entry * @return true if the next entry exists, false otherwise */ bool next(); @@ -80,7 +81,7 @@ class cursor { std::unique_ptr log_entry_; std::vector large_objects_{}; - explicit cursor(const boost::filesystem::path& file) noexcept; + explicit cursor(const boost::filesystem::path& file); friend class snapshot; }; diff --git a/include/limestone/api/datastore.h b/include/limestone/api/datastore.h index 94bd7ee3..7113e891 100644 --- a/include/limestone/api/datastore.h +++ b/include/limestone/api/datastore.h @@ -67,6 +67,7 @@ class datastore { /** * @brief create an object with the given configuration * @param conf a reference to a configuration object used in the object construction + * @exception limestone_exception if an I/O error occurs during construction */ explicit datastore(configuration const& conf); @@ -99,7 +100,7 @@ class datastore { status restore(std::string_view from, bool keep_backup) const noexcept; // restore (prusik era) - status restore(std::string_view from, std::vector& entries); + status restore(std::string_view from, std::vector& entries) noexcept; /** * @brief returns the status of the restore process that is currently in progress or that has finished immediately before @@ -111,6 +112,7 @@ class datastore { /** * @brief transition this object to an operational state * @details after this method is called, create_channel() can be invoked. + * @exception limestone_io_exception Thrown if an I/O error occurs. * @attention this function is not thread-safe, and the from directory must not contain any files other than log files. */ void ready(); @@ -136,7 +138,7 @@ class datastore { * @return the reference of the log_channel * @attention this function should be called before the ready() is called. */ - log_channel& create_channel(const boost::filesystem::path& location); + log_channel& create_channel(const boost::filesystem::path& location) noexcept; /** * @brief provide the largest epoch ID @@ -149,6 +151,7 @@ class datastore { /** * @brief change the current epoch ID * @param new epoch id which must be greater than current epoch ID. + * @exception limestone_io_exception Thrown if an I/O error occurs. * @attention this function should be called after the ready() is called. */ void switch_epoch(epoch_id_type epoch_id); @@ -189,6 +192,7 @@ class datastore { /** * @brief start backup operation * @detail a backup object is created, which contains a list of log files. + * @exception limestone_io_exception Thrown if an I/O error occurs. * @return a reference to the backup object. */ backup& begin_backup(); @@ -197,6 +201,7 @@ class datastore { /** * @brief start backup operation * @detail a backup_detail object is created, which contains a list of log entry. + * @exception limestone_io_exception Thrown if an I/O error occurs. * @return a reference to the backup_detail object. */ std::unique_ptr begin_backup(backup_type btype); @@ -216,7 +221,12 @@ class datastore { void recover(const epoch_tag&) const noexcept; /** - * Performs online compaction of the datastore. + * @brief Performs online log file compaction. + * + * This function compacts log files that have been rotated and are no longer subject to new entries. + * + * @exception limestone_exception Thrown if an I/O error occurs during the compaction process. + * @attention this function should be called before the ready() is called. */ void compact_with_online(); diff --git a/include/limestone/api/file_set_entry.h b/include/limestone/api/file_set_entry.h index 942f799c..d82810e6 100644 --- a/include/limestone/api/file_set_entry.h +++ b/include/limestone/api/file_set_entry.h @@ -37,9 +37,9 @@ class file_set_entry { file_set_entry(boost::filesystem::path source_path, boost::filesystem::path destination_path, bool is_detached) : source_path_(std::move(source_path)), destination_path_(std::move(destination_path)), is_detached_(is_detached) {} - [[nodiscard]] boost::filesystem::path source_path() const { return source_path_; } - [[nodiscard]] boost::filesystem::path destination_path() const { return destination_path_; } - [[nodiscard]] bool is_detached() const { return is_detached_; } + [[nodiscard]] boost::filesystem::path source_path() const noexcept { return source_path_; } + [[nodiscard]] boost::filesystem::path destination_path() const noexcept { return destination_path_; } + [[nodiscard]] bool is_detached() const noexcept { return is_detached_; } private: boost::filesystem::path source_path_ {}; diff --git a/include/limestone/api/limestone_exception.h b/include/limestone/api/limestone_exception.h index 8839c503..d3ff19e6 100644 --- a/include/limestone/api/limestone_exception.h +++ b/include/limestone/api/limestone_exception.h @@ -27,10 +27,10 @@ namespace limestone::api { class limestone_exception : public std::runtime_error { public: - explicit limestone_exception(const std::string& message) + explicit limestone_exception(const std::string& message) noexcept : std::runtime_error(message) {} - explicit limestone_exception(const std::string& message, int error_code) + explicit limestone_exception(const std::string& message, int error_code) noexcept : std::runtime_error(message), error_code_(error_code) {} [[nodiscard]] int error_code() const noexcept { return error_code_; } @@ -42,15 +42,15 @@ class limestone_exception : public std::runtime_error { class limestone_io_exception : public limestone_exception { public: // Constructor that takes an error message and errno as arguments (int) - explicit limestone_io_exception(const std::string& message, int error_code) + explicit limestone_io_exception(const std::string& message, int error_code) noexcept : limestone_exception(message, error_code) {} // Constructor that takes an error message and boost::system::error_code as arguments - explicit limestone_io_exception(const std::string& message, const boost::system::error_code& error_code) + explicit limestone_io_exception(const std::string& message, const boost::system::error_code& error_code) noexcept : limestone_exception(message, error_code.value()) {} // Helper function to format the error message for int error_code - static std::string format_message(const std::string& message, int error_code) { + static std::string format_message(const std::string& message, int error_code) noexcept{ // Retrieve the system error message corresponding to errno std::string errno_str = std::strerror(error_code); // Format the complete error message @@ -58,7 +58,7 @@ class limestone_io_exception : public limestone_exception { } // Helper function to format the error message for boost::system::error_code - static std::string format_message(const std::string& message, const boost::system::error_code& error_code) { + static std::string format_message(const std::string& message, const boost::system::error_code& error_code) noexcept { return format_message(message, error_code.value()); } }; diff --git a/include/limestone/api/log_channel.h b/include/limestone/api/log_channel.h index bfcf1128..55aafcbe 100644 --- a/include/limestone/api/log_channel.h +++ b/include/limestone/api/log_channel.h @@ -47,6 +47,7 @@ class log_channel { /** * @brief join a persistence session for the current epoch in this channel * @attention this function is not thread-safe. + * @exception limestone_exception if I/O error occurs * @note the current epoch is the last epoch specified by datastore::switch_epoch() */ void begin_session(); @@ -54,6 +55,7 @@ class log_channel { /** * @brief notifies the completion of an operation in this channel for the current persistent session the channel is participating in * @attention this function is not thread-safe. + * @exception limestone_exception if I/O error occurs * @note when all channels that have participated in the current persistent session call end_session() and the current epoch is * greater than the session's epoch, the persistent session itself is complete */ @@ -71,6 +73,7 @@ class log_channel { * @param key the key byte string for the entry to be added * @param value the value byte string for the entry to be added * @param write_version (optional) the write version of the entry to be added. If omitted, the default value is used + * @exception limestone_exception if I/O error occurs * @attention this function is not thread-safe. */ void add_entry(storage_id_type storage_id, std::string_view key, std::string_view value, write_version_type write_version); @@ -82,6 +85,7 @@ class log_channel { * @param value the value byte string for the entry to be added * @param write_version (optional) the write version of the entry to be added. If omitted, the default value is used * @param large_objects (optional) the list of large objects associated with the entry to be added + * @exception limestone_exception if I/O error occurs * @attention this function is not thread-safe. */ void add_entry(storage_id_type storage_id, std::string_view key, std::string_view value, write_version_type write_version, const std::vector& large_objects); @@ -91,6 +95,7 @@ class log_channel { * @param storage_id the storage ID of the entry to be deleted * @param key the key byte string for the entry to be deleted * @param write_version the write version of the entry to be removed + * @exception limestone_exception if I/O error occurs * @attention this function is not thread-safe. * @note no deletion operation is performed on the entry that has been added to the current persistent session, instead, * the entries to be deleted are treated as if they do not exist in a recover() operation from a log stored in the current persistent session @@ -101,6 +106,7 @@ class log_channel { * @brief add an entry indicating the addition of the specified storage * @param storage_id the storage ID of the entry to be added * @param write_version the write version of the entry to be added + * @exception limestone_exception if I/O error occurs * @attention this function is not thread-safe. * @impl this operation may be ignored. */ @@ -110,6 +116,7 @@ class log_channel { * @brief add an entry indicating the deletion of the specified storage and all entries for that storage * @param storage_id the storage ID of the entry to be removed * @param write_version the write version of the entry to be removed + * @exception limestone_exception if I/O error occurs * @attention this function is not thread-safe. * @note no deletion operation is performed on the entry that has been added to the current persistent session, instead, * the target entries are treated as if they do not exist in the recover() operation from the log stored in the current persistent session. @@ -120,6 +127,7 @@ class log_channel { * @brief add an entry indicating the deletion of all entries contained in the specified storage * @param storage_id the storage ID of the entry to be removed * @param write_version the write version of the entry to be removed + * @exception limestone_exception if I/O error occurs * @attention this function is not thread-safe. * @note no deletion operation is performed on the entry that has been added to the current persistent session, instead, * the target entries are treated as if they do not exist in the recover() operation from the log stored in the current persistent session. @@ -131,13 +139,13 @@ class log_channel { */ [[nodiscard]] boost::filesystem::path file_path() const noexcept; +private: /** * @brief Waits until the specified epoch's session is completed and the epoch ID is removed from waiting_epoch_ids_. * @param epoch The epoch ID associated with the session to wait for. */ void wait_for_end_session(epoch_id_type epoch); -private: datastore& envelope_; boost::filesystem::path location_; diff --git a/include/limestone/api/snapshot.h b/include/limestone/api/snapshot.h index f5946676..03088cd7 100644 --- a/include/limestone/api/snapshot.h +++ b/include/limestone/api/snapshot.h @@ -45,6 +45,7 @@ class snapshot { * @brief create a cursor to read the entire contents of the snapshot and returns it * @details the returned cursor points to the first element by calling cursor::next(). * @attention this function is thread-safe. + * @exception limestone_exception if the file stream of the cursor is not good. * @return unique pointer of the cursor */ [[nodiscard]] std::unique_ptr get_cursor() const; diff --git a/src/limestone/backup_detail.cpp b/src/limestone/backup_detail.cpp index 884fa057..27d02bbc 100644 --- a/src/limestone/backup_detail.cpp +++ b/src/limestone/backup_detail.cpp @@ -20,8 +20,8 @@ namespace limestone::api { // for LOG-0 -epoch_id_type backup_detail::log_finish() const { - return log_finish_; +epoch_id_type backup_detail::log_finish() const noexcept { + return log_finish_; } // restriction of current implementation: diff --git a/src/limestone/configuration.cpp b/src/limestone/configuration.cpp index ae7e8e0d..dcc39a36 100644 --- a/src/limestone/configuration.cpp +++ b/src/limestone/configuration.cpp @@ -17,7 +17,7 @@ namespace limestone::api { -configuration::configuration() = default; +configuration::configuration() noexcept = default; configuration::configuration(const std::vector& data_locations, boost::filesystem::path metadata_location) noexcept : metadata_location_(std::move(metadata_location)) { diff --git a/src/limestone/cursor.cpp b/src/limestone/cursor.cpp index 6313e64b..96d23160 100644 --- a/src/limestone/cursor.cpp +++ b/src/limestone/cursor.cpp @@ -18,16 +18,15 @@ #include #include #include "logging_helper.h" - +#include "limestone_exception_helper.h" #include "log_entry.h" namespace limestone::api { -cursor::cursor(const boost::filesystem::path& file) noexcept : log_entry_(std::make_unique()) { +cursor::cursor(const boost::filesystem::path& file) : log_entry_(std::make_unique()) { istrm_.open(file, std::ios_base::in | std::ios_base::binary ); if (!istrm_.good()) { - LOG_LP(ERROR) << "file stream of the cursor is not good (" << file << ")"; - std::abort(); + LOG_AND_THROW_EXCEPTION("file stream of the cursor is not good (" + file.string() + ")"); } } cursor::~cursor() noexcept { diff --git a/src/limestone/datastore.cpp b/src/limestone/datastore.cpp index 14d17819..a17ed450 100644 --- a/src/limestone/datastore.cpp +++ b/src/limestone/datastore.cpp @@ -131,7 +131,6 @@ void datastore::ready() { state_ = state::ready; } - std::unique_ptr datastore::get_snapshot() const { check_after_ready(static_cast(__func__)); return std::unique_ptr(new snapshot(location_)); @@ -142,7 +141,7 @@ std::shared_ptr datastore::shared_snapshot() const { return std::shared_ptr(new snapshot(location_)); } -log_channel& datastore::create_channel(const boost::filesystem::path& location) { +log_channel& datastore::create_channel(const boost::filesystem::path& location) noexcept{ check_before_ready(static_cast(__func__)); std::lock_guard lock(mtx_channel_); @@ -477,6 +476,8 @@ void datastore::stop_online_compaction_worker() { } void datastore::compact_with_online() { + check_after_ready(static_cast(__func__)); + // rotate first rotation_result result = rotate_log_files(); diff --git a/src/limestone/datastore_restore.cpp b/src/limestone/datastore_restore.cpp index 0c2c3b9c..f44a8a41 100644 --- a/src/limestone/datastore_restore.cpp +++ b/src/limestone/datastore_restore.cpp @@ -28,15 +28,20 @@ static constexpr const char *version_error_prefix = "/:limestone unsupported bac "see https://github.com/project-tsurugi/tsurugidb/blob/master/docs/upgrade-guide.md"; status purge_dir(const boost::filesystem::path& dir) { - for (const boost::filesystem::path& p : boost::filesystem::directory_iterator(dir)) { - if (!boost::filesystem::is_directory(p)) { - try { - boost::filesystem::remove(p); - } catch (boost::filesystem::filesystem_error& ex) { - LOG_LP(ERROR) << ex.what() << " file = " << p.string(); - return status::err_permission_error; + try { + for (const boost::filesystem::path& p : boost::filesystem::directory_iterator(dir)) { + if (!boost::filesystem::is_directory(p)) { + try { + boost::filesystem::remove(p); + } catch (const boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << ex.what() << " file = " << p.string(); + return status::err_permission_error; + } } } + } catch (const boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << "Failed to iterate directory: " << ex.what() << " dir = " << dir.string(); + return status::err_permission_error; } return status::ok; } @@ -66,39 +71,52 @@ status datastore::restore(std::string_view from, bool keep_backup) const noexcep // log_dir version check boost::filesystem::path manifest_path = from_dir / std::string(internal::manifest_file_name); - if (!boost::filesystem::exists(manifest_path)) { - VLOG_LP(log_info) << "no manifest file in backup"; - LOG(ERROR) << internal::version_error_prefix << " (version mismatch: version 0, server supports version 1)"; - return status::err_broken_data; + try { + if (!boost::filesystem::exists(manifest_path)) { + VLOG_LP(log_info) << "no manifest file in backup"; + LOG(ERROR) << internal::version_error_prefix << " (version mismatch: version 0, server supports version 1)"; + return status::err_broken_data; + } + } catch (const boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << "Filesystem error: " << ex.what() << " file = " << manifest_path.string(); + return status::err_permission_error; } if (auto rc = internal::check_manifest(manifest_path); rc != status::ok) { return rc; } if (auto rc = internal::purge_dir(location_); rc != status::ok) { return rc; } - for (const boost::filesystem::path& p : boost::filesystem::directory_iterator(from_dir)) { - try { - boost::filesystem::copy_file(p, location_ / p.filename()); - } - catch (boost::filesystem::filesystem_error& ex) { - LOG_LP(ERROR) << ex.what() << " file = " << p.string(); - return status::err_permission_error; - } - } - if (!keep_backup) { + try { for (const boost::filesystem::path& p : boost::filesystem::directory_iterator(from_dir)) { try { - boost::filesystem::remove(p); + boost::filesystem::copy_file(p, location_ / p.filename()); + } catch (boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << ex.what() << " file = " << p.string(); + return status::err_permission_error; } - catch (boost::filesystem::filesystem_error& ex) { - LOG_LP(WARNING) << ex.what() << " file = " << p.string(); + } + } catch (const boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << "Failed to iterate directory: " << ex.what(); + return status::err_permission_error; + } + try { + if (!keep_backup) { + for (const boost::filesystem::path& p : boost::filesystem::directory_iterator(from_dir)) { + try { + boost::filesystem::remove(p); + } catch (boost::filesystem::filesystem_error& ex) { + LOG_LP(WARNING) << ex.what() << " file = " << p.string(); + } } } + } catch (const boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << "Failed to iterate directory: " << ex.what(); + return status::err_permission_error; } return status::ok; } // prusik era -status datastore::restore(std::string_view from, std::vector& entries) { +status datastore::restore(std::string_view from, std::vector& entries) noexcept{ VLOG_LP(log_debug) << "restore (from prusik) begin, from directory = " << from; auto from_dir = boost::filesystem::path(std::string(from)); @@ -114,11 +132,18 @@ status datastore::restore(std::string_view from, std::vector& en } else { src = from_dir / src; } - if (!boost::filesystem::exists(src) || !boost::filesystem::is_regular_file(src)) { - LOG_LP(ERROR) << "file not found : file = " << src.string(); - return status::err_not_found; + try { + if (!boost::filesystem::exists(src) || !boost::filesystem::is_regular_file(src)) { + LOG_LP(ERROR) << "File not found or not a regular file: " << src.string(); + return status::err_not_found; + } + } catch (const boost::filesystem::filesystem_error& ex) { + LOG_LP(ERROR) << "Filesystem error: " << ex.what() << " file = " << src.string(); + return status::err_permission_error; + } + if (auto rc = internal::check_manifest(src); rc != status::ok) { + return rc; } - if (auto rc = internal::check_manifest(src); rc != status::ok) { return rc; } manifest_count++; } if (manifest_count < 1) { // XXX: change to != 1 ??