Implemented Linux support for Areca controllers with enclosures (untested).

Implemented Windows support for Areca controllers (thanks to Richard Kagerer);
untested so far.
This commit is contained in:
Alexander Shaduri
2013-02-24 20:02:30 +00:00
parent 462d465f3a
commit 6cc8770599
9 changed files with 684 additions and 87 deletions
+2 -2
View File
@@ -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)
@@ -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<TwCliExecutorGui> TwCliExecutorGuiRefPtr;
/// Executor for cli (Areca utility)
template<class ExecutorSync>
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<CmdexSync> ArecaCliExecutor;
/// A reference-counting pointer to ArecaCliExecutor
typedef hz::intrusive_ptr<ArecaCliExecutor> ArecaCliExecutorRefPtr;
/// tw_cli executor with GUI support
typedef ArecaCliExecutorGeneric<CmdexSyncGui> ArecaCliExecutorGui;
/// A reference-counting pointer to ArecaCliExecutorGui
typedef hz::intrusive_ptr<ArecaCliExecutorGui> ArecaCliExecutorGuiRefPtr;
#endif
+10 -1
View File
@@ -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<CmdexSync> 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);
+2 -1
View File
@@ -31,7 +31,8 @@ class ExecutorFactory : public hz::intrusive_ptr_referenced {
/// Executor type for create_executor()
enum Type {
ExecutorSmartctl,
ExecutorTwCli
ExecutorTwCli,
ExecutorArecaCli
};
@@ -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<StorageDeviceRefPtr>& 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<StorageDeviceRefPtr>& drives, ExecutorFactoryRefPtr ex_factory, std::string& last_output)
{
hz::intrusive_ptr<CmdexSync> 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();
}
@@ -18,6 +18,7 @@
#include <cstdio> // std::fgets(), std::FILE
#include <cerrno> // ENXIO
#include <set>
#include <map>
#include <vector>
#include <utility> // std::pair
@@ -328,40 +329,6 @@ inline std::string read_proc_scsi_sg_devices_file(std::vector< std::vector<int>
/// 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<StorageDeviceRefPtr>& drives, ExecutorFactoryRefPtr ex_factory)
{
hz::intrusive_ptr<CmdexSync> 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();
}
/**
<pre>
@@ -601,10 +568,12 @@ inline std::string detect_drives_linux_3ware(std::vector<StorageDeviceRefPtr>& 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<int32_t>("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<int32_t>("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<StorageDeviceRefPtr>&
/** </tt>
/** <pre>
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.
</tt> */
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.
</pre> */
inline std::string detect_drives_linux_areca(std::vector<StorageDeviceRefPtr>& 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<StorageDeviceRefPtr>& d
return error_msg;
}
std::set<int> controller_hosts;
std::map<int, bool> 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<StorageDeviceRefPtr>& 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<StorageDeviceRefPtr>& d
hz::intrusive_ptr<CmdexSync> smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl);
for (std::set<int>::iterator iter = controller_hosts.begin(); iter != controller_hosts.end(); ++iter) {
const int host_num = *iter;
for (std::map<int, bool>::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<StorageDeviceRefPtr>& 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<int32_t>("system/linux_areca_enc_max_enclosure");
max_enclosures = std::max(1, std::min(8, max_enclosures)); // 1-8
int max_ports = rconfig::get_data<int32_t>("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<int32_t>("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");
}
@@ -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<StorageDeviceRefPtr>& 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<CmdexSync> 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();
}
/** <pre>
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.
------------------------------------------------------------
</pre> */
inline std::string areca_cli_get_drives(const std::string& cli_binary, const std::string& dev, int controller,
std::vector<StorageDeviceRefPtr>& 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<std::string> 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();
}
/** <pre>
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.
</pre> */
inline std::string detect_drives_win32_areca(std::vector<StorageDeviceRefPtr>& drives, ExecutorFactoryRefPtr ex_factory)
{
debug_out_info("app", DBG_FUNC_MSG << "Detecting drives behind Areca controller(s)...\n");
int scan_controllers = rconfig::get_data<int32_t>("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<int32_t>("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<std::string>("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<CmdexSync> 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<int32_t>("system/win32_areca_max_controllers");
int max_noenc_ports = rconfig::get_data<int32_t>("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<int32_t>("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<int32_t>("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<StorageDeviceRefPtr>& dr
// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx
std::string detect_drives_win32(std::vector<StorageDeviceRefPtr>& drives, ExecutorFactoryRefPtr ex_factory)
{
std::vector<std::string> error_msgs;
std::string error_msg;
hz::intrusive_ptr<CmdexSync> smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl);
// Fetch multiport devices using --scan-open.
std::set<int> 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<int> 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<std::string> 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<StorageDeviceRefPtr>& 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<StorageDeviceRefPtr>& 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");
}
+1 -1
View File
@@ -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<Gtk::Label*>("smartctl_binary_label")) {
app_gtkmm_set_widget_tooltip(*smartctl_binary_label, smartctl_binary_tooltip);
+11 -1
View File
@@ -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