Skip to content

Commit

Permalink
Implemented Basic Output JSON parser.
Browse files Browse the repository at this point in the history
Switched all devices to JSON by default.
  • Loading branch information
ashaduri committed Mar 20, 2024
1 parent 73463e5 commit 6edcf19
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 79 deletions.
60 changes: 4 additions & 56 deletions src/applib/smartctl_json_ata_parser.cpp
Expand Up @@ -82,8 +82,6 @@ _custom/smart_enabled

hz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse(std::string_view smartctl_output)
{
using namespace SmartctlJsonParserHelpers;

if (hz::string_trim_copy(smartctl_output).empty()) {
debug_out_warn("app", DBG_FUNC_MSG << "Empty string passed as an argument. Returning.\n");
return hz::Unexpected(SmartctlParserError::EmptyInput, "Smartctl data is empty.");
Expand All @@ -97,10 +95,13 @@ hz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse(std::string_v
return hz::Unexpected(SmartctlParserError::SyntaxError, std::string("Invalid JSON data: ") + e.what());
}

auto version_parse_status = parse_version(json_root_node);
AtaStorageProperty merged_property, full_property;
auto version_parse_status = SmartctlJsonParserHelpers::parse_version(json_root_node, merged_property, full_property);
if (!version_parse_status) {
return version_parse_status;
}
add_property(merged_property);
add_property(full_property);

auto info_parse_status = parse_section_info(json_root_node);
auto health_parse_status = parse_section_health(json_root_node);
Expand All @@ -110,59 +111,6 @@ hz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse(std::string_v



hz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_version(const nlohmann::json& json_root_node)
{
using namespace SmartctlJsonParserHelpers;

std::string smartctl_version;

auto json_ver = get_node_data<std::vector<int>>(json_root_node, "smartctl/version");

if (!json_ver.has_value()) {
debug_out_warn("app", DBG_FUNC_MSG << "Smartctl version not found in JSON.\n");

if (json_ver.error().data() == SmartctlJsonParserError::PathNotFound) {
return hz::Unexpected(SmartctlParserError::NoVersion, "Smartctl version not found in JSON data.");
}
if (json_ver->size() < 2) {
return hz::Unexpected(SmartctlParserError::DataError, "Error getting smartctl version from JSON data: Not enough version components.");
}
return hz::Unexpected(SmartctlParserError::DataError, std::format("Error getting smartctl version from JSON data: {}", json_ver.error().message()));
}

smartctl_version = std::format("{}.{}", json_ver->at(0), json_ver->at(1));

{
AtaStorageProperty p;
p.set_name("Smartctl version", "smartctl/version/_merged", "Smartctl Version");
// 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.readable_value = std::format("{}.{} r{} {} {}", json_ver->at(0), json_ver->at(1),
get_node_data<std::string>(json_root_node, "smartctl/svn_revision", {}).value_or(std::string()),
get_node_data<std::string>(json_root_node, "smartctl/platform_info", {}).value_or(std::string()),
get_node_data<std::string>(json_root_node, "smartctl/build_info", {}).value_or(std::string())
);
p.value = p.readable_value; // string-type value
p.section = AtaStorageProperty::Section::info; // add to info section
add_property(p);
}
if (!SmartctlVersionParser::check_format_supported(SmartctlOutputFormat::Json, smartctl_version)) {
debug_out_warn("app", DBG_FUNC_MSG << "Incompatible smartctl version. Returning.\n");
return hz::Unexpected(SmartctlParserError::IncompatibleVersion, "Incompatible smartctl version.");
}

return {};
}



hz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_info(const nlohmann::json& json_root_node)
{
using namespace SmartctlJsonParserHelpers;
Expand Down
3 changes: 0 additions & 3 deletions src/applib/smartctl_json_ata_parser.h
Expand Up @@ -31,9 +31,6 @@ class SmartctlJsonAtaParser : public SmartctlParser {

private:

/// Parse the version, filling in the properties
hz::ExpectedVoid<SmartctlParserError> parse_version(const nlohmann::json& json_root_node);

/// Parse the info section (root node), filling in the properties
hz::ExpectedVoid<SmartctlParserError> parse_section_info(const nlohmann::json& json_root_node);

Expand Down
150 changes: 148 additions & 2 deletions src/applib/smartctl_json_basic_parser.cpp
Expand Up @@ -22,7 +22,7 @@ License: GNU General Public License v3.0 only
//#include "app_pcrecpp.h"
//#include "smartctl_text_ata_parser.h"
//#include "ata_storage_property_descr.h"
// #include "warning_colors.h"
#include "smartctl_json_parser_helpers.h"
//#include "smartctl_version_parser.h"
#include "smartctl_json_basic_parser.h"

Expand All @@ -32,12 +32,158 @@ License: GNU General Public License v3.0 only
// Parse full "smartctl -x" output
hz::ExpectedVoid<SmartctlParserError> SmartctlJsonBasicParser::parse(std::string_view smartctl_output)
{
using namespace SmartctlJsonParserHelpers;

return {};
if (hz::string_trim_copy(smartctl_output).empty()) {
debug_out_warn("app", DBG_FUNC_MSG << "Empty string passed as an argument. Returning.\n");
return hz::Unexpected(SmartctlParserError::EmptyInput, "Smartctl data is empty.");
}

nlohmann::json json_root_node;
try {
json_root_node = nlohmann::json::parse(smartctl_output);
} 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());
}

AtaStorageProperty merged_property, full_property;
auto version_parse_status = SmartctlJsonParserHelpers::parse_version(json_root_node, merged_property, full_property);
if (!version_parse_status) {
return version_parse_status;
}
add_property(merged_property);
add_property(full_property);

return parse_section_basic_info(json_root_node);
}



hz::ExpectedVoid<SmartctlParserError> SmartctlJsonBasicParser::parse_section_basic_info(const nlohmann::json& json_root_node)
{
using namespace SmartctlJsonParserHelpers;

// TODO CD/DVD, RAID

bool smart_supported = true; // TODO
bool smart_enabled = true; // TODO

{
AtaStorageProperty p;
p.set_name("SMART Supported", "_text_only/smart_supported", "SMART Supported");
p.value = smart_supported;
p.section = AtaStorageProperty::Section::info; // add to info section
add_property(p);
}
{
AtaStorageProperty p;
p.set_name("SMART Enabled", "_text_only/smart_enabled", "SMART Enabled");
p.value = smart_enabled;
p.section = AtaStorageProperty::Section::info; // add to info section
add_property(p);
}

// Here we list the properties that are:
// 1. Essential for all devices, due to them being used in StorageDevice.
// 2. Present in devices for which we do not have specialized parsers (USB, etc.)
static const std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> info_keys = {

{"vendor", _("Vendor"), string_formatter()}, // Flash drive
{"product", _("Product"), string_formatter()}, // Flash drive

{"model_family", _("Model Family"), string_formatter()}, // (S)ATA
{"model_name", _("Device Model"), string_formatter()},

{"revision", _("Revision"), string_formatter()}, // Flash drive
{"scsi_version", _("SCSI Version"), string_formatter()}, // Flash drive

{"user_capacity/bytes", _("Capacity"),
custom_string_formatter<int64_t>([](int64_t value)
{
return std::format("{} [{}; {} bytes]",
hz::format_size(static_cast<uint64_t>(value), true),
hz::format_size(static_cast<uint64_t>(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<AtaStorageProperty, SmartctlParserError>
{
std::vector<std::string> values;
if (auto jval1 = get_node_data<int64_t>(root_node, "logical_block_size"); jval1) {
values.emplace_back(std::format("{} bytes logical", jval1.value()));
}
if (auto jval2 = get_node_data<int64_t>(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));
}
},

{"serial_number", _("Serial Number"), string_formatter()},
{"firmware_version", _("Firmware Version"), 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<AtaStorageProperty, SmartctlParserError>
{
std::vector<std::string> values;
if (auto jval1 = get_node_data<std::string>(root_node, "interface_speed/max/string"); jval1) {
values.emplace_back(std::format("Max: {}", jval1.value()));
}
if (auto jval2 = get_node_data<std::string>(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()},

{"rotation_rate", _("Rotation Rate"), // (S)ATA, used to detect HDD vs SSD
custom_string_formatter<int64_t>([](int64_t value)
{
return std::format("{} RPM", value);
})
},

{"form_factor/name", _("Form Factor"), 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());
}
}

return {};
}



Expand Down
5 changes: 5 additions & 0 deletions src/applib/smartctl_json_basic_parser.h
Expand Up @@ -29,6 +29,11 @@ class SmartctlJsonBasicParser : public SmartctlParser {
// Overridden
hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) override;


private:

hz::ExpectedVoid<SmartctlParserError> parse_section_basic_info(const nlohmann::json& json_root_node);

};


Expand Down
69 changes: 67 additions & 2 deletions src/applib/smartctl_json_parser_helpers.h
Expand Up @@ -20,6 +20,9 @@ License: GNU General Public License v3.0 only
#include "hz/string_algo.h"
#include "smartctl_version_parser.h"
#include "hz/format_unit.h"
#include "hz/error_container.h"
#include "ata_storage_property.h"



enum class SmartctlJsonParserError {
Expand All @@ -31,6 +34,7 @@ enum class SmartctlJsonParserError {
};



namespace SmartctlJsonParserHelpers {


Expand Down Expand Up @@ -80,6 +84,7 @@ template<typename T>
}



/// Get json node data. The path is slash-separated string.
/// If the data is not is found, the default value is returned.
template<typename T>
Expand All @@ -104,12 +109,16 @@ template<typename T>
}



/// 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)
-> hz::ExpectedValue<AtaStorageProperty, SmartctlParserError> >;


auto string_formatter()

/// Return a lambda which retrieves a key value as a string, and sets it as a property.
inline auto string_formatter()
{
return [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)
-> hz::ExpectedValue<AtaStorageProperty, SmartctlParserError>
Expand All @@ -127,7 +136,9 @@ auto string_formatter()
}


auto bool_formatter(const std::string_view& true_str, const std::string_view& false_str)

/// 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)
{
return [true_str, false_str](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)
-> hz::ExpectedValue<AtaStorageProperty, SmartctlParserError>
Expand All @@ -145,6 +156,8 @@ auto bool_formatter(const std::string_view& true_str, const std::string_view& fa
}



/// Return a lambda which retrieves a key value as a string (formatted using another lambda), and sets it as a property.
template<typename Type>
auto custom_string_formatter(std::function<std::string(Type value)> formatter)
{
Expand All @@ -165,6 +178,58 @@ auto custom_string_formatter(std::function<std::string(Type value)> formatter)



/// Parse version from json output, returning 2 properties.
inline hz::ExpectedVoid<SmartctlParserError> parse_version(const nlohmann::json& json_root_node,
AtaStorageProperty& merged_property, AtaStorageProperty& full_property)
{
using namespace SmartctlJsonParserHelpers;

std::string smartctl_version;

auto json_ver = get_node_data<std::vector<int>>(json_root_node, "smartctl/version");

if (!json_ver.has_value()) {
debug_out_warn("app", DBG_FUNC_MSG << "Smartctl version not found in JSON.\n");

if (json_ver.error().data() == SmartctlJsonParserError::PathNotFound) {
return hz::Unexpected(SmartctlParserError::NoVersion, "Smartctl version not found in JSON data.");
}
if (json_ver->size() < 2) {
return hz::Unexpected(SmartctlParserError::DataError, "Error getting smartctl version from JSON data: Not enough version components.");
}
return hz::Unexpected(SmartctlParserError::DataError, std::format("Error getting smartctl version from JSON data: {}", json_ver.error().message()));
}

smartctl_version = std::format("{}.{}", json_ver->at(0), json_ver->at(1));

{
merged_property.set_name("Smartctl version", "smartctl/version/_merged", "Smartctl Version");
// p.reported_value = smartctl_version;
merged_property.readable_value = smartctl_version;
merged_property.value = smartctl_version; // string-type value
merged_property.section = AtaStorageProperty::Section::info; // add to info section
}
{
full_property.set_name("Smartctl version", "smartctl/version/_merged_full", "Smartctl Version");
full_property.readable_value = std::format("{}.{} r{} {} {}", json_ver->at(0), json_ver->at(1),
get_node_data<std::string>(json_root_node, "smartctl/svn_revision", {}).value_or(std::string()),
get_node_data<std::string>(json_root_node, "smartctl/platform_info", {}).value_or(std::string()),
get_node_data<std::string>(json_root_node, "smartctl/build_info", {}).value_or(std::string())
);
full_property.value = full_property.readable_value; // string-type value
full_property.section = AtaStorageProperty::Section::info; // add to info section
}
if (!SmartctlVersionParser::check_format_supported(SmartctlOutputFormat::Json, smartctl_version)) {
debug_out_warn("app", DBG_FUNC_MSG << "Incompatible smartctl version. Returning.\n");
return hz::Unexpected(SmartctlParserError::IncompatibleVersion, "Incompatible smartctl version.");
}

return {};
}




} // namespace SmartctlJsonParserHelpers


Expand Down

0 comments on commit 6edcf19

Please sign in to comment.