diff --git a/src/applib/ata_storage_property_descr.cpp b/src/applib/ata_storage_property_descr.cpp index 0feaffc..c2e39fc 100644 --- a/src/applib/ata_storage_property_descr.cpp +++ b/src/applib/ata_storage_property_descr.cpp @@ -1702,7 +1702,7 @@ WarningLevel ata_storage_property_autoset_warning(AtaStorageProperty& p) switch(p.subsection) { case AtaStorageProperty::SubSection::health: - if (name_match(p, "smart_status/passed") && p.get_value() != "PASSED") { + if (name_match(p, "smart_status/passed") && !p.get_value()) { w = WarningLevel::alert; reason = "The drive is reporting that it will FAIL very soon. Please back up as soon as possible!"; } diff --git a/src/applib/smartctl_ata_json_parser.cpp b/src/applib/smartctl_ata_json_parser.cpp index 7583dc6..a6d549f 100644 --- a/src/applib/smartctl_ata_json_parser.cpp +++ b/src/applib/smartctl_ata_json_parser.cpp @@ -16,7 +16,7 @@ Copyright: #include "hz/debug.h" #include "hz/string_algo.h" #include "smartctl_version_parser.h" -#include "hz/string_num.h" +#include "hz/format_unit.h" @@ -160,6 +160,66 @@ template } +using PropertyRetrievalFunc = std::function< + auto(const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue >; + + +auto string_formatter() +{ + return [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + if (auto jval = get_node_data(root_node, key); jval) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + // p.reported_value = jval.value(); + p.readable_value = jval.value(); + p.value = jval.value(); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + }; +} + + +auto bool_formatter(const std::string_view& true_str, const std::string_view& false_str) +{ + return [true_str, false_str](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + if (auto jval = get_node_data(root_node, key); jval) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + // p.reported_value = (jval.value() ? true_str : false_str); + p.readable_value = (jval.value() ? true_str : false_str); + p.value = jval.value(); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + }; +} + + +template +auto custom_string_formatter(std::function formatter) +{ + return [formatter](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + if (auto jval = get_node_data(root_node, key); jval) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + // p.reported_value = formatter(jval.value()); + p.readable_value = formatter(jval.value()); + p.value = jval.value(); + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + }; +} + + } @@ -173,9 +233,9 @@ hz::ExpectedVoid SmartctlAtaJsonParser::parse_full(const st return hz::Unexpected(SmartctlParserError::EmptyInput, "Smartctl data is empty."); } - nlohmann::json root_node; + nlohmann::json json_root_node; try { - root_node = nlohmann::json::parse(json_data_full); + json_root_node = nlohmann::json::parse(json_data_full); } catch (const nlohmann::json::parse_error& e) { debug_out_warn("app", DBG_FUNC_MSG << "Error parsing smartctl output as JSON: " << e.what() << "\n"); return hz::Unexpected(SmartctlParserError::SyntaxError, std::string("Invalid JSON data: ") + e.what()); @@ -184,7 +244,7 @@ hz::ExpectedVoid SmartctlAtaJsonParser::parse_full(const st // Version std::string smartctl_version; { - auto json_ver = get_node_data>(root_node, "smartctl/version"); + auto json_ver = get_node_data>(json_root_node, "smartctl/version"); if (!json_ver.has_value()) { debug_out_warn("app", DBG_FUNC_MSG << "Smartctl version not found in JSON.\n"); @@ -203,20 +263,21 @@ hz::ExpectedVoid SmartctlAtaJsonParser::parse_full(const st { AtaStorageProperty p; p.set_name("Smartctl version", "smartctl/version/_merged", "Smartctl Version"); - p.reported_value = smartctl_version; - p.value = p.reported_value; // string-type value + // p.reported_value = smartctl_version; + p.readable_value = smartctl_version; + p.value = smartctl_version; // string-type value p.section = AtaStorageProperty::Section::info; // add to info section add_property(p); } { AtaStorageProperty p; p.set_name("Smartctl version", "smartctl/version/_merged_full", "Smartctl Version"); - p.reported_value = std::format("{}.{} r{} {} {}", json_ver->at(0), json_ver->at(1), - get_node_data(root_node, "smartctl/svn_revision", {}).value_or(std::string()), - get_node_data(root_node, "smartctl/platform_info", {}).value_or(std::string()), - get_node_data(root_node, "smartctl/build_info", {}).value_or(std::string()) + p.readable_value = std::format("{}.{} r{} {} {}", json_ver->at(0), json_ver->at(1), + get_node_data(json_root_node, "smartctl/svn_revision", {}).value_or(std::string()), + get_node_data(json_root_node, "smartctl/platform_info", {}).value_or(std::string()), + get_node_data(json_root_node, "smartctl/build_info", {}).value_or(std::string()) ); - p.value = p.reported_value; // string-type value + p.value = p.readable_value; // string-type value p.section = AtaStorageProperty::Section::info; // add to info section add_property(p); } @@ -227,34 +288,141 @@ hz::ExpectedVoid SmartctlAtaJsonParser::parse_full(const st } - // Info section - const std::unordered_map info_keys = { - {"model_family", _("Model Family")}, - {"model_name", _("Model Name")}, - {"serial_number", _("Serial Number")}, - {"firmware_version", _("Firmware Version")}, - {"user_capacity", _("User Capacity")}, - {"logical_block_size", _("Logical Block Size")}, - {"physical_block_size", _("Physical Block Size")}, - {"rotation_rate", _("Rotation Rate")}, - {"in_smartctl_database", _("In Smartctl Database")}, - }; + // Info Section + { - for (const auto& [key, displayable_name] : info_keys) { - auto jval = get_node_data(root_node, key); - // TODO Type error + const std::vector> info_keys = { + {"model_family", _("Model Family"), string_formatter()}, + {"model_name", _("Device Model"), string_formatter()}, + {"serial_number", _("Serial Number"), string_formatter()}, - if (jval.has_value()) { - AtaStorageProperty p; - p.section = AtaStorageProperty::Section::info; - p.set_name(key, key, displayable_name); - p.reported_value = jval.value(); - p.value = p.reported_value; // string-type value + {"wwn/_merged", _("World Wide Name"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + auto jval1 = get_node_data(root_node, "wwn/naa"); + auto jval2 = get_node_data(root_node, "wwn/oui"); + auto jval3 = get_node_data(root_node, "wwn/id"); - add_property(p); + if (jval1 && jval2 && jval3) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + // p.readable_value = std::format("{:X} {:X} {:X}", jval1.value(), jval2.value(), jval3.value()); + p.readable_value = std::format("{:X}-{:06X}-{:08X}", jval1.value(), jval2.value(), jval3.value()); + p.value = p.readable_value; // string-type value + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"firmware_version", _("Firmware Version"), string_formatter()}, + + {"user_capacity/bytes", _("Capacity"), + custom_string_formatter([](int64_t value) + { + return std::format("{} [{}; {} bytes]", + hz::format_size(static_cast(value), true), + hz::format_size(static_cast(value), false), + hz::number_to_string_locale(value)); + }) + }, + + {"physical_block_size/_and/logical_block_size", _("Sector Size"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + std::vector values; + if (auto jval1 = get_node_data(root_node, "logical_block_size"); jval1) { + values.emplace_back(std::format("{} bytes logical", jval1.value())); + } + if (auto jval2 = get_node_data(root_node, "physical_block_size"); jval2) { + values.emplace_back(std::format("{} bytes physical", jval2.value())); + } + if (!values.empty()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.readable_value = hz::string_join(values, ", "); + p.value = p.readable_value; + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"rotation_rate", _("Rotation Rate"), + custom_string_formatter([](int64_t value) + { + return std::format("{} RPM", value); + }) + }, + + {"form_factor/name", _("Form Factor"), string_formatter()}, + {"trim/supported", _("TRIM Supported"), bool_formatter(_("Yes"), _("No"))}, + {"in_smartctl_database", _("In Smartctl Database"), bool_formatter(_("Yes"), _("No"))}, + {"ata_version/string", _("ATA Version"), string_formatter()}, + {"sata_version/string", _("SATA Version"), string_formatter()}, + + {"interface_speed/_merged", _("Interface Speed"), + [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name) + -> hz::ExpectedValue + { + std::vector values; + if (auto jval1 = get_node_data(root_node, "interface_speed/max/string"); jval1) { + values.emplace_back(std::format("Max: {}", jval1.value())); + } + if (auto jval2 = get_node_data(root_node, "interface_speed/current/string"); jval2) { + values.emplace_back(std::format("Current: {}", jval2.value())); + } + if (!values.empty()) { + AtaStorageProperty p; + p.set_name(key, key, displayable_name); + p.readable_value = hz::string_join(values, ", "); + p.value = p.readable_value; + return p; + } + return hz::Unexpected(SmartctlParserError::KeyNotFound, std::format("Error getting key {} from JSON data.", key)); + } + }, + + {"local_time/asctime", _("Scanned on"), string_formatter()}, + {"read_lookahead/enabled", _("Read Look-Ahead"), bool_formatter(_("Enabled"), _("Disabled"))}, + {"write_cache/enabled", _("Write Cache"), bool_formatter(_("Enabled"), _("Disabled"))}, + {"ata_dsn/enabled", _("DSN Feature"), bool_formatter(_("Enabled"), _("Disabled"))}, + {"ata_security/string", _("ATA Security"), string_formatter()}, + }; + + for (const auto& [key, displayable_name, retrieval_func] : info_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()); + } } } + + // Health Section + { + const std::vector> health_keys = { + {"smart_status/passed", _("Overall Health Self-Assessment Test"), bool_formatter(_("PASSED"), _("FAILED"))}, + }; + + for (const auto& [key, displayable_name, retrieval_func] : health_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::data; + p->subsection = AtaStorageProperty::SubSection::health; + add_property(p.value()); + } + } + } + + return {}; } diff --git a/src/applib/smartctl_ata_json_parser.h b/src/applib/smartctl_ata_json_parser.h index e264fc7..163623b 100644 --- a/src/applib/smartctl_ata_json_parser.h +++ b/src/applib/smartctl_ata_json_parser.h @@ -19,9 +19,7 @@ Copyright: -/// Smartctl (S)ATA text output parser. -/// Note: ALL parse_* functions (except parse_full() and parse_version()) -/// expect data in unix-newline format! +/// Smartctl (S)ATA JSON output parser class SmartctlAtaJsonParser : public SmartctlParser { public: diff --git a/src/applib/smartctl_ata_text_parser.cpp b/src/applib/smartctl_ata_text_parser.cpp index 5bc3a8f..65039a6 100644 --- a/src/applib/smartctl_ata_text_parser.cpp +++ b/src/applib/smartctl_ata_text_parser.cpp @@ -791,7 +791,7 @@ Device is: In smartctl database [for details use: -P show] if (app_pcre_match("/SMART overall-health self-assessment/mi", name)) { pt.set_name(name, "smart_status/passed", "Overall Health Self-Assessment Test"); pt.reported_value = value; - pt.value = pt.reported_value; // string-type value + pt.value = (pt.reported_value == "PASSED"); // bool add_property(pt); } diff --git a/src/applib/smartctl_parser.h b/src/applib/smartctl_parser.h index 092959d..5c1de35 100644 --- a/src/applib/smartctl_parser.h +++ b/src/applib/smartctl_parser.h @@ -34,13 +34,12 @@ enum class SmartctlParserError { InternalError, NoSubsectionsParsed, DataError, + KeyNotFound, }; -/// Smartctl (S)ATA text output parser. -/// Note: ALL parse_* functions (except parse_full() and parse_version()) -/// expect data in unix-newline format! +/// Smartctl output parser. class SmartctlParser { protected: diff --git a/src/applib/smartctl_text_parser_helper.cpp b/src/applib/smartctl_text_parser_helper.cpp index a2f5546..99b43ca 100644 --- a/src/applib/smartctl_text_parser_helper.cpp +++ b/src/applib/smartctl_text_parser_helper.cpp @@ -45,8 +45,8 @@ std::string SmartctlTextParserHelper::parse_byte_size(const std::string& str, in // startup, so set it now (temporarily). Otherwise, just use the current locale's // thousands separator. { - std::string old_locale = hz::locale_c_get(); - hz::ScopedCLocale loc("", old_locale == "C"); // set system locale if the current one is C + const std::string old_locale = hz::locale_c_get(); + const hz::ScopedCLocale loc("", old_locale == "C"); // set system locale if the current one is C struct lconv* lc = std::localeconv(); if (lc && lc->thousands_sep && lc->thousands_sep[0] != '\0') {