From d7032eb35152dbbe05f59044d71fabf6a9d009b8 Mon Sep 17 00:00:00 2001 From: Alexander Shaduri Date: Mon, 24 Feb 2025 13:52:04 +0400 Subject: [PATCH] Allow smartctl < 7.3 again, falling back to Text parser; this configuration is for compatibility only and not fully supported. --- src/applib/smartctl_version_parser.cpp | 37 ++++-- src/applib/smartctl_version_parser.h | 8 ++ src/applib/storage_device.cpp | 13 +- src/gui/gsc_main_window.cpp | 159 ++++++++++++++----------- src/gui/gsc_main_window.h | 9 +- 5 files changed, 144 insertions(+), 82 deletions(-) diff --git a/src/applib/smartctl_version_parser.cpp b/src/applib/smartctl_version_parser.cpp index 2bd1d17..8677554 100644 --- a/src/applib/smartctl_version_parser.cpp +++ b/src/applib/smartctl_version_parser.cpp @@ -69,17 +69,34 @@ bool SmartctlVersionParser::check_format_supported(SmartctlOutputFormat format, -SmartctlOutputFormat SmartctlVersionParser::get_default_format(SmartctlParserType parser_type) +namespace { + +SmartctlOutputFormat s_smartctl_output_default_format = SmartctlOutputFormat::Json; + +} + + + +void SmartctlVersionParser::set_default_format(SmartctlOutputFormat format) { - switch (parser_type) { - case SmartctlParserType::Basic: - return SmartctlOutputFormat::Json; - case SmartctlParserType::Ata: - return SmartctlOutputFormat::Json; - case SmartctlParserType::Nvme: - return SmartctlOutputFormat::Json; - } - return SmartctlOutputFormat::Json; + s_smartctl_output_default_format = format; +} + + + +SmartctlOutputFormat SmartctlVersionParser::get_default_format([[maybe_unused]] SmartctlParserType parser_type) +{ + // We no longer differentiate between parser types - they + // all either use Text, or Json. +// switch (parser_type) { +// case SmartctlParserType::Basic: +// return SmartctlOutputFormat::Json; +// case SmartctlParserType::Ata: +// return SmartctlOutputFormat::Json; +// case SmartctlParserType::Nvme: +// return SmartctlOutputFormat::Json; +// } + return s_smartctl_output_default_format; } diff --git a/src/applib/smartctl_version_parser.h b/src/applib/smartctl_version_parser.h index 78e5575..65868b4 100644 --- a/src/applib/smartctl_version_parser.h +++ b/src/applib/smartctl_version_parser.h @@ -48,13 +48,21 @@ class SmartctlVersionParser { static bool check_format_supported(SmartctlOutputFormat format, const std::string& version_only); + /// Get default output format for a parser type. + static void set_default_format(SmartctlOutputFormat format); + /// Get default output format for a parser type. static SmartctlOutputFormat get_default_format(SmartctlParserType parser_type); + /// Get default output format for a parser type. static SmartctlParserType get_default_parser_type(StorageDeviceDetectedType detected_type); + // We require this version at runtime to support --get=all. + static constexpr double minimum_req_runtime_version = 5.43; + + private: // Text Parser: diff --git a/src/applib/storage_device.cpp b/src/applib/storage_device.cpp index b588101..c2587c4 100644 --- a/src/applib/storage_device.cpp +++ b/src/applib/storage_device.cpp @@ -155,8 +155,17 @@ hz::ExpectedVoid StorageDevice::parse_basic_data() // Clear everything fetched before, except outputs and type this->clear_parse_results(); + // Detect the output format. + auto detect_status = SmartctlParser::detect_output_format(this->get_basic_output()); + auto output_format = SmartctlOutputFormat::Text; + if (detect_status.has_value()) { + output_format = detect_status.value(); + } else { + debug_out_warn("app", "Cannot detect smartctl output format. Assuming Text.\n"); + } + // Parse using Basic parser. This supports all drive types. - auto basic_parser = SmartctlParser::create(SmartctlParserType::Basic, SmartctlOutputFormat::Json); + auto basic_parser = SmartctlParser::create(SmartctlParserType::Basic, output_format); DBG_ASSERT_RETURN(basic_parser, hz::Unexpected(StorageDeviceError::ParseError, _("Cannot create parser"))); // This also fills the drive type properties. @@ -189,7 +198,7 @@ hz::ExpectedVoid StorageDevice::parse_basic_data() // Note that this may try to parse data the second time (it may already have // been parsed by parse_data() which failed at it). // if (do_set_properties) { -// auto parser = SmartctlParser::create(SmartctlParserType::Ata, SmartctlOutputFormat::Json); +// auto parser = SmartctlParser::create(SmartctlParserType::Ata, output_format); // DBG_ASSERT_RETURN(parser, hz::Unexpected(StorageDeviceError::ParseError, _("Cannot create parser"))); // // if (parser->parse(this->basic_output_)) { // try to parse it diff --git a/src/gui/gsc_main_window.cpp b/src/gui/gsc_main_window.cpp index 4ad4758..4828d08 100644 --- a/src/gui/gsc_main_window.cpp +++ b/src/gui/gsc_main_window.cpp @@ -82,72 +82,10 @@ GscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr // Check if smartctl is executable - - std::string error_msg; - bool show_output_button = true; - - do { - const std::string smartctl_binary = hz::fs_path_to_string(get_smartctl_binary()); - - // Don't use default options here - they are used when invoked - // with a device option. -// std::string smartctl_def_options = rconfig::get_data("system/smartctl_options"); - - if (smartctl_binary.empty()) { - error_msg = _("Smartctl binary is not specified in configuration."); - show_output_button = false; - break; - } - -// if (!smartctl_def_options.empty()) -// smartctl_def_options += " "; - - SmartctlExecutorGui ex; - ex.create_running_dialog(this); - ex.set_running_msg(_("Checking if smartctl is executable...")); - - ex.set_command(smartctl_binary, {"-V"}); // --version - - if (!ex.execute() || !ex.get_error_msg().empty()) { - error_msg = ex.get_error_msg(); - break; - } - - const std::string output = ex.get_stdout_str(); - if (output.empty()) { - error_msg = _("Smartctl returned an empty output."); - break; - } - - std::string version, version_full; - if (!SmartctlVersionParser::parse_version_text(output, version, version_full)) { - error_msg = _("Smartctl returned invalid output."); - break; - } - - { - // We require this version at runtime to support --get=all. - const double minimum_req_version = 5.43; - double version_double = 0; - if (hz::string_is_numeric_nolocale(version, version_double, false)) { - if (version_double < minimum_req_version) { - error_msg = Glib::ustring::compose(_("Smartctl version %1 found, %2 required."), version, hz::number_to_string_nolocale(minimum_req_version)); - break; - } - } - } - - } while (false); - - const bool smartctl_valid = error_msg.empty(); - if (!smartctl_valid) { - gsc_executor_error_dialog_show(_("There was an error while executing smartctl"), - error_msg + "\n\n" + _("Please specify the correct smartctl binary in Preferences.") + "", - this, true, show_output_button); - } + bool smartctl_valid = check_smartctl_version_and_set_format(); // Scan - populate_iconview(smartctl_valid); + populate_iconview_on_startup(smartctl_valid); } @@ -163,7 +101,7 @@ GscMainWindow::~GscMainWindow() -void GscMainWindow::populate_iconview(bool smartctl_valid) +void GscMainWindow::populate_iconview_on_startup(bool smartctl_valid) { if (!smartctl_valid) { iconview_->set_empty_view_message(GscMainWindowIconView::Message::NoSmartctl); @@ -173,7 +111,7 @@ void GscMainWindow::populate_iconview(bool smartctl_valid) } else if (rconfig::get_data("gui/scan_on_startup") // config option && !get_startup_settings().no_scan) { // command-line option - rescan_devices(); // scan for devices and fill the iconview + rescan_devices(true); // scan for devices and fill the iconview } else { iconview_->set_empty_view_message(GscMainWindowIconView::Message::ScanDisabled); @@ -568,7 +506,7 @@ void GscMainWindow::on_action_activated(GscMainWindow::action_t action_type) break; case action_rescan_devices: - rescan_devices(); + rescan_devices(false); break; case action_executor_log: @@ -851,12 +789,21 @@ void GscMainWindow::update_status_widgets() -void GscMainWindow::rescan_devices() +void GscMainWindow::rescan_devices(bool startup) { // ignore double-scan (may happen because we use gtk loop iterations here). if (this->scanning_) return; + // If we're not in startup, smartctl version may have changed (by specifying a different binary in Preferences) + // so we need to re-validate the output format: + if (!startup) { + // This shows an error dialog on error + if (!check_smartctl_version_and_set_format()) { + return; + } + } + // don't manipulate window sensitiveness here - it breaks things // (cursors, gtk errors pop out, etc.) @@ -1166,6 +1113,82 @@ void GscMainWindow::show_add_device_chooser() +bool GscMainWindow::check_smartctl_version_and_set_format() +{ + std::string error_msg; + bool show_output_button = true; + + do { + const std::string smartctl_binary = hz::fs_path_to_string(get_smartctl_binary()); + + // Don't use default options here - they are used when invoked + // with a device option. +// std::string smartctl_def_options = rconfig::get_data("system/smartctl_options"); + + if (smartctl_binary.empty()) { + error_msg = _("Smartctl binary is not specified in configuration."); + show_output_button = false; + break; + } + +// if (!smartctl_def_options.empty()) +// smartctl_def_options += " "; + + SmartctlExecutorGui ex; + ex.create_running_dialog(this); + ex.set_running_msg(_("Checking if smartctl is executable...")); + + ex.set_command(smartctl_binary, {"-V"}); // --version + + if (!ex.execute() || !ex.get_error_msg().empty()) { + error_msg = ex.get_error_msg(); + break; + } + + const std::string output = ex.get_stdout_str(); + if (output.empty()) { + error_msg = _("Smartctl returned an empty output."); + break; + } + + std::string version, version_full; + if (!SmartctlVersionParser::parse_version_text(output, version, version_full)) { + error_msg = _("Smartctl returned invalid output."); + break; + } + + // Check smartctl runtime version + if (double version_double = 0; hz::string_is_numeric_nolocale(version, version_double, false)) { + if (version_double < SmartctlVersionParser::minimum_req_runtime_version) { + error_msg = Glib::ustring::compose(_("Smartctl version %1 found, %2 required."), + version, + hz::number_to_string_nolocale(SmartctlVersionParser::minimum_req_runtime_version)); + break; + } + } + + if (SmartctlVersionParser::check_format_supported(SmartctlOutputFormat::Json, version)) { + debug_out_info("app", "Smartctl JSON output format supported.\n"); + SmartctlVersionParser::set_default_format(SmartctlOutputFormat::Json); + } else { + debug_out_warn("app", "Smartctl JSON output format not supported, falling back to Text output format.\n"); + SmartctlVersionParser::set_default_format(SmartctlOutputFormat::Text); + } + + } while (false); + + const bool smartctl_valid = error_msg.empty(); + if (!smartctl_valid) { + gsc_executor_error_dialog_show(_("There was an error while executing smartctl"), + error_msg + "\n\n" + _("Please specify the correct smartctl binary in Preferences.") + "", + this, true, show_output_button); + } + + return smartctl_valid; +} + + + void GscMainWindow::show_load_virtual_file_chooser() { static std::string last_dir; diff --git a/src/gui/gsc_main_window.h b/src/gui/gsc_main_window.h index 769e8cb..f485d0f 100644 --- a/src/gui/gsc_main_window.h +++ b/src/gui/gsc_main_window.h @@ -45,7 +45,7 @@ class GscMainWindow : public AppBuilderWidget { /// Scan for devices and fill the iconview - void rescan_devices(); + void rescan_devices(bool startup); /// Execute update-smart-drivedb @@ -111,7 +111,7 @@ class GscMainWindow : public AppBuilderWidget { bool create_widgets(); /// scan and populate iconview widget with drive icons - void populate_iconview(bool smartctl_valid); + void populate_iconview_on_startup(bool smartctl_valid); /// Show "Add Device" window void show_add_device_chooser(); @@ -120,6 +120,11 @@ class GscMainWindow : public AppBuilderWidget { void show_load_virtual_file_chooser(); + /// Check smartctl version and set default parser format accordingly. + /// An error dialog is shown if there is an error with smartctl. + bool check_smartctl_version_and_set_format(); + + /// Called when quit has been requested (by delete event or Quit action) void quit_requested();