From 18a6628d0ea32026f9d39b3181a5f1bd74354af0 Mon Sep 17 00:00:00 2001 From: Alexander Shaduri Date: Tue, 23 Apr 2024 17:03:52 +0400 Subject: [PATCH] Support parsing more of ATA output in JSON. --- src/applib/smartctl_json_ata_parser.cpp | 633 ++++++++++++++++++------ src/applib/smartctl_parser_types.h | 2 +- src/applib/smartctl_text_ata_parser.cpp | 4 +- 3 files changed, 492 insertions(+), 147 deletions(-) diff --git a/src/applib/smartctl_json_ata_parser.cpp b/src/applib/smartctl_json_ata_parser.cpp index 481dd4c..64a6147 100644 --- a/src/applib/smartctl_json_ata_parser.cpp +++ b/src/applib/smartctl_json_ata_parser.cpp @@ -119,18 +119,84 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse(std::string_v 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); + // Add properties for each parsed section so that the UI knows which tabs to show or hide + { + auto section_parse_status = parse_section_health(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::Health; + p.set_name("_parser/health_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_capabilities(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::Capabilities; + p.set_name("_parser/capabilities_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_attributes(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::Attributes; + p.set_name("_parser/attributes_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_directory_log(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::DirectoryLog; + p.set_name("_parser/directory_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_error_log(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::ErrorLog; + p.set_name("_parser/error_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_selftest_log(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::SelftestLog; + p.set_name("_parser/selftest_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_selective_selftest_log(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::SelectiveSelftestLog; + p.set_name("_parser/selective_selftest_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_scttemp_log(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::TemperatureLog; + p.set_name("_parser/temperature_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_scterc_log(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::ErcLog; + p.set_name("_parser/erc_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_devstat(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::Devstat; + p.set_name("_parser/devstat_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } + { + auto section_parse_status = parse_section_sataphy(json_root_node); + AtaStorageProperty p; + p.section = AtaStorageProperty::Section::PhyLog; + p.set_name("_parser/phy_log_section_available"); + p.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection; + } return {}; } @@ -516,15 +582,23 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_capab }; + bool section_properties_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::Capabilities; add_property(p.value()); + section_properties_found = true; } } + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::Capabilities))); + } + return {}; } @@ -534,53 +608,59 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_attri { using namespace SmartctlJsonParserHelpers; + bool section_properties_found = false; + // Revision - { + if (get_node_exists(json_root_node, "ata_smart_attributes/revision").value_or(false)) { AtaStorageProperty p; p.set_name("ata_smart_attributes/revision", "ata_smart_attributes/revision", _("Data structure revision number")); p.section = AtaStorageProperty::Section::Attributes; p.value = get_node_data(json_root_node, "ata_smart_attributes/revision").value_or(0); add_property(p); + section_properties_found = true; } - std::string table_key = "ata_smart_attributes/table"; + const std::string table_key = "ata_smart_attributes/table"; auto table_node = get_node(json_root_node, table_key); - if (!table_node) { - return hz::Unexpected(SmartctlParserError::KeyNotFound, table_node.error().message()); - } - if (!table_node->is_array()) { - return hz::Unexpected(SmartctlParserError::DataError, std::format("Node {} is not an array.", table_key)); - } // Entries - for (const auto& table_entry : table_node.value()) { - AtaStorageAttribute a; + if (table_node.has_value() && table_node->is_array()) { + for (const auto& table_entry : table_node.value()) { + AtaStorageAttribute a; - a.id = get_node_data(table_entry, "id").value_or(0); - a.flag = get_node_data(table_entry, "flags/string").value_or(""); - a.value = (get_node_exists(table_entry, "value").value_or(false) ? std::optional(get_node_data(table_entry, "value").value_or(0)) : std::nullopt); - a.worst = (get_node_exists(table_entry, "worst").value_or(false) ? std::optional(get_node_data(table_entry, "worst").value_or(0)) : std::nullopt); - a.threshold = (get_node_exists(table_entry, "thresh").value_or(false) ? std::optional(get_node_data(table_entry, "thresh").value_or(0)) : std::nullopt); - a.attr_type = get_node_data(table_entry, "flags/prefailure").value_or(false) ? AtaStorageAttribute::AttributeType::Prefail : AtaStorageAttribute::AttributeType::OldAge; - a.update_type = get_node_data(table_entry, "flags/updated_online").value_or(false) ? AtaStorageAttribute::UpdateType::Always : AtaStorageAttribute::UpdateType::Offline; + a.id = get_node_data(table_entry, "id").value_or(0); + a.flag = get_node_data(table_entry, "flags/string").value_or(""); + a.value = (get_node_exists(table_entry, "value").value_or(false) ? std::optional(get_node_data(table_entry, "value").value_or(0)) : std::nullopt); + a.worst = (get_node_exists(table_entry, "worst").value_or(false) ? std::optional(get_node_data(table_entry, "worst").value_or(0)) : std::nullopt); + a.threshold = (get_node_exists(table_entry, "thresh").value_or(false) ? std::optional(get_node_data(table_entry, "thresh").value_or(0)) : std::nullopt); + a.attr_type = get_node_data(table_entry, "flags/prefailure").value_or(false) ? AtaStorageAttribute::AttributeType::Prefail : AtaStorageAttribute::AttributeType::OldAge; + a.update_type = get_node_data(table_entry, "flags/updated_online").value_or(false) ? AtaStorageAttribute::UpdateType::Always : AtaStorageAttribute::UpdateType::Offline; - const std::string when_failed = get_node_data(table_entry, "when_failed").value_or(std::string()); - if (when_failed == "now") { - a.when_failed = AtaStorageAttribute::FailTime::Now; - } else if (when_failed == "past") { - a.when_failed = AtaStorageAttribute::FailTime::Past; - } else { // "" - a.when_failed = AtaStorageAttribute::FailTime::None; + const std::string when_failed = get_node_data(table_entry, "when_failed").value_or(std::string()); + if (when_failed == "now") { + a.when_failed = AtaStorageAttribute::FailTime::Now; + } else if (when_failed == "past") { + a.when_failed = AtaStorageAttribute::FailTime::Past; + } else { // "" + a.when_failed = AtaStorageAttribute::FailTime::None; + } + + a.raw_value = get_node_data(table_entry, "raw/string").value_or(std::string()); + a.raw_value_int = get_node_data(table_entry, "raw/value").value_or(0); + + AtaStorageProperty p; + p.set_name(get_node_data(table_entry, "name").value_or(std::string())); + p.section = AtaStorageProperty::Section::Attributes; + p.value = a; + add_property(p); + + section_properties_found = true; } + } - a.raw_value = get_node_data(table_entry, "raw/string").value_or(std::string()); - a.raw_value_int = get_node_data(table_entry, "raw/value").value_or(0); - - AtaStorageProperty p; - p.set_name(get_node_data(table_entry, "name").value_or(std::string())); - p.section = AtaStorageProperty::Section::Attributes; - p.value = a; - add_property(p); + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::Attributes))); } return {}; @@ -593,9 +673,11 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_direc using namespace SmartctlJsonParserHelpers; using namespace std::string_literals; + bool section_properties_found = false; + std::vector lines; - { + if (get_node_exists(json_root_node, "ata_log_directory/gp_dir_version").value_or(false)) { AtaStorageProperty p; p.set_name("ata_log_directory/gp_dir_version", "ata_log_directory/gp_dir_version", _("General purpose log directory version")); p.section = AtaStorageProperty::Section::DirectoryLog; @@ -603,8 +685,9 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_direc add_property(p); lines.emplace_back(std::format("General Purpose Log Directory Version: {}", p.get_value())); + section_properties_found = true; } - { + if (get_node_exists(json_root_node, "ata_log_directory/smart_dir_version").value_or(false)) { AtaStorageProperty p; p.set_name("ata_log_directory/smart_dir_version", "ata_log_directory/smart_dir_version", _("SMART log directory version")); p.section = AtaStorageProperty::Section::DirectoryLog; @@ -612,8 +695,9 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_direc add_property(p); lines.emplace_back(std::format("SMART Log Directory Version: {}", p.get_value())); + section_properties_found = true; } - { + if (get_node_exists(json_root_node, "ata_log_directory/smart_dir_multi_sector").value_or(false)) { AtaStorageProperty p; p.set_name("ata_log_directory/smart_dir_multi_sector", "ata_log_directory/smart_dir_multi_sector", _("Multi-sector log support")); p.section = AtaStorageProperty::Section::DirectoryLog; @@ -621,51 +705,54 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_direc add_property(p); lines.emplace_back(std::format("Multi-sector log support: {}", p.get_value() ? "Yes" : "No")); + section_properties_found = true; } // Table - std::string table_key = "ata_log_directory/table"; + const std::string table_key = "ata_log_directory/table"; auto table_node = get_node(json_root_node, table_key); - if (!table_node) { - return hz::Unexpected(SmartctlParserError::KeyNotFound, table_node.error().message()); - } - if (!table_node->is_array()) { - return hz::Unexpected(SmartctlParserError::DataError, std::format("Node {} is not an array.", table_key)); - } // Entries - for (const auto& table_entry : table_node.value()) { - const uint64_t address = get_node_data(table_entry, "address").value_or(0); - const std::string name = get_node_data(table_entry, "name").value_or(std::string()); - const bool read = get_node_data(table_entry, "read").value_or(false); - const bool write = get_node_data(table_entry, "write").value_or(false); - const uint64_t gp_sectors = get_node_data(table_entry, "gp_sectors").value_or(0); - const uint64_t smart_sectors = get_node_data(table_entry, "smart_sectors").value_or(0); + if (table_node.has_value() && table_node->is_array()) { + for (const auto& table_entry : table_node.value()) { + const uint64_t address = get_node_data(table_entry, "address").value_or(0); + const std::string name = get_node_data(table_entry, "name").value_or(std::string()); + const bool read = get_node_data(table_entry, "read").value_or(false); + const bool write = get_node_data(table_entry, "write").value_or(false); + const uint64_t gp_sectors = get_node_data(table_entry, "gp_sectors").value_or(0); + const uint64_t smart_sectors = get_node_data(table_entry, "smart_sectors").value_or(0); - // Address, GPL/SL, RO/RW, Num Sectors (GPL, Smart) , Name - // 0x00 GPL,SL R/O 1 Log Directory - lines.emplace_back(std::format( - "0x{:02X} GPL Sectors: {:8} SL Sectors: {:8} {}{} {}", - address, - gp_sectors == 0 ? "-" : std::to_string(gp_sectors), - smart_sectors == 0 ? "-" : std::to_string(smart_sectors), - (read ? "R" : "-"), - (write ? "W" : "-"), - name)); + // Address, GPL/SL, RO/RW, Num Sectors (GPL, Smart) , Name + // 0x00 GPL,SL R/O 1 Log Directory + lines.emplace_back(std::format( + "0x{:02X} GPL Sectors: {:8} SL Sectors: {:8} {}{} {}", + address, + gp_sectors == 0 ? "-" : std::to_string(gp_sectors), + smart_sectors == 0 ? "-" : std::to_string(smart_sectors), + (read ? "R" : "-"), + (write ? "W" : "-"), + name)); + } + + // The whole section + { + AtaStorageProperty p; + p.set_name("General Purpose Log Directory", "ata_log_directory/_merged"); + p.section = AtaStorageProperty::Section::DirectoryLog; + p.reported_value = hz::string_join(lines, "\n"); + p.value = p.reported_value; // string-type value + + add_property(p); + } + + section_properties_found = true; } - // The whole section - { - AtaStorageProperty p; - p.set_name("General Purpose Log Directory", "ata_log_directory/_merged"); - p.section = AtaStorageProperty::Section::DirectoryLog; - p.reported_value = hz::string_join(lines, "\n"); - p.value = p.reported_value; // string-type value - - add_property(p); + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::DirectoryLog))); } - return {}; } @@ -675,47 +762,54 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_error { using namespace SmartctlJsonParserHelpers; + bool section_properties_found = false; + // Revision - { + if (get_node_exists(json_root_node, "ata_smart_error_log/extended/revision").value_or(false)) { AtaStorageProperty p; p.set_name("ata_smart_error_log/extended/revision", "ata_smart_error_log/extended/revision", _("SMART extended comprehensive error log version")); p.section = AtaStorageProperty::Section::ErrorLog; p.value = get_node_data(json_root_node, "ata_smart_error_log/extended/revision").value_or(0); add_property(p); + section_properties_found = true; } // Count - { + if (get_node_exists(json_root_node, "ata_smart_error_log/extended/count").value_or(false)) { AtaStorageProperty p; p.set_name("ata_smart_error_log/extended/count", "ata_smart_error_log/extended/count", _("ATA error count")); p.section = AtaStorageProperty::Section::ErrorLog; p.value = get_node_data(json_root_node, "ata_smart_error_log/extended/count").value_or(0); add_property(p); + section_properties_found = true; } - std::string table_key = "ata_smart_error_log/extended/table"; + const std::string table_key = "ata_smart_error_log/extended/table"; auto table_node = get_node(json_root_node, table_key); - if (!table_node) { - return hz::Unexpected(SmartctlParserError::KeyNotFound, table_node.error().message()); - } - if (!table_node->is_array()) { - return hz::Unexpected(SmartctlParserError::DataError, std::format("Node {} is not an array.", table_key)); - } // Entries - for (const auto& table_entry : table_node.value()) { - AtaStorageErrorBlock block; - block.error_num = get_node_data(table_entry, "error_number").value_or(0); - block.log_index = get_node_data(table_entry, "log_index").value_or(0); - block.lifetime_hours = get_node_data(table_entry, "lifetime_hours").value_or(0); - block.device_state = get_node_data(table_entry, "device_state/string").value_or(std::string()); - block.lba = get_node_data(table_entry, "completion_registers/lba").value_or(0); - block.type_more_info = get_node_data(table_entry, "error_description").value_or(std::string()); + if (table_node.has_value() && table_node->is_array()) { + for (const auto& table_entry : table_node.value()) { + AtaStorageErrorBlock block; + block.error_num = get_node_data(table_entry, "error_number").value_or(0); + block.log_index = get_node_data(table_entry, "log_index").value_or(0); + block.lifetime_hours = get_node_data(table_entry, "lifetime_hours").value_or(0); + block.device_state = get_node_data(table_entry, "device_state/string").value_or(std::string()); + block.lba = get_node_data(table_entry, "completion_registers/lba").value_or(0); + block.type_more_info = get_node_data(table_entry, "error_description").value_or(std::string()); - AtaStorageProperty p; - p.set_name(std::format("Error {}", block.error_num)); - p.section = AtaStorageProperty::Section::ErrorLog; - p.value = block; - add_property(p); + AtaStorageProperty p; + p.set_name(std::format("Error {}", block.error_num)); + p.section = AtaStorageProperty::Section::ErrorLog; + p.value = block; + add_property(p); + } + + section_properties_found = true; + } + + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::ErrorLog))); } return {}; @@ -727,13 +821,16 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selft { using namespace SmartctlJsonParserHelpers; + bool section_properties_found = false; + // Revision - { + if (get_node_exists(json_root_node, "ata_smart_self_test_log/extended/revision").value_or(false)) { AtaStorageProperty p; p.set_name("ata_smart_self_test_log/extended/revision", "ata_smart_self_test_log/extended/revision", _("SMART extended self-test log version")); p.section = AtaStorageProperty::Section::SelftestLog; p.value = get_node_data(json_root_node, "ata_smart_self_test_log/extended/revision").value_or(0); add_property(p); + section_properties_found = true; } std::vector counts; @@ -745,8 +842,8 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selft p.section = AtaStorageProperty::Section::SelftestLog; p.value = get_node_data(json_root_node, "ata_smart_self_test_log/extended/count").value_or(0); p.show_in_ui = false; - counts.emplace_back(std::format("Self-test entries: {}", p.get_value())); add_property(p); + counts.emplace_back(std::format("Self-test entries: {}", p.get_value())); } // Error Count { @@ -755,8 +852,8 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selft p.section = AtaStorageProperty::Section::SelftestLog; p.value = get_node_data(json_root_node, "ata_smart_self_test_log/extended/error_count_total").value_or(0); p.show_in_ui = false; - counts.emplace_back(std::format("Total error count: {}", p.get_value())); add_property(p); + counts.emplace_back(std::format("Total error count: {}", p.get_value())); } // Outdated Error Count { @@ -765,8 +862,8 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selft p.section = AtaStorageProperty::Section::SelftestLog; p.value = get_node_data(json_root_node, "ata_smart_self_test_log/extended/error_count_outdated").value_or(0); p.show_in_ui = false; - counts.emplace_back(std::format("Outdated error count: {}", p.get_value())); add_property(p); + counts.emplace_back(std::format("Outdated error count: {}", p.get_value())); } // Displayed Counts @@ -776,48 +873,53 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selft p.section = AtaStorageProperty::Section::SelftestLog; p.value = hz::string_join(counts, "; "); add_property(p); + + section_properties_found = true; } - std::string table_key = "ata_smart_self_test_log/extended/table"; + const std::string table_key = "ata_smart_self_test_log/extended/table"; auto table_node = get_node(json_root_node, table_key); - if (!table_node) { - return hz::Unexpected(SmartctlParserError::KeyNotFound, table_node.error().message()); - } - if (!table_node->is_array()) { - return hz::Unexpected(SmartctlParserError::DataError, std::format("Node {} is not an array.", table_key)); - } // Entries - uint32_t entry_num = 1; - for (const auto& table_entry : table_node.value()) { - AtaStorageSelftestEntry entry; - entry.test_num = entry_num; - entry.type = get_node_data(table_entry, "type/string").value_or(std::string()); // FIXME use type/value for i18n - entry.status_str = get_node_data(table_entry, "status/string").value_or(std::string()); - entry.remaining_percent = get_node_data(table_entry, "status/remaining_percent").value_or(0); - entry.lifetime_hours = get_node_data(table_entry, "lifetime_hours").value_or(0); - entry.passed = get_node_data(table_entry, "status/passed").value_or(false); + if (table_node.has_value() && table_node->is_array()) { + uint32_t entry_num = 1; + for (const auto& table_entry : table_node.value()) { + AtaStorageSelftestEntry entry; + entry.test_num = entry_num; + entry.type = get_node_data(table_entry, "type/string").value_or(std::string()); // FIXME use type/value for i18n + entry.status_str = get_node_data(table_entry, "status/string").value_or(std::string()); + entry.remaining_percent = get_node_data(table_entry, "status/remaining_percent").value_or(0); + entry.lifetime_hours = get_node_data(table_entry, "lifetime_hours").value_or(0); + entry.passed = get_node_data(table_entry, "status/passed").value_or(false); - if (get_node_exists(table_entry, "lba").value_or(false)) { - entry.lba_of_first_error = std::format("0x{:X}", get_node_data(table_entry, "lba").value_or(0)); - } else { - entry.lba_of_first_error = "-"; + if (get_node_exists(table_entry, "lba").value_or(false)) { + entry.lba_of_first_error = std::format("0x{:X}", get_node_data(table_entry, "lba").value_or(0)); + } else { + entry.lba_of_first_error = "-"; + } + + if (get_node_exists(table_entry, "status/value").value_or(false)) { + const uint8_t status_value = get_node_data(table_entry, "status/value").value_or(0); + entry.status = static_cast(status_value >> 4); + } else { + entry.status = AtaStorageSelftestEntry::Status::Unknown; + } + + AtaStorageProperty p; + p.set_name(std::format("Self-test entry {}", entry.test_num)); + p.section = AtaStorageProperty::Section::SelftestLog; + p.value = entry; + add_property(p); + + ++entry_num; } - if (get_node_exists(table_entry, "status/value").value_or(false)) { - const uint8_t status_value = get_node_data(table_entry, "status/value").value_or(0); - entry.status = static_cast(status_value >> 4); - } else { - entry.status = AtaStorageSelftestEntry::Status::Unknown; - } + section_properties_found = true; + } - AtaStorageProperty p; - p.set_name(std::format("Self-test entry {}", entry.test_num)); - p.section = AtaStorageProperty::Section::SelftestLog; - p.value = entry; - add_property(p); - - ++entry_num; + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::SelftestLog))); } return {}; @@ -827,28 +929,271 @@ hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selft hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_selective_selftest_log(const nlohmann::json& json_root_node) { - return hz::ExpectedVoid(); + using namespace SmartctlJsonParserHelpers; + using namespace std::string_literals; + + bool section_properties_found = false; + + std::vector lines; + + if (get_node_exists(json_root_node, "ata_smart_selective_self_test_log/revision").value_or(false)) { + AtaStorageProperty p; + p.set_name("ata_smart_selective_self_test_log/revision", "ata_smart_selective_self_test_log/revision", _("SMART Selective self-test log data structure revision number")); + p.section = AtaStorageProperty::Section::SelectiveSelftestLog; + p.value = get_node_data(json_root_node, "ata_smart_selective_self_test_log/revision").value_or(0); + add_property(p); + section_properties_found = true; + } + if (get_node_exists(json_root_node, "ata_smart_selective_self_test_log/power_up_scan_resume_minutes").value_or(false)) { + AtaStorageProperty p; + p.set_name("ata_smart_selective_self_test_log/power_up_scan_resume_minutes", + "ata_smart_selective_self_test_log/power_up_scan_resume_minutes", + _("If Selective self-test is pending on power-up, resume delay (minutes)")); + p.section = AtaStorageProperty::Section::SelectiveSelftestLog; + p.value = get_node_data(json_root_node, "ata_smart_selective_self_test_log/power_up_scan_resume_minutes").value_or(0); + add_property(p); + section_properties_found = true; + } + if (get_node_exists(json_root_node, "ata_smart_selective_self_test_log/remainder_scan_enabled").value_or(false)) { + AtaStorageProperty p; + p.set_name("ata_smart_selective_self_test_log/remainder_scan_enabled", + "ata_smart_selective_self_test_log/remainder_scan_enabled", + _("After scanning selected spans, scan remainder of the drive")); + p.section = AtaStorageProperty::Section::SelectiveSelftestLog; + p.value = get_node_data(json_root_node, "ata_smart_selective_self_test_log/remainder_scan_enabled").value_or(0); + add_property(p); + section_properties_found = true; + } + + // Table + const std::string table_key = "ata_smart_selective_self_test_log/table"; + auto table_node = get_node(json_root_node, table_key); + + // Entries + if (table_node.has_value() && table_node->is_array()) { + int entry_num = 1; + for (const auto& table_entry : table_node.value()) { + const uint64_t lba_min = get_node_data(table_entry, "lba_min").value_or(0); + const uint64_t lba_max = get_node_data(table_entry, "lba_max").value_or(0); + const std::string status_str = get_node_data(table_entry, "status/string").value_or(std::string()); + + lines.emplace_back(std::format( + "{:2} Min/Max LBA: {:20}{:20} Status: {}", + entry_num, + lba_min, + lba_max, + status_str)); + ++entry_num; + } + + // The whole section + { + AtaStorageProperty p; + p.set_name("SMART selective self-test log", "ata_smart_selective_self_test_log/_merged"); + p.section = AtaStorageProperty::Section::SelectiveSelftestLog; + p.reported_value = hz::string_join(lines, "\n"); + p.value = p.reported_value; // string-type value + + add_property(p); + } + + section_properties_found = true; + } + + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::SelectiveSelftestLog))); + } + + return {}; } hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_scttemp_log(const nlohmann::json& json_root_node) { - return hz::ExpectedVoid(); + using namespace SmartctlJsonParserHelpers; + using namespace std::string_literals; + + bool section_properties_found = false; + + std::vector lines; + + if (get_node_exists(json_root_node, "ata_sct_status/format_version").value_or(false)) { + lines.emplace_back(std::format("SCT status version: {}", get_node_data(json_root_node, "ata_sct_status/format_version").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/sct_version").value_or(false)) { + lines.emplace_back(std::format("SCT format version: {}", get_node_data(json_root_node, "ata_sct_status/sct_version").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/device_state").value_or(false)) { + lines.emplace_back(std::format("Device state: {}", get_node_data(json_root_node, "ata_sct_status/device_state").value_or(std::string()))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/current").value_or(false)) { + lines.emplace_back(std::format("Current temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/current").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/power_cycle_min").value_or(false)) { + lines.emplace_back(std::format("Power cycle min. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/power_cycle_min").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/power_cycle_max").value_or(false)) { + lines.emplace_back(std::format("Power cycle max. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/power_cycle_max").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/lifetime_min").value_or(false)) { + lines.emplace_back(std::format("Lifetime min. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/lifetime_min").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/lifetime_max").value_or(false)) { + lines.emplace_back(std::format("Lifetime max. temperature: {}° Celsius", get_node_data(json_root_node, "ata_sct_status/temperature/lifetime_max").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/under_limit_count").value_or(false)) { + lines.emplace_back(std::format("Under limit count: {}", get_node_data(json_root_node, "ata_sct_status/temperature/under_limit_count").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_status/temperature/over_limit_count").value_or(false)) { + lines.emplace_back(std::format("Over limit count: {}", get_node_data(json_root_node, "ata_sct_status/temperature/over_limit_count").value_or(0))); + } + lines.emplace_back(); + if (get_node_exists(json_root_node, "ata_sct_temperature_history/version").value_or(false)) { + lines.emplace_back(std::format("SCT temperature history version: {}", get_node_data(json_root_node, "ata_sct_temperature_history/version").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_temperature_history/sampling_period_minutes").value_or(false)) { + lines.emplace_back(std::format("Temperature sampling period: {} min.", get_node_data(json_root_node, "ata_sct_temperature_history/sampling_period_minutes").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_temperature_history/logging_interval_minutes").value_or(false)) { + lines.emplace_back(std::format("Temperature logging interval: {} min.", get_node_data(json_root_node, "ata_sct_temperature_history/logging_interval_minutes").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/op_limit_min").value_or(false)) { + lines.emplace_back(std::format("Recommended operating temperature (minimum): {}° Celsius", + get_node_data(json_root_node, "ata_sct_temperature_history/temperature/op_limit_min").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/op_limit_max").value_or(false)) { + lines.emplace_back(std::format("Recommended operating temperature (maximum): {}° Celsius", + get_node_data(json_root_node, "ata_sct_temperature_history/temperature/op_limit_max").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/limit_min").value_or(false)) { + lines.emplace_back(std::format("Allowed operating temperature (minimum): {}° Celsius", + get_node_data(json_root_node, "ata_sct_temperature_history/temperature/limit_min").value_or(0))); + } + if (get_node_exists(json_root_node, "ata_sct_temperature_history/temperature/limit_max").value_or(false)) { + lines.emplace_back(std::format("Allowed operating temperature (maximum): {}° Celsius", + get_node_data(json_root_node, "ata_sct_temperature_history/temperature/limit_max").value_or(0))); + } + + // The whole section + if (!lines.empty()) { + AtaStorageProperty p; + p.set_name("Temperature log", "ata_sct_status/_merged"); + p.section = AtaStorageProperty::Section::TemperatureLog; + p.reported_value = hz::string_join(lines, "\n"); + p.value = p.reported_value; // string-type value + add_property(p); + + section_properties_found = true; + } + + // TODO Temperature log graph + + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::TemperatureLog))); + } + + return {}; } hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_scterc_log(const nlohmann::json& json_root_node) { - return hz::ExpectedVoid(); + using namespace SmartctlJsonParserHelpers; + using namespace std::string_literals; + + bool section_properties_found = false; + + std::vector lines; + + if (get_node_exists(json_root_node, "ata_sct_erc/read/enabled").value_or(false)) { + lines.emplace_back(std::format("SCT error recovery control (read): {}, {:.2f} seconds", + (get_node_data(json_root_node, "ata_sct_erc/read/enabled").value_or(false) ? "enabled" : "disabled"), + get_node_data(json_root_node, "ata_sct_erc/read/deciseconds").value_or(0.) / 10.)); + } + if (get_node_exists(json_root_node, "ata_sct_erc/write/enabled").value_or(false)) { + lines.emplace_back(std::format("SCT error recovery control (write): {}, {:.2f} seconds", + (get_node_data(json_root_node, "ata_sct_erc/write/enabled").value_or(false) ? "enabled" : "disabled"), + get_node_data(json_root_node, "ata_sct_erc/write/deciseconds").value_or(0.) / 10.)); + } + + // The whole section + if (!lines.empty()) { + AtaStorageProperty p; + p.set_name("SCT error recovery log", "ata_sct_erc/_merged"); + p.section = AtaStorageProperty::Section::ErcLog; + p.reported_value = hz::string_join(lines, "\n"); + p.value = p.reported_value; // string-type value + add_property(p); + + section_properties_found = true; + } + + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::ErcLog))); + } + + return {}; } hz::ExpectedVoid SmartctlJsonAtaParser::parse_section_devstat(const nlohmann::json& json_root_node) { - return hz::ExpectedVoid(); + using namespace SmartctlJsonParserHelpers; + + bool section_properties_found = false; + + const std::string pages_key = "ata_device_statistics/pages"; + auto page_node = get_node(json_root_node, pages_key); + + // Entries + if (page_node.has_value() && page_node->is_array()) { + for (const auto& page_entry : page_node.value()) { + AtaStorageStatistic page_stat; + page_stat.is_header = true; + page_stat.page = get_node_data(page_entry, "number").value_or(0); + + AtaStorageProperty page_prop; + page_prop.set_name(get_node_data(page_entry, "name").value_or(std::string())); + page_prop.section = AtaStorageProperty::Section::Devstat; + page_prop.value = page_stat; + add_property(page_prop); + + const std::string table_key = "table"; + auto table_node = get_node(page_entry, table_key); + + if (table_node.has_value() && table_node->is_array()) { + for (const auto& table_entry : table_node.value()) { + AtaStorageStatistic s; + s.page = page_stat.page; + s.flags = get_node_data(table_entry, "flags/string").value_or(std::string()); + s.value_int = get_node_data(table_entry, "value").value_or(0); + s.value = std::to_string(get_node_data(table_entry, "value").value_or(0)); + s.offset = get_node_data(table_entry, "offset").value_or(0); + + AtaStorageProperty p; + p.set_name(get_node_data(table_entry, "name").value_or(std::string())); + p.section = AtaStorageProperty::Section::Devstat; + p.value = s; + add_property(p); + } + } + + section_properties_found = true; + } + } + + if (!section_properties_found) { + return hz::Unexpected(SmartctlParserError::NoSection, + std::format("No section {} parsed.", AtaStorageProperty::get_readable_section_name(AtaStorageProperty::Section::Devstat))); + } + + return {}; } diff --git a/src/applib/smartctl_parser_types.h b/src/applib/smartctl_parser_types.h index 82fe9df..bccefd6 100644 --- a/src/applib/smartctl_parser_types.h +++ b/src/applib/smartctl_parser_types.h @@ -23,7 +23,7 @@ enum class SmartctlParserError { SyntaxError, NoVersion, IncompatibleVersion, - NoSections, + NoSection, ///< Returned by parser of each section if the section is not found UnknownSection, ///< Local parsing function error InternalError, NoSubsectionsParsed, diff --git a/src/applib/smartctl_text_ata_parser.cpp b/src/applib/smartctl_text_ata_parser.cpp index cf5b24c..38b6850 100644 --- a/src/applib/smartctl_text_ata_parser.cpp +++ b/src/applib/smartctl_text_ata_parser.cpp @@ -263,7 +263,7 @@ hz::ExpectedVoid SmartctlTextAtaParser::parse(std::string_v if (!status) { debug_out_warn("app", DBG_FUNC_MSG << "No ATA sections could be parsed. Returning.\n"); - return hz::Unexpected(SmartctlParserError::NoSections, "No ATA sections could be parsed."); + return hz::Unexpected(SmartctlParserError::NoSection, "No ATA sections could be parsed."); } return {}; @@ -1916,7 +1916,7 @@ If Selective self-test is pending on power-up, resume after 0 minute delay. // the whole subsection { AtaStorageProperty p(pt); - p.set_name("SMART Selective self-test log", "ata_smart_selective_self_test_log/_merged"); + p.set_name("SMART selective self-test log", "ata_smart_selective_self_test_log/_merged"); p.reported_value = sub; p.value = p.reported_value; // string-type value