diff --git a/src/applib/ata_storage_property.h b/src/applib/ata_storage_property.h index 0bc4922..fd88bed 100644 --- a/src/applib/ata_storage_property.h +++ b/src/applib/ata_storage_property.h @@ -248,6 +248,29 @@ class AtaStorageProperty { [[nodiscard]] static std::string get_readable_section_name(Section s); + using ValueVariantType = std::variant< + std::monostate, ///< None + std::string, ///< Value (if it's a string) + int64_t, ///< Value (if it's an integer) + bool, ///< Value (if it's bool) + std::chrono::seconds, ///< Value in seconds (if it's time interval) + AtaStorageCapability, ///< Value (if it's a capability) + AtaStorageAttribute, ///< Value (if it's an attribute) + AtaStorageStatistic, ///< Value (if it's a statistic from devstat) + AtaStorageErrorBlock, ///< Value (if it's a error block) + AtaStorageSelftestEntry ///< Value (if it's a self-test entry) + >; + + + /// Constructor + AtaStorageProperty() = default; + + /// Constructor + AtaStorageProperty(Section section_, ValueVariantType value_) + : section(section_), value(std::move(value_)) + { } + + /// Get displayable value type name [[nodiscard]] std::string get_storable_value_type_name() const; @@ -297,18 +320,7 @@ class AtaStorageProperty { std::string reported_value; ///< String representation of the value as reported std::string readable_value; ///< User-friendly readable representation of value. if empty, use the other members. - std::variant< - std::monostate, ///< None - std::string, ///< Value (if it's a string) - int64_t, ///< Value (if it's an integer) - bool, ///< Value (if it's bool) - std::chrono::seconds, ///< Value in seconds (if it's time interval) - AtaStorageCapability, ///< Value (if it's a capability) - AtaStorageAttribute, ///< Value (if it's an attribute) - AtaStorageStatistic, ///< Value (if it's a statistic from devstat) - AtaStorageErrorBlock, ///< Value (if it's a error block) - AtaStorageSelftestEntry ///< Value (if it's a self-test entry) - > value; + ValueVariantType value; ///< Stored value WarningLevel warning_level = WarningLevel::None; ///< Warning severity for this property std::string warning_reason; // Warning reason (displayable) diff --git a/src/applib/ata_storage_property_descr.cpp b/src/applib/ata_storage_property_descr.cpp index 44af796..d61cef6 100644 --- a/src/applib/ata_storage_property_descr.cpp +++ b/src/applib/ata_storage_property_descr.cpp @@ -1613,7 +1613,7 @@ bool ata_storage_property_autoset_description(AtaStorageProperty& p, AtaStorageA || auto_set(p, "ata_smart_data/self_test/status/_group", "Status of the last self-test run.") || auto_set(p, "ata_smart_data/offline_data_collection/_group", "Drive properties related to Offline Data Collection and self-tests.") || auto_set(p, "ata_smart_data/capabilities/_group", "Drive properties related to SMART handling.") - || auto_set(p, "ata_smart_data/capabilities/error_logging_supported", "Drive properties related to error logging.") + || auto_set(p, "ata_smart_data/capabilities/error_logging_supported/_group", "Drive properties related to error logging.") || auto_set(p, "ata_sct_capabilities/_group", "Drive properties related to temperature information."); break; diff --git a/src/applib/selftest.cpp b/src/applib/selftest.cpp index 1fa4b9f..f59e0d4 100644 --- a/src/applib/selftest.cpp +++ b/src/applib/selftest.cpp @@ -274,7 +274,7 @@ hz::ExpectedVoid SelfTest::update(const std::shared_ptr() || e.get_value().test_num != 0 - || e.generic_name != "ata_smart_data/self_test/status/passed") + || e.generic_name != "ata_smart_data/self_test/status/_merged") continue; p = e; } diff --git a/src/applib/smartctl_json_ata_parser.cpp b/src/applib/smartctl_json_ata_parser.cpp index 155183c..7898a17 100644 --- a/src/applib/smartctl_json_ata_parser.cpp +++ b/src/applib/smartctl_json_ata_parser.cpp @@ -9,15 +9,23 @@ Copyright: /// \weakgroup applib /// @{ -#include - #include "smartctl_json_ata_parser.h" + +#include +#include +#include +#include +#include + #include "json/json.hpp" + +#include "ata_storage_property.h" #include "hz/debug.h" #include "hz/string_algo.h" // #include "smartctl_version_parser.h" #include "hz/format_unit.h" #include "hz/error_container.h" +#include "hz/string_num.h" #include "smartctl_json_parser_helpers.h" @@ -103,8 +111,24 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse(std::string_v add_property(merged_property); add_property(full_property); + // Info must be supported. auto info_parse_status = parse_section_info(json_root_node); - auto health_parse_status = parse_section_health(json_root_node); + if (!info_parse_status) { + return info_parse_status; + } + + // Ignore parse errors here, they are not critical. + [[maybe_unused]] auto health_parse_status = parse_section_health(json_root_node); + [[maybe_unused]] auto capabilities_parse_status = parse_section_capabilities(json_root_node); + [[maybe_unused]] auto attributes_parse_status = parse_section_attributes(json_root_node); + [[maybe_unused]] auto directory_log_parse_status = parse_section_directory_log(json_root_node); + [[maybe_unused]] auto error_log_parse_status = parse_section_error_log(json_root_node); + [[maybe_unused]] auto selftest_log_parse_status = parse_section_selftest_log(json_root_node); + [[maybe_unused]] auto selective_selftest_log_parse_status = parse_section_selective_selftest_log(json_root_node); + [[maybe_unused]] auto scttemp_log_parse_status = parse_section_scttemp_log(json_root_node); + [[maybe_unused]] auto scterc_log_parse_status = parse_section_scterc_log(json_root_node); + [[maybe_unused]] auto devstat_parse_status = parse_section_devstat(json_root_node); + [[maybe_unused]] auto sataphy_parse_status = parse_section_sataphy(json_root_node); return {}; } @@ -115,7 +139,10 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_info( { using namespace SmartctlJsonParserHelpers; - static const std::vector> info_keys = { + // This is very similar to Basic Parser, but the Basic Parser supports different drive types, while this + // one is only for ATA. + + static const std::vector> json_keys = { {"model_family", _("Model Family"), string_formatter()}, {"model_name", _("Device Model"), string_formatter()}, {"serial_number", _("Serial Number"), string_formatter()}, @@ -216,16 +243,21 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_info( {"ata_security/string", _("ATA Security"), string_formatter()}, }; - for (const auto& [key, displayable_name, retrieval_func] : info_keys) { + bool any_found = false; + for (const auto& [key, displayable_name, retrieval_func] : json_keys) { DBG_ASSERT(retrieval_func != nullptr); auto p = retrieval_func(json_root_node, key, displayable_name); if (p.has_value()) { // ignore if not found p->section = AtaStorageProperty::Section::Info; add_property(p.value()); + any_found = true; } } + if (!any_found) { + return hz::Unexpected(SmartctlParserError::KeyNotFound, "No keys info found in JSON data."); + } return {}; } @@ -254,4 +286,300 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_healt +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_capabilities(const nlohmann::json& json_root_node) +{ + using namespace SmartctlJsonParserHelpers; + + static const std::vector> json_keys = { +// // FIXME Remove? +// {"ata_smart_data/capabilities/_group", _("SMART Capabilities"), +// conditional_formatter("ata_smart_data/capabilities/values", +// AtaStorageProperty{} )}, +// +// // FIXME Remove? +// {"ata_smart_data/capabilities/error_logging_supported/_group", _("Error Logging Capabilities"), +// conditional_formatter("ata_smart_data/capabilities/error_logging_supported", +// AtaStorageProperty{} )}, +// + + {"ata_smart_data/offline_data_collection/status/_auto_enabled", _("Automatic offline data collection status"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_data(root_node, "ata_smart_data/offline_data_collection/status/value"); + if (value_val.has_value()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = static_cast(value_val.value() & 0x80); // taken from ataprint.cpp + p.readable_value = p.get_value() ? _("Enabled") : _("Disabled"); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + + // Last self-test status + {"ata_smart_data/offline_data_collection/status/value/_decoded", "Last offline data collection status", + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_data(root_node, "ata_smart_data/offline_data_collection/status/value"); + if (value_val.has_value()) { + std::string status_str; + switch (value_val.value() & 0x7f) { + // Data from smartmontools/ataprint.cpp + case 0x00: status_str = _("Never started"); break; + case 0x02: status_str = _("Completed without error"); break; + case 0x03: status_str = (value_val.value() == 0x03 ? _("In progress") : _("In reserved state")); break; + case 0x04: status_str = _("Suspended by an interrupting command from host"); break; + case 0x05: status_str = _("Aborted by an interrupting command from host"); break; + case 0x06: status_str = _("Aborted by the device with a fatal error"); break; + default: status_str = ((value_val.value() & 0x7f) > 0x40 ? _("In vendor-specific state") : _("In reserved state")); break; + } + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::Capabilities; + p.set_name(key, key, displayable_name); + p.value = status_str; + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"ata_smart_data/offline_data_collection/completion_seconds", _("Time to complete offline data collection"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_data(root_node, key); + if (value_val.has_value()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = std::chrono::seconds(value_val.value()); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"ata_smart_data/self_test/status/value/_decoded", _("Self-test execution status"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + // Testing: + // "status": { + // "value": 249, + // "string": "in progress, 90% remaining", + // "remaining_percent": 90 + // }, + + // Not testing: + // "status": { + // "value": 0, + // "string": "completed without error", + // "passed": true + // }, + + AtaStorageSelftestEntry::Status status = AtaStorageSelftestEntry::Status::Unknown; + + auto value_val = get_node_data(root_node, "ata_smart_data/self_test/status/value"); + if (value_val.has_value()) { + switch (value_val.value() >> 4) { + // Data from smartmontools/ataprint.cpp + case 0x0: status = AtaStorageSelftestEntry::Status::CompletedNoError; break; + case 0x1: status = AtaStorageSelftestEntry::Status::AbortedByHost; break; + case 0x2: status = AtaStorageSelftestEntry::Status::Interrupted; break; + case 0x3: status = AtaStorageSelftestEntry::Status::FatalOrUnknown; break; + case 0x4: status = AtaStorageSelftestEntry::Status::ComplUnknownFailure; break; + case 0x5: status = AtaStorageSelftestEntry::Status::ComplElectricalFailure; break; + case 0x6: status = AtaStorageSelftestEntry::Status::ComplServoFailure; break; + case 0x7: status = AtaStorageSelftestEntry::Status::ComplReadFailure; break; + case 0x8: status = AtaStorageSelftestEntry::Status::ComplHandlingDamage; break; + // Special case + case 0xf: status = AtaStorageSelftestEntry::Status::InProgress; break; + default: status = AtaStorageSelftestEntry::Status::Reserved; break; + } + + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = AtaStorageSelftestEntry::get_readable_status_name(status); + return p; + } + + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"ata_smart_data/self_test/status/remaining_percent", _("Self-test remaining percentage"), + custom_string_formatter([](int64_t value) + { + return std::format("{} %", value); + }) + }, + + + {"ata_smart_data/capabilities/self_tests_supported", _("Self-tests supported"), bool_formatter(_("Yes"), _("No"))}, + + {"ata_smart_data/capabilities/exec_offline_immediate_supported", _("Offline immediate test supported"), bool_formatter(_("Yes"), _("No"))}, + {"ata_smart_data/capabilities/offline_is_aborted_upon_new_cmd", _("Abort offline collection on new command"), bool_formatter(_("Yes"), _("No"))}, + {"ata_smart_data/capabilities/offline_surface_scan_supported", _("Offline surface scan supported"), bool_formatter(_("Yes"), _("No"))}, + + {"ata_smart_data/capabilities/conveyance_self_test_supported", _("Conveyance self-test supported"), bool_formatter(_("Yes"), _("No"))}, + {"ata_smart_data/capabilities/selective_self_test_supported", _("Selective self-test supported"), bool_formatter(_("Yes"), _("No"))}, + + {"ata_smart_data/self_test/polling_minutes/short", _("Short self-test status recommended polling time"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_data(root_node, key); + if (value_val.has_value()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = std::chrono::minutes(value_val.value()); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"ata_smart_data/self_test/polling_minutes/extended", _("Extended self-test status recommended polling time"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_data(root_node, key); + if (value_val.has_value()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = std::chrono::minutes(value_val.value()); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"ata_smart_data/self_test/polling_minutes/conveyance", _("Conveyance self-test status recommended polling time"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_data(root_node, key); + if (value_val.has_value()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = std::chrono::minutes(value_val.value()); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + + {"ata_smart_data/capabilities/attribute_autosave_enabled", _("Saves SMART data before entering power-saving mode"), bool_formatter(_("Enabled"), _("Disabled"))}, + + {"ata_smart_data/capabilities/error_logging_supported", _("Error logging supported"), bool_formatter(_("Yes"), _("No"))}, + {"ata_smart_data/capabilities/gp_logging_supported", _("General purpose logging supported"), bool_formatter(_("Yes"), _("No"))}, + + {"ata_sct_capabilities/_supported", _("SCT capabilities supported"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto value_val = get_node_exists(root_node, "ata_sct_capabilities"); + if (value_val.has_value()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.value = value_val.value(); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + {"ata_sct_capabilities/error_recovery_control_supported", _("SCT error recovery control supported"), bool_formatter(_("Yes"), _("No"))}, + {"ata_sct_capabilities/feature_control_supported", _("SCT feature control supported"), bool_formatter(_("Yes"), _("No"))}, + {"ata_sct_capabilities/data_table_supported", _("SCT data table supported"), bool_formatter(_("Yes"), _("No"))}, + + }; + + for (const auto& [key, displayable_name, retrieval_func] : json_keys) { + DBG_ASSERT(retrieval_func != nullptr); + auto p = retrieval_func(json_root_node, key, displayable_name); + if (p.has_value()) { // ignore if not found + p->section = AtaStorageProperty::Section::Capabilities; + add_property(p.value()); + } + } + + return {}; +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_attributes(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_directory_log(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_error_log(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selftest_log(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selective_selftest_log(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_scttemp_log(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_scterc_log(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_devstat(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_sataphy(const nlohmann::json& json_root_node) +{ + return hz::ExpectedVoid(); +} + + + +hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_internal_capabilities(AtaStorageProperty& cap_prop) +{ + return hz::ExpectedVoid(); +} + + + /// @} diff --git a/src/applib/smartctl_json_ata_parser.h b/src/applib/smartctl_json_ata_parser.h index 3ba8832..0da2db3 100644 --- a/src/applib/smartctl_json_ata_parser.h +++ b/src/applib/smartctl_json_ata_parser.h @@ -12,10 +12,14 @@ Copyright: #ifndef SMARTCTL_JSON_ATA_PARSER_H #define SMARTCTL_JSON_ATA_PARSER_H +#include "smartctl_parser.h" + #include +#include #include "json/json.hpp" -#include "smartctl_parser.h" + +#include "hz/error_container.h" @@ -36,6 +40,40 @@ class SmartctlJsonAtaParser : public SmartctlParser { /// Parse the health section (root node), filling in the properties hz::ExpectedVoid parse_section_health(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_capabilities(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_attributes(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_directory_log(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_error_log(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_selftest_log(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_selective_selftest_log(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_scttemp_log(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_scterc_log(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_devstat(const nlohmann::json& json_root_node); + + /// Parse a section from json data + hz::ExpectedVoid parse_section_sataphy(const nlohmann::json& json_root_node); + + + /// Check the capabilities for internal properties we can use. + hz::ExpectedVoid parse_section_internal_capabilities(AtaStorageProperty& cap_prop); }; diff --git a/src/applib/smartctl_json_basic_parser.cpp b/src/applib/smartctl_json_basic_parser.cpp index dadec34..6c0f22d 100644 --- a/src/applib/smartctl_json_basic_parser.cpp +++ b/src/applib/smartctl_json_basic_parser.cpp @@ -9,22 +9,30 @@ Copyright: /// \weakgroup applib /// @{ +#include "smartctl_json_basic_parser.h" + // #include "local_glibmm.h" //#include // localeconv -//#include -//#include +#include +#include +#include +#include +#include // #include "hz/locale_tools.h" // ScopedCLocale, locale_c_get(). -//#include "hz/string_algo.h" // string_* -//#include "hz/string_num.h" // string_is_numeric, number_to_string +#include "hz/string_algo.h" // string_* +#include "hz/string_num.h" // string_is_numeric, number_to_string //#include "hz/debug.h" // debug_* +#include "json/json.hpp" +#include "hz/error_container.h" //#include "app_pcrecpp.h" //#include "smartctl_text_ata_parser.h" //#include "ata_storage_property_descr.h" +#include "ata_storage_property.h" #include "smartctl_json_parser_helpers.h" +#include "smartctl_parser_types.h" //#include "smartctl_version_parser.h" -#include "smartctl_json_basic_parser.h" diff --git a/src/applib/smartctl_json_basic_parser.h b/src/applib/smartctl_json_basic_parser.h index 0d38c99..e843a3c 100644 --- a/src/applib/smartctl_json_basic_parser.h +++ b/src/applib/smartctl_json_basic_parser.h @@ -15,6 +15,8 @@ Copyright: //#include //#include +#include "json/json.hpp" + #include "smartctl_parser.h" diff --git a/src/applib/smartctl_json_parser_helpers.h b/src/applib/smartctl_json_parser_helpers.h index 31bfa5d..3b604ca 100644 --- a/src/applib/smartctl_json_parser_helpers.h +++ b/src/applib/smartctl_json_parser_helpers.h @@ -12,12 +12,17 @@ Copyright: #ifndef SMARTCTL_JSON_PARSER_HELPERS_H #define SMARTCTL_JSON_PARSER_HELPERS_H +#include +#include +#include #include +#include #include #include "json/json.hpp" #include "hz/debug.h" #include "hz/string_algo.h" +#include "smartctl_parser_types.h" #include "smartctl_version_parser.h" #include "hz/format_unit.h" #include "hz/error_container.h" @@ -38,10 +43,9 @@ enum class SmartctlJsonParserError { namespace SmartctlJsonParserHelpers { -/// Get json node data. The path is slash-separated string. -/// \throws std::runtime_error If not found or one of the paths is not an object -template -[[nodiscard]] hz::ExpectedValue get_node_data(const nlohmann::json& root, const std::string& path) +/// Get node from json data. The path is slash-separated string. +[[nodiscard]] inline hz::ExpectedValue +get_node(const nlohmann::json& root, std::string_view path) { using namespace std::literals; @@ -63,13 +67,7 @@ template if (auto iter = curr->find(comp_name); iter != curr->end()) { // path component exists const auto& jval = iter.value(); if (comp_index + 1 == components.size()) { // it's the "value" component - try { - return jval.get(); // may throw json::type_error - } - catch (nlohmann::json::type_error& ex) { - return hz::Unexpected(SmartctlJsonParserError::TypeError, - std::format("Cannot get node data \"{}\", component \"{}\" has wrong type: {}.", path, comp_name, ex.what())); - } + return jval; } // continue to the next component curr = &jval; @@ -85,10 +83,32 @@ template + +/// Get json node data. The path is slash-separated string. +/// \return SmartctlJsonParserError on error. +template +[[nodiscard]] hz::ExpectedValue get_node_data(const nlohmann::json& root, std::string_view path) +{ + auto node_result = get_node(root, path); + if (!node_result) { + return hz::UnexpectedFrom(node_result); + } + + try { + return node_result.value().get(); // may throw json::type_error + } + catch (nlohmann::json::type_error& ex) { + return hz::Unexpected(SmartctlJsonParserError::TypeError, + std::format("Cannot get node data \"{}\", component has wrong type: {}.", path, ex.what())); + } +} + + + /// Get json node data. The path is slash-separated string. /// If the data is not is found, the default value is returned. template -[[nodiscard]] hz::ExpectedValue get_node_data(const nlohmann::json& root, const std::string& path, const T& default_value) +[[nodiscard]] hz::ExpectedValue get_node_data(const nlohmann::json& root, std::string_view path, const T& default_value) { auto expected_data = get_node_data(root, path); @@ -110,6 +130,31 @@ template +/// Check if json node exists. The path is slash-separated string. +[[nodiscard]] inline hz::ExpectedValue +get_node_exists(const nlohmann::json& root, std::string_view path) +{ + auto node_result = get_node(root, path); + if (node_result.has_value()) { + return true; + } + + switch (node_result.error().data()) { + case SmartctlJsonParserError::PathNotFound: + return false; + + case SmartctlJsonParserError::UnexpectedObjectInPath: + case SmartctlJsonParserError::EmptyPath: + case SmartctlJsonParserError::InternalError: + case SmartctlJsonParserError::TypeError: + break; + } + + return hz::UnexpectedFrom(node_result); +} + + + /// A signature for a property retrieval function. using PropertyRetrievalFunc = std::function< auto(const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) @@ -137,6 +182,30 @@ inline auto string_formatter() +/// Return a lambda which returns a return_property if conditional_path exists. +/// If the path doesn't exist, an error is returned. +inline auto conditional_formatter(const std::string_view conditional_path, AtaStorageProperty return_property) +{ + return [conditional_path, return_property](const nlohmann::json& root_node, const std::string& key, [[maybe_unused]] const std::string& displayable_name) mutable + -> hz::ExpectedValue + { + auto node_exists_result = get_node_exists(root_node, conditional_path); + if (!node_exists_result.has_value()) { + return hz::Unexpected(SmartctlParserError::DataError, node_exists_result.error().message()); + } + + if (node_exists_result.value()) { + return_property.generic_name = key; + return_property.displayable_name = displayable_name; + return return_property; + } + + return hz::Unexpected(SmartctlParserError::InternalError, std::format("Error getting key {} from JSON data.", key)); + }; +} + + + /// Return a lambda which retrieves a key value as a bool (formatted according to parameters), and sets it as a property. inline auto bool_formatter(const std::string_view& true_str, const std::string_view& false_str) { diff --git a/src/applib/smartctl_parser.h b/src/applib/smartctl_parser.h index 3d8520e..d469bd8 100644 --- a/src/applib/smartctl_parser.h +++ b/src/applib/smartctl_parser.h @@ -13,7 +13,6 @@ Copyright: #define SMARTCTL_PARSER_H #include -#include #include #include "ata_storage_property.h" diff --git a/src/applib/smartctl_text_ata_parser.cpp b/src/applib/smartctl_text_ata_parser.cpp index 65b631b..5b5ef7d 100644 --- a/src/applib/smartctl_text_ata_parser.cpp +++ b/src/applib/smartctl_text_ata_parser.cpp @@ -9,20 +9,26 @@ Copyright: /// \weakgroup applib /// @{ +#include "smartctl_text_ata_parser.h" + // #include "local_glibmm.h" #include // localeconv #include +#include +#include #include +#include // #include "hz/locale_tools.h" // ScopedCLocale, locale_c_get(). +#include "ata_storage_property.h" #include "hz/string_algo.h" // string_* #include "hz/string_num.h" // string_is_numeric, number_to_string #include "hz/debug.h" // debug_* #include "app_pcrecpp.h" -#include "smartctl_text_ata_parser.h" -#include "ata_storage_property_descr.h" +//#include "ata_storage_property_descr.h" // #include "warning_colors.h" +#include "smartctl_parser_types.h" #include "smartctl_version_parser.h" #include "smartctl_text_parser_helper.h" @@ -1042,7 +1048,7 @@ hz::ExpectedVoid SmartctlTextAtaParser::parse_section_data_ cap_prop.generic_name = "ata_smart_data/capabilities/_group"; } else if (re_error_log_cap_group.PartialMatch(cap_prop.reported_name)) { - cap_prop.generic_name = "ata_smart_data/capabilities/error_logging_supported"; + cap_prop.generic_name = "ata_smart_data/capabilities/error_logging_supported/_group"; } else if (re_sct_cap_group.PartialMatch(cap_prop.reported_name)) { cap_prop.generic_name = "ata_sct_capabilities/_group"; @@ -1059,7 +1065,7 @@ hz::ExpectedVoid SmartctlTextAtaParser::parse_section_data_ AtaStorageProperty p; p.section = AtaStorageProperty::Section::Internal; - p.set_name("ata_smart_data/self_test/status/passed"); + p.set_name("ata_smart_data/self_test/status/_merged"); AtaStorageSelftestEntry sse; sse.test_num = 0; diff --git a/src/applib/smartctl_text_basic_parser.cpp b/src/applib/smartctl_text_basic_parser.cpp index 20224af..23f813a 100644 --- a/src/applib/smartctl_text_basic_parser.cpp +++ b/src/applib/smartctl_text_basic_parser.cpp @@ -9,30 +9,34 @@ Copyright: /// \weakgroup applib /// @{ +#include "smartctl_text_basic_parser.h" + // #include "local_glibmm.h" -//#include // localeconv //#include //#include +#include +#include +#include // #include "hz/locale_tools.h" // ScopedCLocale, locale_c_get(). +#include "ata_storage_property.h" #include "hz/string_algo.h" // string_* -//#include "hz/string_num.h" // string_is_numeric, number_to_string +#include "hz/string_num.h" // string_is_numeric, number_to_string //#include "hz/debug.h" // debug_* #include "app_pcrecpp.h" -//#include "smartctl_text_ata_parser.h" //#include "ata_storage_property_descr.h" // #include "warning_colors.h" +#include "smartctl_parser_types.h" #include "smartctl_version_parser.h" -#include "smartctl_text_basic_parser.h" -#include "hz/string_num.h" #include "smartctl_text_parser_helper.h" + hz::ExpectedVoid SmartctlTextBasicParser::parse(std::string_view smartctl_output) { // perform any2unix - std::string output = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_output)); + const std::string output = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_output)); if (output.empty()) { debug_out_warn("app", DBG_FUNC_MSG << "Empty string passed as an argument. Returning.\n"); @@ -192,7 +196,7 @@ hz::ExpectedVoid SmartctlTextBasicParser::parse(std::string std::string size; if (app_pcre_match("/^User Capacity:[ \\t]*(.*)$/mi", output, &size)) { int64_t bytes = 0; - std::string readable_size = SmartctlTextParserHelper::parse_byte_size(size, bytes, false); + const std::string readable_size = SmartctlTextParserHelper::parse_byte_size(size, bytes, false); AtaStorageProperty p; p.set_name("User Capacity", "user_capacity/bytes", "Capacity"); p.reported_value = size; diff --git a/src/applib/storage_property_repository.h b/src/applib/storage_property_repository.h index ced5fe2..a97e0a3 100644 --- a/src/applib/storage_property_repository.h +++ b/src/applib/storage_property_repository.h @@ -6,6 +6,7 @@ Copyright: #ifndef STORAGE_PROPERTY_REPOSITORY_H #define STORAGE_PROPERTY_REPOSITORY_H +#include #include #include "ata_storage_property.h" diff --git a/src/hz/error_container.h b/src/hz/error_container.h index 3d6aff2..1005180 100644 --- a/src/hz/error_container.h +++ b/src/hz/error_container.h @@ -15,6 +15,7 @@ Copyright: #include #include #include +#include // #include @@ -94,14 +95,25 @@ class ErrorContainer { +/// ExpectedValue is a wrapper around tl::expected that uses ErrorContainer as the error type. template using ExpectedValue = tl::expected>; +/// ExpectedVoid is a wrapper around tl::expected that uses ErrorContainer as the error type and void as value. template using ExpectedVoid = tl::expected>; +/// Unexpected creates an unexpected value with an ErrorContainer as error. +template +auto UnexpectedFromContainer(const ErrorContainerWithData& container) +{ + return tl::unexpected(container); +} + + +/// Unexpected creates an unexpected value with an ErrorContainer. template auto Unexpected(ErrorData&& data, std::string error_message, const std::source_location& loc = std::source_location::current() @@ -109,13 +121,22 @@ auto Unexpected(ErrorData&& data, std::string error_message, ) { return tl::unexpected(ErrorContainer(std::forward(data), - std::move(error_message), + std::move(error_message), loc // trace )); } +/// UnexpectedFrom creates an unexpected value from ExpectedValue or ExpectedVoid +/// containing an error. +template +auto UnexpectedFrom(ExpectedValueT unexpected_value) +{ + return tl::unexpected(unexpected_value.error()); +} + + /* std::ostream& operator<< (std::ostream& os, const std::source_location& location)