diff --git a/gsmartcontrol/src/applib/Makefile.am b/gsmartcontrol/src/applib/Makefile.am index fc29c8a..25a52dc 100644 --- a/gsmartcontrol/src/applib/Makefile.am +++ b/gsmartcontrol/src/applib/Makefile.am @@ -7,11 +7,11 @@ libapplib_a_SOURCES = app_gtkmm_utils.cpp app_pango_utils.cpp cmdex.cpp \ storage_detector_win32.cpp storage_device.cpp storage_property.cpp \ storage_property_descr.cpp noinst_HEADERS = app_gtkmm_features.h app_gtkmm_utils.h app_pango_utils.h \ - app_pcrecpp.h app_ui_res_utils.h cmdex.h cmdex_sync.h cmdex_sync_gui.h executor_factory.h gui_utils.h \ + app_pcrecpp.h app_ui_res_utils.h cli_executors.h cmdex.h cmdex_sync.h cmdex_sync_gui.h executor_factory.h gui_utils.h \ selftest.h smartctl_executor.h smartctl_executor_gui.h smartctl_parser.h \ storage_detector.h storage_detector_helpers.h storage_detector_linux.h storage_detector_other.h \ storage_detector_win32.h storage_device.h storage_property.h storage_property_colors.h \ - storage_property_descr.h storage_settings.h tw_cli_executor.h wrapping_label.h + storage_property_descr.h storage_settings.h wrapping_label.h # don't use absolute path for the current dir's .a, because the make # dependency resolver won't get it (needed for parallel builds) diff --git a/gsmartcontrol/src/applib/tw_cli_executor.h b/gsmartcontrol/src/applib/cli_executors.h similarity index 52% rename from gsmartcontrol/src/applib/tw_cli_executor.h rename to gsmartcontrol/src/applib/cli_executors.h index 37e20a9..96f190f 100644 --- a/gsmartcontrol/src/applib/tw_cli_executor.h +++ b/gsmartcontrol/src/applib/cli_executors.h @@ -9,8 +9,8 @@ /// \weakgroup applib /// @{ -#ifndef TW_CLI_EXECUTOR_H -#define TW_CLI_EXECUTOR_H +#ifndef CLI_EXECUTORS_H +#define CLI_EXECUTORS_H #include "cmdex.h" #include "cmdex_sync.h" @@ -129,6 +129,118 @@ typedef hz::intrusive_ptr TwCliExecutorGuiRefPtr; +/// Executor for cli (Areca utility) +template +class ArecaCliExecutorGeneric : public ExecutorSync { + public: + + /// Constructor + ArecaCliExecutorGeneric(const std::string& cmd, const std::string& cmdargs) + : ExecutorSync(cmd, cmdargs) + { + this->construct(); + } + + + /// Constructor + ArecaCliExecutorGeneric() + { + this->construct(); + } + + + /// Virtual destructor + virtual ~ArecaCliExecutorGeneric() + { } + + + protected: + + /// Called from constructors + void construct() + { + ExecutorSync::get_command_executor().set_exit_status_translator(&ArecaCliExecutorGeneric::translate_exit_status, NULL); + this->set_error_header("An error occurred while executing Areca cli:\n\n"); + } + + + /// Exit status translate handler + static std::string translate_exit_status(int status, void* user_data) + { + return std::string(); + } + + + /// Import the last error from command executor and clear all errors there + virtual void import_error() + { + Cmdex& cmdex = this->get_command_executor(); + cmdex.errors_lock(); + + Cmdex::error_list_t errors = cmdex.get_errors(false); // these are not clones + + hz::ErrorBase* e = 0; + // find the last relevant error. + // note: const_reverse_iterator doesn't work on gcc 3, so don't do it. + for (Cmdex::error_list_t::reverse_iterator iter = errors.rbegin(); iter != errors.rend(); ++iter) { + // ignore iochannel errors, they may mask the real errors + if ((*iter)->get_type() != "giochannel" && (*iter)->get_type() != "custom") { + e = (*iter)->clone(); + break; + } + } + + cmdex.clear_errors(false); // and clear them + + cmdex.errors_unlock(); + + if (e) { // if error is present, alert the user + on_error_warn(e); + } + } + + + /// This is called when an error occurs in command executor. + /// Note: The warnings are already printed via debug_* in cmdex. + virtual void on_error_warn(hz::ErrorBase* e) + { + if (!e) + return; + + // import the error only if it's relevant. + std::string error_type = e->get_type(); + + // ignore giochannel errors - higher level errors will be triggered, and they more user-friendly. + if (error_type == "giochannel" || error_type == "custom") { + return; + } + + this->set_error_msg(e->get_message()); + } + +}; + + + + +/// tw_cli executor without GUI support +typedef ArecaCliExecutorGeneric ArecaCliExecutor; + +/// A reference-counting pointer to ArecaCliExecutor +typedef hz::intrusive_ptr ArecaCliExecutorRefPtr; + + + +/// tw_cli executor with GUI support +typedef ArecaCliExecutorGeneric ArecaCliExecutorGui; + +/// A reference-counting pointer to ArecaCliExecutorGui +typedef hz::intrusive_ptr ArecaCliExecutorGuiRefPtr; + + + + + #endif diff --git a/gsmartcontrol/src/applib/executor_factory.cpp b/gsmartcontrol/src/applib/executor_factory.cpp index dec3055..bbb3cf3 100644 --- a/gsmartcontrol/src/applib/executor_factory.cpp +++ b/gsmartcontrol/src/applib/executor_factory.cpp @@ -12,7 +12,7 @@ #include "hz/debug.h" #include "executor_factory.h" #include "smartctl_executor_gui.h" -#include "tw_cli_executor.h" +#include "cli_executors.h" @@ -44,6 +44,15 @@ hz::intrusive_ptr ExecutorFactory::create_executor(ExecutorFactory::T } return TwCliExecutorRefPtr(new TwCliExecutor()); } + case ExecutorArecaCli: + { + if (use_gui_) { + ArecaCliExecutorGuiRefPtr ex = ArecaCliExecutorGuiRefPtr(new ArecaCliExecutorGui()); + ex->create_running_dialog(parent_); // dialog parent + return ex; + } + return ArecaCliExecutorRefPtr(new ArecaCliExecutor()); + } } DBG_ASSERT(0); diff --git a/gsmartcontrol/src/applib/executor_factory.h b/gsmartcontrol/src/applib/executor_factory.h index f6e40ce..bbf2cbf 100644 --- a/gsmartcontrol/src/applib/executor_factory.h +++ b/gsmartcontrol/src/applib/executor_factory.h @@ -31,7 +31,8 @@ class ExecutorFactory : public hz::intrusive_ptr_referenced { /// Executor type for create_executor() enum Type { ExecutorSmartctl, - ExecutorTwCli + ExecutorTwCli, + ExecutorArecaCli }; diff --git a/gsmartcontrol/src/applib/storage_detector_helpers.h b/gsmartcontrol/src/applib/storage_detector_helpers.h index 9663b0a..28baf4b 100644 --- a/gsmartcontrol/src/applib/storage_detector_helpers.h +++ b/gsmartcontrol/src/applib/storage_detector_helpers.h @@ -68,7 +68,7 @@ inline std::string execute_tw_cli(ExecutorFactoryRefPtr ex_factory, const std::s -/// Get number of ports using tw_cli. Return -1 on error. +/// Get the drives on a 3ware controller using tw_cli. /// Note that the drives are inserted in the order they are detected. inline std::string tw_cli_get_drives(const std::string& dev, int controller, std::vector& drives, ExecutorFactoryRefPtr ex_factory, bool use_tw_cli_dev) @@ -149,6 +149,53 @@ inline std::string tw_cli_get_controllers(ExecutorFactoryRefPtr ex_factory, std: +/// Get number of ports by sequentially running smartctl on each port, until +/// one of the gives an error. \c type contains a printf-formatted string with %d. +/// \return an error message on error. +inline std::string smartctl_scan_drives_sequentially(const std::string& dev, const std::string& type, + int from, int to, std::vector& drives, ExecutorFactoryRefPtr ex_factory, std::string& last_output) +{ + hz::intrusive_ptr smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl); + + for (int i = from; i <= to; ++i) { + std::string type_arg = hz::string_sprintf(type.c_str(), i); + StorageDeviceRefPtr drive(new StorageDevice(dev, type_arg)); + + // This will generate an error if smartctl doesn't return 0, which is what happens + // with non-populated ports. + // Sometimes the output contains: + // "Read Device Identity failed: Input/output error" + // or + // "Read Device Identity failed: empty IDENTIFY data" + std::string error_msg = drive->fetch_basic_data_and_parse(smartctl_ex); + last_output = drive->get_info_output(); + + // If we've reached smartctl port limit (older versions may have smaller limits), abort. + if (app_pcre_match("/VALID ARGUMENTS ARE/mi", last_output)) { + break; + } + + // If we couldn't open the device, it means there is no such controller at specified device + // and scanning the ports is useless. + if (app_pcre_match("/No .* controller found/mi", last_output) + || app_pcre_match("/Smartctl open device: .* failed: No such device/mi", last_output) ) { + break; + } + + if (!error_msg.empty()) { + debug_out_info("app", "Smartctl returned with an error: " << error_msg << "\n"); + debug_out_dump("app", "Skipping drive " << drive->get_device_with_type() << " due to smartctl error.\n"); + } else { + drives.push_back(drive); + debug_out_info("app", "Added drive " << drive->get_device_with_type() << ".\n"); + } + } + + return std::string(); +} + + + diff --git a/gsmartcontrol/src/applib/storage_detector_linux.cpp b/gsmartcontrol/src/applib/storage_detector_linux.cpp index 9ca0538..186d25d 100644 --- a/gsmartcontrol/src/applib/storage_detector_linux.cpp +++ b/gsmartcontrol/src/applib/storage_detector_linux.cpp @@ -18,6 +18,7 @@ #include // std::fgets(), std::FILE #include // ENXIO #include +#include #include #include // std::pair @@ -328,40 +329,6 @@ inline std::string read_proc_scsi_sg_devices_file(std::vector< std::vector -/// Get number of ports by sequentially running smartctl on each port, until -/// one of the gives an error. Return -1 on error. -inline std::string smartctl_get_drives(const std::string& dev, const std::string& type, - int from, int to, std::vector& drives, ExecutorFactoryRefPtr ex_factory) -{ - hz::intrusive_ptr smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl); - - for (int i = from; i <= to; ++i) { - std::string type_arg = hz::string_sprintf(type.c_str(), i); - StorageDeviceRefPtr drive(new StorageDevice(dev, type_arg)); - -// std::string error_msg = execute_smartctl(dev, "-d " + type_arg, "-i", smartctl_ex, output); - std::string error_msg = drive->fetch_basic_data_and_parse(smartctl_ex); - std::string output = drive->get_info_output(); - - // if we've reached smartctl port limit (older versions may have smaller limits), abort. - if (app_pcre_match("/VALID ARGUMENTS ARE/mi", output)) { - break; - } - - if (!error_msg.empty()) { - debug_out_info("app", "Smartctl returned with an error: " << error_msg << "\n"); - debug_out_dump("app", "Skipping drive " << drive->get_device_with_type() << " due to smartctl error.\n"); - } else { - drives.push_back(drive); - debug_out_info("app", "Added drive " << drive->get_device_with_type() << ".\n"); - } - } - - return std::string(); -} - - - /**
@@ -601,10 +568,12 @@ inline std::string detect_drives_linux_3ware(std::vector& d
 
 		error_msg = tw_cli_get_drives(dev, vendors_models[i].first, drives, ex_factory, false);
 		if (!error_msg.empty()) {  // no tw_cli
-			int max_ports = rconfig::get_data("system/linux_max_scan_ports");
-			debug_out_dump("app", "Starting brute-force port scan on 0-" << max_ports << " ports, device \"" << dev << "\". Change the maximum by setting \"system/linux_max_scan_ports\" config key.\n");
-			max_ports = std::max(0, std::min(max_ports, 128));  // Reasonable sanity check.
-			error_msg = smartctl_get_drives(dev, "3ware,%d", 0, max_ports, drives, ex_factory);
+			int max_ports = rconfig::get_data("system/linux_3ware_max_scan_port");
+			max_ports = std::max(0, std::min(max_ports, 127));  // Sanity check
+			debug_out_dump("app", "Starting brute-force port scan on 0-" << max_ports << " ports, device \"" << dev
+					<< "\". Change the maximum by setting \"system/linux_3ware_max_scan_port\" config key.\n");
+			std::string last_output;
+			error_msg = smartctl_scan_drives_sequentially(dev, "3ware,%d", 0, max_ports, drives, ex_factory, last_output);
 			debug_out_dump("app", "Brute-force port scan finished.\n");
 		}
 
@@ -741,9 +710,15 @@ inline std::string detect_drives_linux_adaptec(std::vector&
 
 
 
-/** 
+/** 
 Areca Linux (arcmsr driver):
-Call as: smartctl -i -d areca,[1-24] /dev/sg0
+Call as:
+	- smartctl -i -d areca,N /dev/sg0  (N is [1,24]).
+	- smartctl -i -d areca,N/E /dev/sg0  (N is [1,128], E is [1,8]).
+The models that have "ix" (case-insensitive) in their model names
+have the enclosures and require the N,E syntax.
+Note: Using N/E syntax with non-enclosure cards seems to work
+regardless of the value of E.
 
 Detection:
 	There don't seem to be any entries in /proc/devices.
@@ -755,13 +730,14 @@ Detect SCSI host (N in hostN below):
 	The line number in /proc/scsi/sg/devices is X in /dev/sgX.
 Check /sys/bus/scsi/devices/hostN/scsi_host/hostN/host_fw_hd_channels, set
 	its contents as the number of ports. If not present, use 24 (maximum).
-Probe each port for a valid drive: If the output contains "Device Read Identity Failed",
-	then there is no drive on that port (or Areca has an old firmware, nothing we can
-	do there).
+	No-enclosure models only, since we have no info yet for the enclosure ones.
+Probe each port for a valid drive: If the output contains "Device Read Identity Failed"
+	or "empty IDENTIFY data", then there is no drive on that port (or Areca has an
+	old firmware, nothing we can do there).
 Notification: If /sys/bus/scsi/devices/hostN/scsi_host/hostN/host_fw_version
-	is older than "V1.46 2009-01-06", notify the user (maybe its better to grep
-	the smartctl output for that on port 0?). NOT IMPLEMENTED YET.
- */
+	is older than "V1.46 2009-01-06", (1.51 for enclosure-having cards) notify the user
+	(maybe its better to grep the smartctl output for that on port 0?). NOT IMPLEMENTED YET.
+
*/ inline std::string detect_drives_linux_areca(std::vector& drives, ExecutorFactoryRefPtr ex_factory) { debug_out_info("app", DBG_FUNC_MSG << "Detecting drives behind Areca controller(s)...\n"); @@ -772,10 +748,11 @@ inline std::string detect_drives_linux_areca(std::vector& d return error_msg; } - std::set controller_hosts; + std::map controller_hosts; // controller # -> has_enclosure for (std::size_t i = 0; i < vendors_models.size(); ++i) { - if (!app_pcre_match("/Vendor: Areca /i", vendors_models[i].second)) { + std::string vendor_line = vendors_models[i].second; + if (!app_pcre_match("/Vendor: Areca /i", vendor_line)) { continue; // not a supported controller } int host_num = vendors_models[i].first; @@ -786,7 +763,14 @@ inline std::string detect_drives_linux_areca(std::vector& d debug_out_dump("app", "Skipping adapter with SCSI host " << host_num << ", host already found.\n"); continue; } - controller_hosts.insert(host_num); + + // Check if it contains the "ix" string in its model name. It may not be exactly + // a suffix (there may be "-VOL#00" or something like that appended as well). + bool has_enclosure = app_pcre_match("/Model:.+ix.+Rev:/i", vendor_line); + debug_out_dump("app", DBG_FUNC_MSG << "Areca controller (SCSI host " << host_num + << ") seems to " << (has_enclosure ? "have enclosure(s)" : "have no enclosures") << ".\n"); + + controller_hosts[host_num] = has_enclosure; } if (controller_hosts.empty()) { @@ -802,8 +786,9 @@ inline std::string detect_drives_linux_areca(std::vector& d hz::intrusive_ptr smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl); - for (std::set::iterator iter = controller_hosts.begin(); iter != controller_hosts.end(); ++iter) { - const int host_num = *iter; + for (std::map::iterator iter = controller_hosts.begin(); iter != controller_hosts.end(); ++iter) { + const int host_num = iter->first; + const bool has_enclosure = iter->second; for (std::size_t sg_num = 0; sg_num < sg_entries.size(); ++sg_num) { if (sg_entries[sg_num].size() < 5) { @@ -816,29 +801,53 @@ inline std::string detect_drives_linux_areca(std::vector& d continue; } - int max_ports = 0; + if (has_enclosure) { + // TODO We have no information on what "/sys/bus/scsi/devices/host%d/scsi_host/host%d/host_fw_hd_channels" + // contains in case of enclosure-having cards. + + int max_enclosures = rconfig::get_data("system/linux_areca_enc_max_enclosure"); + max_enclosures = std::max(1, std::min(8, max_enclosures)); // 1-8 + + int max_ports = rconfig::get_data("system/linux_areca_enc_max_scan_port"); + max_ports = std::max(1, std::min(128, max_ports)); // 1-128 sanity check + + std::string dev = std::string("/dev/sg") + hz::number_to_string(sg_num); + + debug_out_dump("app", "Starting brute-force port/enclosure scan on 1-" << max_ports << " ports, 1-" << max_enclosures << " enclosures, device \"" << dev + << "\". Change the maximums by setting \"system/linux_areca_enc_max_scan_port\" and \"system/linux_areca_enc_max_enclosure\" config keys.\n"); + std::string last_output; + for (int enclosure_no = 1; enclosure_no < max_enclosures; ++enclosure_no) { + error_msg = smartctl_scan_drives_sequentially(dev, "areca,%d/" + hz::number_to_string(enclosure_no), 1, max_ports, drives, ex_factory, last_output); + } + debug_out_dump("app", "Brute-force port/enclosure scan finished.\n"); - std::string ports_path = hz::string_sprintf("/sys/bus/scsi/devices/host%d/scsi_host/host%d/host_fw_hd_channels", host_num, host_num); - hz::File ports_file(ports_path); - std::string ports_file_contents; - if (!read_proc_file(ports_file, ports_file_contents)) { - debug_out_warn("app", DBG_FUNC_MSG << "Couldn't read the number of ports on Areca controller (\"" << ports_path << "\"): " - << ports_file.get_error_utf8() << ", trying manually.\n"); } else { - hz::string_is_numeric(hz::string_trim_copy(ports_file_contents), max_ports); - debug_out_dump("app", DBG_FUNC_MSG << "Detected " << max_ports << " ports, through \"" << ports_path << "\".\n"); + int max_ports = 0; + + // Read the number of ports. + std::string ports_path = hz::string_sprintf("/sys/bus/scsi/devices/host%d/scsi_host/host%d/host_fw_hd_channels", host_num, host_num); + hz::File ports_file(ports_path); + std::string ports_file_contents; + if (!read_proc_file(ports_file, ports_file_contents)) { + debug_out_warn("app", DBG_FUNC_MSG << "Couldn't read the number of ports on Areca controller (\"" << ports_path << "\"): " + << ports_file.get_error_utf8() << ", trying manually.\n"); + } else { + hz::string_is_numeric(hz::string_trim_copy(ports_file_contents), max_ports); + debug_out_dump("app", DBG_FUNC_MSG << "Detected " << max_ports << " ports, through \"" << ports_path << "\".\n"); + } + if (max_ports == 0) { + max_ports = rconfig::get_data("system/linux_areca_noenc_max_scan_port"); + } + max_ports = std::max(1, std::min(24, max_ports)); // 1-24 sanity check + + std::string dev = std::string("/dev/sg") + hz::number_to_string(sg_num); + debug_out_dump("app", "Starting brute-force port scan on 1-" << max_ports << " ports, device \"" << dev + << "\". Change the maximum by setting \"system/linux_areca_noenc_max_scan_port\" config key.\n"); + std::string last_output; + error_msg = smartctl_scan_drives_sequentially(dev, "areca,%d", 1, max_ports, drives, ex_factory, last_output); + debug_out_dump("app", "Brute-force port scan finished.\n"); } - if (max_ports < 1 || max_ports > 24) { - max_ports = 24; // default to 24 ports (smartctl maximum for Areca) - } - - std::string dev = std::string("/dev/sg") + hz::number_to_string(sg_num); - - debug_out_dump("app", "Starting brute-force port scan on 1-" << max_ports << " ports, device \"" << dev << "\".\n"); - error_msg = smartctl_get_drives(dev, "areca,%d", 1, max_ports, drives, ex_factory); - debug_out_dump("app", "Brute-force port scan finished.\n"); - if (!error_msg.empty()) { debug_out_warn("app", DBG_FUNC_MSG << "Couldn't get the drives on ports of Areca controller: " << error_msg << "\n"); } diff --git a/gsmartcontrol/src/applib/storage_detector_win32.cpp b/gsmartcontrol/src/applib/storage_detector_win32.cpp index aec80a5..da76330 100644 --- a/gsmartcontrol/src/applib/storage_detector_win32.cpp +++ b/gsmartcontrol/src/applib/storage_detector_win32.cpp @@ -20,6 +20,7 @@ #include "hz/win32_tools.h" #include "hz/string_sprintf.h" +#include "hz/fs_path.h" #include "rconfig/rconfig_mini.h" #include "app_pcrecpp.h" #include "storage_detector_win32.h" @@ -163,6 +164,390 @@ std::string get_scan_open_multiport_devices(std::vector& dr + +/// Find and execute areca cli with specified options, return its output through \c output. +/// \return error message +inline std::string execute_areca_cli(ExecutorFactoryRefPtr ex_factory, const std::string& cli_binary, + const std::string& command_options, std::string& output) +{ + hz::intrusive_ptr executor = ex_factory->create_executor(ExecutorFactory::ExecutorArecaCli); + + executor->set_command(Glib::shell_quote(cli_binary), command_options); + + if (!executor->execute() || !executor->get_error_msg().empty()) { + debug_out_warn("app", DBG_FUNC_MSG << "Error while executing Areca cli binary.\n"); + } + + // any_to_unix is needed for windows + output = hz::string_trim_copy(hz::string_any_to_unix_copy(executor->get_stdout_str())); + if (output.empty()) { + debug_out_error("app", DBG_FUNC_MSG << "Areca cli returned an empty output.\n"); + return "Areca CLI returned an empty output."; + } + + return std::string(); +} + + + +/**
+Get the drives on Areca controller using Areca cli tool.
+Note that the drives are inserted in the order they are detected.
+
+There are 3 formats of "disk info" output:
+
+1. No expanders:
+------------------------------------------------------------
+  # Ch# ModelName                       Capacity  Usage
+===============================================================================
+  1  1  INTEL SSDSA2M160G2GC             160.0GB  System
+  2  2  INTEL SSDSA2M160G2GC             160.0GB  System
+  3  3  INTEL SSDSA2M160G2GC             160.0GB  System
+  4  4  INTEL SSDSA2M160G2GC             160.0GB  System
+  5  5  Hitachi HDS724040ALE640         4000.8GB  Storage
+  6  6  Hitachi HDS724040ALE640         4000.8GB  Storage
+  7  7  Hitachi HDS724040ALE640         4000.8GB  Storage
+  8  8  Hitachi HDS724040ALE640         4000.8GB  Storage
+  9  9  Hitachi HDS724040ALE640         4000.8GB  Storage
+ 10 10  Hitachi HDS724040ALE640         4000.8GB  Storage
+ 11 11  Hitachi HDS724040ALE640         4000.8GB  Backup
+ 12 12  Hitachi HDS724040ALE640         4000.8GB  Backup
+===============================================================================
+GuiErrMsg<0x00>: Success.
+------------------------------------------------------------
+
+2. No expanders (this output comes from CLI documentation, possibly an old format):
+------------------------------------------------------------
+ #   ModelName        Serial#          FirmRev     Capacity  State
+===============================================================================
+ 1   ST3250620NS      5QE1CP8S         3.AEE        250.1GB  RaidSet Member(1)
+ 2   ST3250620NS      5QE1CP8S         3.AEE        250.1GB  RaidSet Member(1)
+....(snip)....
+12   ST3250620NS      5QE1CP8S         3.AEE        250.1GB  RaidSet Member(1)
+===============================================================================
+GuiErrMsg<0x00>: Success.
+------------------------------------------------------------
+
+3. Expanders:
+------------------------------------------------------------
+  # Enc# Slot#   ModelName                        Capacity  Usage
+===============================================================================
+  1  01  Slot#1  N.A.                                0.0GB  N.A.
+  2  01  Slot#2  N.A.                                0.0GB  N.A.
+  3  01  Slot#3  N.A.                                0.0GB  N.A.
+  4  01  Slot#4  N.A.                                0.0GB  N.A.
+  5  01  Slot#5  N.A.                                0.0GB  N.A.
+  6  01  Slot#6  N.A.                                0.0GB  N.A.
+  7  01  Slot#7  N.A.                                0.0GB  N.A.
+  8  01  Slot#8  N.A.                                0.0GB  N.A.
+  9  02  SLOT 01 N.A.                                0.0GB  N.A.
+ 10  02  SLOT 02 N.A.                                0.0GB  N.A.
+ 11  02  SLOT 03 N.A.                                0.0GB  N.A.
+ 12  02  SLOT 04 N.A.                                0.0GB  N.A.
+ 13  02  SLOT 05 N.A.                                0.0GB  N.A.
+ 14  02  SLOT 06 N.A.                                0.0GB  N.A.
+ 15  02  SLOT 07 N.A.                                0.0GB  N.A.
+ 16  02  SLOT 08 N.A.                                0.0GB  N.A.
+ 17  02  SLOT 09 N.A.                                0.0GB  N.A.
+ 18  02  SLOT 10 N.A.                                0.0GB  N.A.
+ 19  02  SLOT 11 N.A.                                0.0GB  N.A.
+ 20  02  SLOT 12 N.A.                                0.0GB  N.A.
+ 21  02  SLOT 13 N.A.                                0.0GB  N.A.
+ 22  02  SLOT 14 ST910021AS                        100.0GB  Free
+ 23  02  SLOT 15 WDC WD3200BEVT-75A23T0            320.1GB  HotSpare[Global]
+ 24  02  SLOT 16 N.A.                                0.0GB  N.A.
+ 25  02  SLOT 17 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000
+ 26  02  SLOT 18 ST31500341AS                     1500.3GB  Raid Set # 000
+ 27  02  SLOT 19 ST3320620AS                       320.1GB  Raid Set # 000
+ 28  02  SLOT 20 ST31500341AS                     1500.3GB  Raid Set # 000
+ 29  02  SLOT 21 ST3500320AS                       500.1GB  Raid Set # 000
+ 30  02  SLOT 22 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000
+ 31  02  SLOT 23 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000
+ 32  02  SLOT 24 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000
+ 33  02  EXTP 01 N.A.                                0.0GB  N.A.
+ 34  02  EXTP 02 N.A.                                0.0GB  N.A.
+ 35  02  EXTP 03 N.A.                                0.0GB  N.A.
+ 36  02  EXTP 04 N.A.                                0.0GB  N.A.
+===============================================================================
+GuiErrMsg<0x00>: Success.
+------------------------------------------------------------
+
*/ +inline std::string areca_cli_get_drives(const std::string& cli_binary, const std::string& dev, int controller, + std::vector& drives, ExecutorFactoryRefPtr ex_factory) +{ + debug_out_info("app", "Getting available Areca drives (ports) for controller " << controller << " through Areca CLI...\n"); + + // TODO Support controller number. + // So far it seems the only way to pass the controller number to CLI is to use + // the interactive mode. + + std::string output; + std::string error = execute_areca_cli(ex_factory, cli_binary, "disk info", output); + if (!error.empty()) { + return error; + } + + // split to lines + std::vector lines; + hz::string_split(output, '\n', lines, true); + + enum FormatType { + FormatUnknown, + FormatNoEnc1, + FormatNoEnc2, + FormatEnc + }; + + pcrecpp::RE noenc1_header_re = app_pcre_re("/^\\s*#\\s+Ch#/mi"); + pcrecpp::RE noenc2_header_re = app_pcre_re("/^\\s*#\\s+ModelName/mi"); + pcrecpp::RE exp_header_re = app_pcre_re("/^\\s*#\\s+Enc#/mi"); + + FormatType format_type = FormatUnknown; + for (std::size_t i = 0; i < lines.size(); ++i) { + if (noenc1_header_re.PartialMatch(lines.at(i))) { + format_type = FormatNoEnc1; + break; + } else if (noenc2_header_re.PartialMatch(lines.at(i))) { + format_type = FormatNoEnc2; + break; + } else if (exp_header_re.PartialMatch(lines.at(i))) { + format_type = FormatEnc; + break; + } + } + if (format_type == FormatUnknown) { + debug_out_warn("app", "Could not read Areca CLI output: No valid header found.\n"); + return "Could not read Areca CLI output: No valid header found."; + } + + // Note: These may not match the full model, but just the first part is sufficient for comparison with "N.A.". + pcrecpp::RE noexp1_port_re = app_pcre_re("/^\\s*[0-9]+\\s+([0-9]+)\\s+([^\\s]+)/mi"); // matches port, model. + pcrecpp::RE noexp2_port_re = app_pcre_re("/^\\s*([0-9]+)\\s+([^\\s]+)/mi"); // matches port, model. + pcrecpp::RE exp_port_re = app_pcre_re("/^\\s*[0-9]+\\s+([0-9]+)\\s+(?:Slot#|SLOT\\s+)([0-9]+)\\s+([^\\s]+)/mi"); // matches enclosure, port, model. + + bool has_enclosure = (format_type == FormatEnc); + if (has_enclosure) { + debug_out_dump("app", "Areca controller seems to have enclosures.\n"); + } else { + debug_out_dump("app", "Areca controller doesn't have any enclosures.\n"); + } + + for (std::size_t i = 0; i < lines.size(); ++i) { + std::string port_str, model_str; + if (has_enclosure) { + std::string enclosure_str; + if (exp_port_re.PartialMatch(hz::string_trim_copy(lines.at(i)), &enclosure_str, &port_str, &model_str)) { + if (model_str != "N.A.") { + int port = -1, enclosure = -1; + hz::string_is_numeric(port_str, port); + hz::string_is_numeric(enclosure_str, enclosure); + drives.push_back(StorageDeviceRefPtr(new StorageDevice(dev, + "areca," + hz::number_to_string(port) + "/" + hz::number_to_string(enclosure)))); + debug_out_info("app", "Added Areca drive " << drives.back()->get_device_with_type() << ".\n"); + } + } + } else { // no enclosures + pcrecpp::RE port_re = (format_type == FormatNoEnc1 ? noexp1_port_re : noexp2_port_re); + if (port_re.PartialMatch(hz::string_trim_copy(lines.at(i)), &port_str, &model_str)) { + if (model_str != "N.A.") { + int port = -1; + hz::string_is_numeric(port_str, port); + drives.push_back(StorageDeviceRefPtr(new StorageDevice(dev, "areca," + hz::number_to_string(port)))); + debug_out_info("app", "Added Areca drive " << drives.back()->get_device_with_type() << ".\n"); + } + } + } + } + + return std::string(); +} + + + +/**
+Areca Windows:
+Call as:
+	- smartctl -i -d areca,N /dev/arcmsr0  (N is [1,24]).
+	- smartctl -i -d areca,N/E /dev/arcmsr0  (N is [1,128], E is [1,8]).
+The models that have "ix" (case-insensitive) in their model names
+have the enclosures and require the N,E syntax.
+Note: Using N/E syntax with non-enclosure cards seems to work
+regardless of the value of E.
+
+Detection:
+	Check the registry keys for Areca management software and
+	the CLI utility. One of them has to be installed.
+Check if CLI is installed. If it is, run it and parse the output. This should
+	give us the populated port list (non-enclosure), and the populated port
+	and expander list (enclosure models).
+	There are two problems with CLI:
+	If no controller is present, it crashes.
+		We try the following first:
+			smartctl -i -d areca,1 /dev/arcmsr0
+		If it contains "arcmsr0: No Areca controller found" (Windows), or
+			"Smartctl open device: /dev/arcmsr0 [areca_disk#01_enc#01] failed: No such device" (Linux),
+			then there's no controller there.
+	I don't see how it reports different controllers (there's only one list).
+		We ignore this for now.
+If CLI is not installed, do the brute-force way:
+	For arcmsr0, 1, ..., 8 (until we see "No Areca controller found"):
+		Scan -d areca,[1-24] /dev/arcmsrN
+		If at least one drive was found, it's a no-enclosure controller.
+		If no drives were found, it's probably an enclosure controller, try
+		-d areca,[1-128]/[1-8] /dev/arcmsrN
+			It's 2-3 drives a second on an empty port, so some limits are set in config.
+
*/ +inline std::string detect_drives_win32_areca(std::vector& drives, ExecutorFactoryRefPtr ex_factory) +{ + debug_out_info("app", DBG_FUNC_MSG << "Detecting drives behind Areca controller(s)...\n"); + + int scan_controllers = rconfig::get_data("system/win32_areca_scan_controllers"); + if (scan_controllers == 0) { // disabled + debug_out_info("app", "Areca controller scanning is disabled through config.\n"); + return std::string(); + } + + // Check if Areca tools are installed + + std::string cli_inst_path; + hz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\CLI", "InstallPath", cli_inst_path); + if (cli_inst_path.empty()) { + hz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\CLI", "InstallPath", cli_inst_path); + } + + if (scan_controllers == 2) { // auto + std::string http_inst_path; + hz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\archttp", "InstallPath", http_inst_path); + if (http_inst_path.empty()) { + hz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\archttp", "InstallPath", http_inst_path); + } + if (cli_inst_path.empty() && http_inst_path.empty()) { + debug_out_info("app", "No Areca software found. Install Areca CLI or set \"system/win32_areca_scan_controllers\" config key to 1 to force scanning.\n"); + return std::string(); + } + if (http_inst_path.empty()) { + debug_out_dump("app", "Areca HTTP installation not found.\n"); + } else { + debug_out_dump("app", "Areca HTTP installation found at: \"" << http_inst_path << "\".\n"); + } + } + if (cli_inst_path.empty()) { + debug_out_dump("app", "Areca CLI installation not found.\n"); + } else { + debug_out_dump("app", "Areca CLI installation found at: \"" << cli_inst_path << "\".\n"); + } + + bool cli_found = !cli_inst_path.empty(); + + int use_cli = rconfig::get_data("system/win32_areca_use_cli"); + bool scan_detect = (use_cli != 1); // Whether to detect using sequential port scanning. Only do that if CLI is not forced. + if (use_cli == 2) { // auto + use_cli = cli_found; + } + + std::string cli_binary; + if (use_cli == 1) { + cli_binary = rconfig::get_data("system/areca_cli_binary"); + if (!hz::FsPath(cli_binary).is_absolute() && !cli_inst_path.empty()) { + cli_binary = hz::FsPath(cli_inst_path).append(cli_binary).str(); + } + if (hz::FsPath(cli_binary).is_absolute() && !hz::FsPath(cli_binary).exists()) { + use_cli = 0; + debug_out_dump("app", "Areca CLI binary \"" << cli_binary << "\" not found.\n"); + } + } + + hz::intrusive_ptr smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl); + + // --- CLI mode + + // Since CLI may segfault if there are no drives, test the controller presence first. + // It doesn't matter if we use areca,N or areca,N/E - we will still get a different + // error if there's no controller. + if (use_cli) { + debug_out_dump("app", "Testing Areca controller presence using smartctl...\n"); + + StorageDeviceRefPtr drive(new StorageDevice("/dev/arcmsr0", "areca,1")); + std::string error_msg = drive->fetch_basic_data_and_parse(smartctl_ex); + std::string output = drive->get_info_output(); + if (app_pcre_match("/No Areca controller found/mi", output) + || app_pcre_match("/Smartctl open device: .* failed: No such device/mi", output) ) { + use_cli = 0; + scan_detect = false; + debug_out_dump("app", "Areca controller not found.\n"); + } + } + + if (use_cli) { + debug_out_info("app", "Scanning Areca drives using CLI...\n"); + int cli_max_controllers = 1; // TODO controller # with CLI. + for (int controller_no = 0; controller_no < cli_max_controllers; ++controller_no) { + std::string error_msg = areca_cli_get_drives(cli_binary, "/dev/arcmsr" + hz::number_to_string(controller_no), controller_no, drives, ex_factory); + // If we get an error on controller 0, fall back to no-cli detection. + if (!error_msg.empty() && controller_no == 0) { + use_cli = 0; + debug_out_warn("app", "Areca scan using CLI failed.\n"); + if (scan_detect) { + debug_out_dump("app", "Trying manual Areca scan because of a CLI error.\n"); + } + } + } + } + + // If CLI scanning failed (and it was not forced), or if there was no CLI, scan manually. + if (use_cli == 0 && scan_detect) { + debug_out_info("app", "Manually scanning Areca controllers and ports...\n"); + + int max_controllers = rconfig::get_data("system/win32_areca_max_controllers"); + int max_noenc_ports = rconfig::get_data("system/win32_areca_neonc_max_scan_port"); + max_noenc_ports = std::max(1, std::min(24, max_noenc_ports)); // 1-24 sanity check + int max_enc_ports = rconfig::get_data("system/win32_areca_enc_max_scan_port"); + max_enc_ports = std::max(1, std::min(128, max_enc_ports)); // 1-128 sanity check + int max_enclosures = rconfig::get_data("system/win32_areca_enc_max_enclosure"); + max_enclosures = std::max(1, std::min(8, max_enclosures)); // 1-8 sanity check + + for (int controller_no = 0; controller_no < max_controllers; ++controller_no) { + std::string dev = std::string("/dev/arcmsr") + hz::number_to_string(controller_no); + + // First, scan using the areca,N format. + debug_out_dump("app", "Starting brute-force port scan (no-enclosure) on 1-" << max_noenc_ports << " ports, device \"" << dev + << "\". Change the maximum by setting \"system/win32_areca_neonc_max_scan_port\" config key.\n"); + + std::size_t old_drive_count = drives.size(); + std::string last_output; + std::string error_msg = smartctl_scan_drives_sequentially(dev, "areca,%d", 1, max_noenc_ports, drives, ex_factory, last_output); + // If the scan stopped because of no controller, stop it all. + if (!error_msg.empty() && (app_pcre_match("/No Areca controller found/mi", last_output) + || app_pcre_match("/Smartctl open device: .* failed: No such device/mi", last_output)) ) { + debug_out_dump("app", "Areca controller " << controller_no << " not present, stopping sequential scan.\n"); + break; + } + + // If no drives were added (but the controller is present), it may be due to + // expander syntax requirement. Try that. + if (drives.size() == old_drive_count) { + for (int enclosure_no = 1; enclosure_no < max_enclosures; ++enclosure_no) { + debug_out_dump("app", "Starting brute-force port scan (enclosure #" << enclosure_no << ") on 1-" << max_enc_ports << " ports, device \"" << dev + << "\". Change the maximums by setting \"system/win32_areca_onc_max_scan_port\" and \"system/win32_areca_enc_max_enclosure\" config keys.\n"); + error_msg = smartctl_scan_drives_sequentially(dev, "areca,%d/" + hz::number_to_string(enclosure_no), 1, max_enc_ports, drives, ex_factory, last_output); + } + } + + debug_out_dump("app", "Brute-force port scan finished.\n"); + } + } + + debug_out_info("app", DBG_FUNC_MSG << "Finished scanning Areca controllers.\n"); + + return std::string(); +} + + + } @@ -174,31 +559,48 @@ std::string get_scan_open_multiport_devices(std::vector& dr // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx std::string detect_drives_win32(std::vector& drives, ExecutorFactoryRefPtr ex_factory) { + std::vector error_msgs; + std::string error_msg; + hz::intrusive_ptr smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl); // Fetch multiport devices using --scan-open. - std::set used_pds; - std::string error_msg = get_scan_open_multiport_devices(drives, ex_factory, used_pds); - bool multiport_found = !drives.empty(); - // Find out their serial numbers + std::set used_pds; + error_msg = get_scan_open_multiport_devices(drives, ex_factory, used_pds); + if (!error_msg.empty()) { + error_msgs.push_back(error_msg); + } + + bool multiport_found = !drives.empty(); + bool areca_open_found = false; // whether areca devices were found at --scan-open time. + + // Find out their serial numbers and whether there are Arecas there. std::set serials; for (std::size_t i = 0; i < drives.size(); ++i) { std::string local_error = drives.at(i)->fetch_basic_data_and_parse(smartctl_ex); if (!local_error.empty()) { - debug_out_info("app", "Smartctl returned with an error: " << error_msg << "\n"); + debug_out_info("app", "Smartctl returned with an error: " << local_error << "\n"); // Don't exit, just report it. } if (!drives.at(i)->get_serial_number().empty()) { // add model as well, who knows, there may be duplicates across vendors serials.insert(drives.at(i)->get_model_name() + "_" + drives.at(i)->get_serial_number()); } + + // See if there are any areca devices. This is not implemented yet by smartctl (as of 6.0), + // but if it ever is, we can skip our own detection below. + std::string type_arg = drives.at(i)->get_type_argument(); + if (type_arg.find("areca") != std::string::npos) { + areca_open_found = true; + } } + // Scan PhysicalDrive entries + debug_out_info("app", "Starting sequential scan of \\\\.\\PhysicalDriveN devices...\n"); - // Scan PhysicalDrive entries for (int drive_num = 0; ; ++drive_num) { // If the drive was already encountered in --scan-open (with a port number), skip it. @@ -233,7 +635,7 @@ std::string detect_drives_win32(std::vector& drives, Execut if (!serials.empty()) { std::string local_error = drive->fetch_basic_data_and_parse(smartctl_ex); if (!local_error.empty()) { - debug_out_info("app", "Smartctl returned with an error: " << error_msg << "\n"); + debug_out_info("app", "Smartctl returned with an error: " << local_error << "\n"); // Don't exit, just report it. } // A serial may be empty if "-q noserial" was given to smartctl. @@ -275,7 +677,14 @@ std::string detect_drives_win32(std::vector& drives, Execut } - return std::string(); + if (!areca_open_found) { + detect_drives_win32_areca(drives, ex_factory); + if (!error_msg.empty()) { + error_msgs.push_back(error_msg); + } + } + + return hz::string_join(error_msgs, "\n"); } diff --git a/gsmartcontrol/src/gsc_preferences_window.cpp b/gsmartcontrol/src/gsc_preferences_window.cpp index 7c03aaa..3279a73 100644 --- a/gsmartcontrol/src/gsc_preferences_window.cpp +++ b/gsmartcontrol/src/gsc_preferences_window.cpp @@ -250,7 +250,7 @@ GscPreferencesWindow::GscPreferencesWindow(BaseObjectType* gtkcobj, const app_ui Glib::ustring smartctl_binary_tooltip = "A path to smartctl binary. If the path is not absolute, the binary will be looked for in user's PATH."; #if defined CONFIG_KERNEL_FAMILY_WINDOWS - smartctl_binary_tooltip += "\n" + "Note: smartctl.exe shows a console during execution, while smartctl-nc.exe (default) doesn't (nc means no-console)."; + smartctl_binary_tooltip += Glib::ustring("\n") + "Note: smartctl.exe shows a console during execution, while smartctl-nc.exe (default) doesn't (nc means no-console)."; #endif if (Gtk::Label* smartctl_binary_label = lookup_widget("smartctl_binary_label")) { app_gtkmm_set_widget_tooltip(*smartctl_binary_label, smartctl_binary_tooltip); diff --git a/gsmartcontrol/src/gsc_settings.h b/gsmartcontrol/src/gsc_settings.h index 1ee14e5..9ff7bab 100644 --- a/gsmartcontrol/src/gsc_settings.h +++ b/gsmartcontrol/src/gsc_settings.h @@ -37,12 +37,19 @@ inline void init_default_settings() #else rconfig::set_default_data("system/smartctl_binary", "smartctl-nc.exe"); // use no-console version by default. rconfig::set_default_data("system/tw_cli_binary", "tw_cli.exe"); + rconfig::set_default_data("system/areca_cli_binary", "cli.exe"); // if relative, an installation path is prepended (if found). #endif // search for "smartctl-nc.exe" in smartmontools installation first. rconfig::set_default_data("system/win32_search_smartctl_in_smartmontools", true); rconfig::set_default_data("system/win32_smartmontools_regpath", "SOFTWARE\\smartmontools"); // in HKLM rconfig::set_default_data("system/win32_smartmontools_regkey", "Install_Dir"); rconfig::set_default_data("system/win32_smartmontools_smartctl_binary", "bin\\smartctl-nc.exe"); // relative to smt install path + rconfig::set_default_data("system/win32_areca_scan_controllers", int32_t(2)); // 0 - no, 1 - yes, 2 - auto (if areca tools are found) + rconfig::set_default_data("system/win32_areca_use_cli", int32_t(2)); // 0 - no, 1 - yes, 2 - auto (if it's found) + rconfig::set_default_data("system/win32_areca_max_controllers", int32_t(4)); // Maximum number of areca controllers (a safety measure). CLI supports 4. + rconfig::set_default_data("system/win32_areca_enc_max_scan_port", int32_t(36)); // 1-128 (areca with enclosures). The last RAID port to scan if no other method is available + rconfig::set_default_data("system/win32_areca_enc_max_enclosure", int32_t(3)); // 1-8 (areca with enclosures). The last RAID enclosure to scan if no other method is available + rconfig::set_default_data("system/win32_areca_neonc_max_scan_port", int32_t(24)); // 1-24 (areca without enclosures). The last RAID port to scan if no other method is available rconfig::set_default_data("system/smartctl_options", ""); // default options on ALL commands rconfig::set_default_data("system/smartctl_device_options", ""); // dev1:val1;dev2:val2;... format, each bin2ascii-encoded. @@ -52,7 +59,10 @@ inline void init_default_settings() rconfig::set_default_data("system/linux_proc_devices_path", "/proc/devices"); // file in linux /proc/devices format rconfig::set_default_data("system/linux_proc_scsi_scsi_path", "/proc/scsi/scsi"); // file in linux /proc/scsi/scsi format rconfig::set_default_data("system/linux_proc_scsi_sg_devices_path", "/proc/scsi/sg/devices"); // file in linux /proc/scsi/sg/devices format - rconfig::set_default_data("system/linux_max_scan_ports", int32_t(23)); // maximum number of RAID ports to scan if no other method is available + rconfig::set_default_data("system/linux_3ware_max_scan_port", int32_t(23)); // 0-127 (3ware). The last RAID port to scan if no other method is available + rconfig::set_default_data("system/linux_areca_enc_max_scan_port", int32_t(36)); // 1-128 (areca with enclosures). The last RAID port to scan if no other method is available + rconfig::set_default_data("system/linux_areca_enc_max_enclosure", int32_t(4)); // 1-8 (areca with enclosures). The last RAID enclosure to scan if no other method is available + rconfig::set_default_data("system/linux_areca_neonc_max_scan_port", int32_t(24)); // 1-24 (areca without enclosures). The last RAID port to scan if no other method is available rconfig::set_default_data("system/solaris_dev_path", "/dev/rdsk"); // path to /dev/rdsk for solaris. rconfig::set_default_data("system/unix_sdev_path", "/dev"); // path to /dev. used by other unices // rconfig::set_default_data("system/device_match_patterns", ""); // semicolon-separated PCRE patterns