mirror of
https://github.com/ashaduri/gsmartcontrol.git
synced 2026-09-29 23:25:48 +00:00
862 lines
25 KiB
C++
862 lines
25 KiB
C++
/******************************************************************************
|
|
License: GNU General Public License v3.0 only
|
|
Copyright:
|
|
(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>
|
|
******************************************************************************/
|
|
/// \file
|
|
/// \author Alexander Shaduri
|
|
/// \ingroup applib
|
|
/// \weakgroup applib
|
|
/// @{
|
|
|
|
#include <unordered_map>
|
|
#include <glibmm.h> // compose()
|
|
#include <glibmm/i18n.h>
|
|
|
|
#include "rconfig/rconfig.h"
|
|
#include "hz/string_algo.h" // string_trim_copy, string_any_to_unix_copy
|
|
#include "hz/fs.h"
|
|
#include "hz/format_unit.h" // hz::format_date
|
|
|
|
#include "app_pcrecpp.h"
|
|
#include "storage_device.h"
|
|
#include "smartctl_parser.h"
|
|
#include "storage_settings.h"
|
|
#include "smartctl_executor.h"
|
|
|
|
|
|
|
|
|
|
std::string StorageDevice::get_type_storable_name(DetectedType type)
|
|
{
|
|
static const std::unordered_map<DetectedType, std::string> m {
|
|
{DetectedType::unknown, "unknown"},
|
|
{DetectedType::invalid, "invalid"},
|
|
{DetectedType::cddvd, "cd/dvd"},
|
|
{DetectedType::raid, "raid"},
|
|
};
|
|
if (auto iter = m.find(type); iter != m.end()) {
|
|
return iter->second;
|
|
}
|
|
return "[internal_error]";
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_status_displayable_name(Status status)
|
|
{
|
|
static const std::unordered_map<Status, std::string> m {
|
|
{Status::enabled, C_("status", "Enabled")},
|
|
{Status::disabled, C_("status", "Disabled")},
|
|
{Status::unsupported, C_("status", "Unsupported")},
|
|
{Status::unknown, C_("status", "Unknown")},
|
|
};
|
|
if (auto iter = m.find(status); iter != m.end()) {
|
|
return iter->second;
|
|
}
|
|
return "[internal_error]";
|
|
}
|
|
|
|
|
|
|
|
StorageDevice::StorageDevice(std::string dev_or_vfile, bool is_virtual)
|
|
{
|
|
is_virtual_ = is_virtual;
|
|
|
|
if (is_virtual) {
|
|
virtual_file_ = hz::fs::u8path(dev_or_vfile);
|
|
} else {
|
|
device_ = std::move(dev_or_vfile);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
StorageDevice::StorageDevice(std::string dev, std::string type_arg)
|
|
: device_(std::move(dev)), type_arg_(std::move(type_arg))
|
|
{ }
|
|
|
|
|
|
|
|
void StorageDevice::clear_fetched(bool including_outputs) {
|
|
if (including_outputs) {
|
|
info_output_.clear();
|
|
full_output_.clear();
|
|
}
|
|
|
|
parse_status_ = ParseStatus::none;
|
|
test_is_active_ = false; // not sure
|
|
|
|
smart_supported_.reset();
|
|
smart_enabled_.reset();
|
|
model_name_.reset();
|
|
aodc_status_.reset();
|
|
family_name_.reset();
|
|
size_.reset();
|
|
health_property_.reset();
|
|
|
|
properties_.clear();
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::fetch_basic_data_and_parse(const std::shared_ptr<CmdexSync>& smartctl_ex)
|
|
{
|
|
if (this->test_is_active_)
|
|
return _("A test is currently being performed on this drive.");
|
|
|
|
this->clear_fetched(); // clear everything fetched before, including outputs
|
|
|
|
// We don't use "--all" - it may cause really screwed up the output (tests, etc...).
|
|
// This looks just like "--info" only on non-smart devices.
|
|
std::string error_msg = execute_device_smartctl("--info --health --capabilities", smartctl_ex, this->info_output_, true); // set type to invalid if needed
|
|
|
|
// Smartctl 5.39 cvs/svn version defaults to usb type on at least linux and windows.
|
|
// This means that the old SCSI identify command isn't executed by default,
|
|
// and there is no information about the device manufacturer/etc... in the output.
|
|
// We detect this and set the device type to scsi to at least have _some_ info.
|
|
if (get_detected_type() == DetectedType::invalid && get_type_argument().empty()) {
|
|
debug_out_info("app", "The device seems to be of different type than auto-detected, trying again with scsi.\n");
|
|
this->set_type_argument("scsi");
|
|
return this->fetch_basic_data_and_parse(smartctl_ex); // try again with scsi
|
|
}
|
|
|
|
// Since the type error leads to "command line didn't parse" error here,
|
|
// we do this after the scsi stuff.
|
|
if (!error_msg.empty()) {
|
|
// Still try to parse something. For some reason, running smartctl on usb flash drive
|
|
// under winxp returns "command line didn't parse", while actually printing its name.
|
|
this->parse_basic_data(false, true);
|
|
return error_msg;
|
|
}
|
|
|
|
// Set some properties too - they are needed for e.g. AODC status, etc...
|
|
return this->parse_basic_data(true);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::parse_basic_data(bool do_set_properties, bool emit_signal)
|
|
{
|
|
this->clear_fetched(false); // clear everything fetched before, except outputs
|
|
|
|
if (this->info_output_.empty()) {
|
|
debug_out_error("app", DBG_FUNC_MSG << "String to parse is empty.\n");
|
|
return _("Cannot read information from an empty string.");
|
|
}
|
|
|
|
std::string version, version_full;
|
|
if (!SmartctlParser::parse_version(this->info_output_, version, version_full)) // is this smartctl data at all?
|
|
return _("Cannot get smartctl version information.");
|
|
|
|
// Detect type. note: we can't distinguish between sata and scsi (on linux, for -d ata switch).
|
|
// Sample output line 1 (encountered on a CDRW drive):
|
|
// SMART support is: Unavailable - Packet Interface Devices [this device: CD/DVD] don't support ATA SMART
|
|
// Sample output line 2 (encountered on a BDRW drive):
|
|
// Device type: CD/DVD
|
|
if (app_pcre_match("/this device: CD\\/DVD/mi", info_output_) || app_pcre_match("/^Device type:\\s+CD\\/DVD/mi", info_output_)) {
|
|
debug_out_dump("app", "Drive " << get_device_with_type() << " seems to be a CD/DVD device.\n");
|
|
this->set_detected_type(DetectedType::cddvd);
|
|
|
|
// This was encountered on a csmi soft-raid under windows with pd0.
|
|
// The device reported that it had smart supported and enabled.
|
|
// Product: Raid 5 Volume
|
|
} else if (app_pcre_match("/Product:[ \\t]*Raid/mi", info_output_)) {
|
|
debug_out_dump("app", "Drive " << get_device_with_type() << " seems to be a RAID volume/controller.\n");
|
|
this->set_detected_type(DetectedType::raid);
|
|
}
|
|
|
|
// RAID volume may report that it has SMART, but it obviously doesn't.
|
|
if (get_detected_type() == DetectedType::raid) {
|
|
smart_supported_ = false;
|
|
smart_enabled_ = false;
|
|
|
|
} else {
|
|
// Note: We don't use SmartctlParser here, because this information
|
|
// may be in some other format. If this information is valid, only then it's
|
|
// passed to SmartctlParser.
|
|
// Compared to SmartctlParser, this one is much looser.
|
|
|
|
// Don't put complete messages here - they change across smartctl versions.
|
|
if (app_pcre_match("/^SMART support is:[ \\t]*Unavailable/mi", info_output_) // cdroms output this
|
|
|| app_pcre_match("/Device does not support SMART/mi", info_output_) // usb flash drives, non-smart hds
|
|
|| app_pcre_match("/Device Read Identity Failed/mi", info_output_)) { // solaris scsi, unsupported by smartctl (maybe others?)
|
|
smart_supported_ = false;
|
|
smart_enabled_ = false;
|
|
|
|
} else if (app_pcre_match("/^SMART support is:[ \\t]*Available/mi", info_output_)
|
|
|| app_pcre_match("/^SMART support is:[ \\t]*Ambiguous/mi", info_output_)) {
|
|
smart_supported_ = true;
|
|
|
|
if (app_pcre_match("/^SMART support is:[ \\t]*Enabled/mi", info_output_)) {
|
|
smart_enabled_ = true;
|
|
} else if (app_pcre_match("/^SMART support is:[ \\t]*Disabled/mi", info_output_)) {
|
|
smart_enabled_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string model;
|
|
if (app_pcre_match("/^Device Model:[ \\t]*(.*)$/mi", info_output_, &model)) { // HD's and cdroms
|
|
model_name_ = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(model), ' ');
|
|
|
|
} else if (app_pcre_match("/^(?:Device|Product):[ \\t]*(.*)$/mi", info_output_, &model)) { // usb flash drives
|
|
model_name_ = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(model), ' ');
|
|
}
|
|
|
|
|
|
std::string family; // this is from smartctl's database
|
|
if (app_pcre_match("/^Model Family:[ \\t]*(.*)$/mi", info_output_, &family)) {
|
|
family_name_ = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(family), ' ');
|
|
}
|
|
|
|
std::string serial;
|
|
if (app_pcre_match("/^Serial Number:[ \\t]*(.*)$/mi", info_output_, &serial)) {
|
|
serial_number_ = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(serial), ' ');
|
|
}
|
|
|
|
std::string rpm_str;
|
|
if (app_pcre_match("/^Rotation Rate:[ \\t]*(.*)$/mi", info_output_, &rpm_str)) {
|
|
int rpm = hz::string_to_number_nolocale<int>(rpm_str, false);
|
|
hdd_ = rpm > 0;
|
|
}
|
|
|
|
|
|
// Note: this property is present since 5.33.
|
|
std::string size;
|
|
if (app_pcre_match("/^User Capacity:[ \\t]*(.*)$/mi", info_output_, &size)) {
|
|
int64_t bytes = 0;
|
|
size_ = SmartctlParser::parse_byte_size(size, bytes, false);
|
|
}
|
|
|
|
|
|
// Try to parse the properties. ignore its errors - we already got what we came for.
|
|
// 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) {
|
|
StorageAttribute::DiskType disk_type = StorageAttribute::DiskType::Any;
|
|
if (hdd_.has_value()) {
|
|
disk_type = hdd_.value() ? StorageAttribute::DiskType::Hdd : StorageAttribute::DiskType::Ssd;
|
|
}
|
|
SmartctlParser ps;
|
|
if (ps.parse_full(this->info_output_, disk_type)) { // try to parse it
|
|
this->set_properties(ps.get_properties()); // copy to our drive, overwriting old data
|
|
}
|
|
}
|
|
|
|
// A model field (and its aliases) is a good indication whether there was any data or not
|
|
set_parse_status(model_name_.has_value() ? ParseStatus::info : ParseStatus::none);
|
|
|
|
if (emit_signal)
|
|
signal_changed.emit(this); // notify listeners
|
|
|
|
return std::string();
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::fetch_data_and_parse(const std::shared_ptr<CmdexSync>& smartctl_ex)
|
|
{
|
|
if (this->test_is_active_)
|
|
return _("A test is currently being performed on this drive.");
|
|
|
|
this->clear_fetched(); // clear everything fetched before, including outputs
|
|
|
|
std::string output;
|
|
std::string error_msg;
|
|
|
|
// instead of -x, we use all the individual options -x encompasses, so that
|
|
// an addition to default -x output won't affect us.
|
|
if (this->get_type_argument() == "scsi") { // not sure about correctness... FIXME probably fails with RAID/scsi
|
|
// This doesn't do much yet, but just in case...
|
|
// SCSI equivalent of -x:
|
|
error_msg = execute_device_smartctl("--health --info --attributes --log=error --log=selftest --log=background --log=sasphy", smartctl_ex, output);
|
|
} else {
|
|
// ATA equivalent of -x:
|
|
error_msg = execute_device_smartctl("--health --info --get=all --capabilities --attributes --format=brief --log=xerror,50,error --log=xselftest,50,selftest --log=selective --log=directory --log=scttemp --log=scterc --log=devstat --log=sataphy",
|
|
smartctl_ex, output, true); // set type to invalid if needed
|
|
}
|
|
// See notes above (in fetch_basic_data_and_parse()).
|
|
if (get_detected_type() == DetectedType::invalid && get_type_argument().empty()) {
|
|
debug_out_info("app", "The device seems to be of different type than auto-detected, trying again with scsi.\n");
|
|
this->set_type_argument("scsi");
|
|
return this->fetch_data_and_parse(smartctl_ex); // try again with scsi
|
|
}
|
|
|
|
// Since the type error leads to "command line didn't parse" error here,
|
|
// we do this after the scsi stuff.
|
|
if (!error_msg.empty())
|
|
return error_msg;
|
|
|
|
this->full_output_ = output;
|
|
return this->parse_data();
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::parse_data()
|
|
{
|
|
this->clear_fetched(false); // clear everything fetched before, except outputs
|
|
|
|
StorageAttribute::DiskType disk_type = StorageAttribute::DiskType::Any;
|
|
if (hdd_.has_value()) {
|
|
disk_type = hdd_.value() ? StorageAttribute::DiskType::Hdd : StorageAttribute::DiskType::Ssd;
|
|
}
|
|
SmartctlParser ps;
|
|
if (ps.parse_full(this->full_output_, disk_type)) { // try to parse it (parse only, set the properties after basic parsing).
|
|
|
|
// refresh basic info too
|
|
this->info_output_ = ps.get_data_full(); // put data including version information
|
|
|
|
// note: this will clear the non-basic properties!
|
|
// this will parse some info that is already parsed by SmartctlParser::parse_full(),
|
|
// but this one sets the StorageDevice class members, not properties.
|
|
this->parse_basic_data(false, false); // don't emit signal, we're not complete yet.
|
|
|
|
// Call this after parse_basic_data(), since it sets parse status to "info".
|
|
this->set_parse_status(StorageDevice::ParseStatus::full);
|
|
|
|
// set the full properties
|
|
this->set_properties(ps.get_properties()); // copy to our drive, overwriting old data
|
|
|
|
signal_changed.emit(this); // notify listeners
|
|
|
|
return std::string();
|
|
}
|
|
|
|
// Don't show any GUI warnings on parse failure - it may just be an unsupported
|
|
// drive (e.g. usb flash disk). Plus, it may flood the string. The data will be
|
|
// parsed again in Info window, and we show the warnings there.
|
|
debug_out_warn("app", DBG_FUNC_MSG << "Cannot parse smartctl output.\n");
|
|
|
|
// proper parsing failed. try to at least extract info section
|
|
this->info_output_ = this->full_output_; // complete output here. sometimes it's only the info section
|
|
if (!this->parse_basic_data(true).empty()) { // will add some properties too. this will emit signal_changed.
|
|
return ps.get_error_msg(); // return full parser's error messages - they are more detailed.
|
|
}
|
|
|
|
return std::string(); // return ok if at least the info was ok.
|
|
}
|
|
|
|
|
|
|
|
StorageDevice::ParseStatus StorageDevice::get_parse_status() const
|
|
{
|
|
return parse_status_;
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::set_smart_enabled(bool b, const std::shared_ptr<CmdexSync>& smartctl_ex)
|
|
{
|
|
if (this->test_is_active_)
|
|
return _("A test is currently being performed on this drive.");
|
|
|
|
// execute smartctl --smart=on|off /dev/...
|
|
// --saveauto=on is also executed when enabling smart.
|
|
|
|
// Output:
|
|
/*
|
|
=== START OF ENABLE/DISABLE COMMANDS SECTION ===
|
|
SMART Enabled.
|
|
SMART Attribute Autosave Enabled.
|
|
--------------------------- OR ---------------------------
|
|
=== START OF ENABLE/DISABLE COMMANDS SECTION ===
|
|
SMART Disabled. Use option -s with argument 'on' to enable it.
|
|
--------------------------- OR ---------------------------
|
|
A mandatory SMART command failed: exiting. To continue, add one or more '-T permissive' options.
|
|
*/
|
|
|
|
std::string output;
|
|
std::string error_msg = execute_device_smartctl((b ? "--smart=on --saveauto=on" : "--smart=off"), smartctl_ex, output);
|
|
if (!error_msg.empty())
|
|
return error_msg;
|
|
|
|
// search at line start, because they are sometimes present in other sentences too.
|
|
if (app_pcre_match("/^SMART Enabled/mi", output) || app_pcre_match("/^SMART Disabled/mi", output)) {
|
|
return std::string(); // success
|
|
|
|
} else if (app_pcre_match("/^A mandatory SMART command failed/mi", output)) {
|
|
return _("Mandatory SMART command failed.");
|
|
}
|
|
|
|
return _("Unknown error occurred.");
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::set_aodc_enabled(bool b, const std::shared_ptr<CmdexSync>& smartctl_ex)
|
|
{
|
|
if (this->test_is_active_)
|
|
return _("A test is currently being performed on this drive.");
|
|
|
|
// execute smartctl --offlineauto=on|off /dev/...
|
|
// Output:
|
|
/*
|
|
=== START OF ENABLE/DISABLE COMMANDS SECTION ===
|
|
SMART Automatic Offline Testing Enabled every four hours.
|
|
--------------------------- OR ---------------------------
|
|
=== START OF ENABLE/DISABLE COMMANDS SECTION ===
|
|
SMART Automatic Offline Testing Disabled.
|
|
--------------------------- OR ---------------------------
|
|
A mandatory SMART command failed: exiting. To continue, add one or more '-T permissive' options.
|
|
*/
|
|
std::string output;
|
|
std::string error_msg = execute_device_smartctl((b ? "--offlineauto=on" : "--offlineauto=off"), smartctl_ex, output);
|
|
if (!error_msg.empty())
|
|
return error_msg;
|
|
|
|
if (app_pcre_match("/Testing Enabled/mi", output) || app_pcre_match("/Testing Disabled/mi", output)) {
|
|
return std::string(); // success
|
|
|
|
} else if (app_pcre_match("/^A mandatory SMART command failed/mi", output)) {
|
|
return _("Mandatory SMART command failed.");
|
|
}
|
|
|
|
return _("Unknown error occurred.");
|
|
}
|
|
|
|
|
|
|
|
StorageDevice::Status StorageDevice::get_smart_status() const
|
|
{
|
|
Status status = Status::unsupported;
|
|
if (smart_enabled_.has_value()) {
|
|
if (smart_enabled_.value()) { // enabled, supported
|
|
status = Status::enabled;
|
|
} else { // if it's disabled, maybe it's unsupported, check that:
|
|
if (smart_supported_.has_value()) {
|
|
if (smart_supported_.value()) { // disabled, supported
|
|
status = Status::disabled;
|
|
} else { // disabled, unsupported
|
|
status = Status::unsupported;
|
|
}
|
|
} else { // disabled, support unknown
|
|
status = Status::disabled;
|
|
}
|
|
}
|
|
} else { // status unknown
|
|
if (smart_supported_.has_value()) {
|
|
if (smart_supported_.value()) { // status unknown, supported
|
|
status = Status::disabled; // at least give the user a chance to try enabling it
|
|
} else { // status unknown, unsupported
|
|
status = Status::unsupported; // most likely
|
|
}
|
|
} else { // status unknown, support unknown
|
|
status = Status::unsupported;
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
StorageDevice::Status StorageDevice::get_aodc_status() const
|
|
{
|
|
// smart-disabled drives are known to print some garbage, so
|
|
// let's protect us from it.
|
|
if (get_smart_status() != Status::enabled)
|
|
return Status::unsupported;
|
|
|
|
if (aodc_status_.has_value()) // cached return value
|
|
return aodc_status_.value();
|
|
|
|
Status status = Status::unknown; // for now
|
|
|
|
bool aodc_supported = false;
|
|
int found = 0;
|
|
|
|
for (const auto& p : properties_) {
|
|
if (p.section == StorageProperty::Section::internal) {
|
|
if (p.generic_name == "aodc_enabled") { // if this is not present at all, we set the unknown status.
|
|
status = (p.get_value<bool>() ? Status::enabled : Status::disabled);
|
|
//++found;
|
|
continue;
|
|
}
|
|
if (p.generic_name == "aodc_support") {
|
|
aodc_supported = p.get_value<bool>();
|
|
++found;
|
|
continue;
|
|
}
|
|
if (found >= 2)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!aodc_supported)
|
|
status = Status::unsupported;
|
|
// if it's supported, then status may be enabled, disabled or unknown.
|
|
|
|
aodc_status_ = status; // store to cache
|
|
|
|
debug_out_info("app", DBG_FUNC_MSG << "AODC status: " << get_status_displayable_name(status) << "\n");
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_device_size_str() const
|
|
{
|
|
return (size_.has_value() ? size_.value() : "");
|
|
}
|
|
|
|
|
|
|
|
StorageProperty StorageDevice::get_health_property() const
|
|
{
|
|
if (health_property_.has_value()) // cached return value
|
|
return health_property_.value();
|
|
|
|
StorageProperty p = this->lookup_property("overall_health",
|
|
StorageProperty::Section::data, StorageProperty::SubSection::health);
|
|
if (!p.empty())
|
|
health_property_ = p; // store to cache
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_device() const
|
|
{
|
|
return device_;
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_device_base() const
|
|
{
|
|
if (is_virtual_)
|
|
return "";
|
|
|
|
std::string::size_type pos = device_.rfind('/'); // find basename
|
|
if (pos == std::string::npos)
|
|
return device_; // fall back
|
|
return device_.substr(pos+1, std::string::npos);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_device_with_type() const
|
|
{
|
|
if (this->get_is_virtual()) {
|
|
std::string vf = this->get_virtual_filename();
|
|
/// Translators: %1 is filename
|
|
std::string ret = Glib::ustring::compose(C_("filename", "Virtual (%1)"), (vf.empty() ? (std::string("[") + C_("filename", "empty") + "]") : vf));
|
|
return ret;
|
|
}
|
|
std::string device = get_device();
|
|
if (!get_type_argument().empty()) {
|
|
device = Glib::ustring::compose(_("%1 (%2)"), device, get_type_argument());
|
|
}
|
|
return device;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_detected_type(DetectedType t)
|
|
{
|
|
detected_type_ = t;
|
|
}
|
|
|
|
|
|
|
|
StorageDevice::DetectedType StorageDevice::get_detected_type() const
|
|
{
|
|
return detected_type_;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_type_argument(std::string arg)
|
|
{
|
|
type_arg_ = std::move(arg);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_type_argument() const
|
|
{
|
|
return type_arg_;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_extra_arguments(std::string args)
|
|
{
|
|
extra_args_ = std::move(args);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_extra_arguments() const
|
|
{
|
|
return extra_args_;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_drive_letters(std::map<char, std::string> letters)
|
|
{
|
|
drive_letters_ = std::move(letters);
|
|
}
|
|
|
|
|
|
|
|
const std::map<char, std::string>& StorageDevice::get_drive_letters() const
|
|
{
|
|
return drive_letters_;
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::format_drive_letters(bool with_volnames) const
|
|
{
|
|
std::vector<std::string> drive_letters_decorated;
|
|
for (const auto& iter : drive_letters_) {
|
|
drive_letters_decorated.push_back(std::string() + (char)std::toupper(iter.first) + ":");
|
|
if (with_volnames && !iter.second.empty()) {
|
|
// e.g. "C: (Local Drive)"
|
|
drive_letters_decorated.back() = Glib::ustring::compose(_("%1 (%2)"), drive_letters_decorated.back(), iter.second);
|
|
}
|
|
}
|
|
return hz::string_join(drive_letters_decorated, ", ");
|
|
}
|
|
|
|
|
|
|
|
bool StorageDevice::get_is_virtual() const
|
|
{
|
|
return is_virtual_;
|
|
}
|
|
|
|
|
|
|
|
hz::fs::path StorageDevice::get_virtual_file() const
|
|
{
|
|
return (is_virtual_ ? virtual_file_ : hz::fs::path());
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_virtual_filename() const
|
|
{
|
|
return (is_virtual_ ? virtual_file_.filename().u8string() : std::string());
|
|
}
|
|
|
|
|
|
|
|
const std::vector<StorageProperty>& StorageDevice::get_properties() const
|
|
{
|
|
return properties_;
|
|
}
|
|
|
|
|
|
|
|
StorageProperty StorageDevice::lookup_property(const std::string& generic_name, StorageProperty::Section section, StorageProperty::SubSection subsection) const
|
|
{
|
|
for (const auto& p : properties_) {
|
|
if (section != StorageProperty::Section::unknown && p.section != section)
|
|
continue;
|
|
if (subsection != StorageProperty::SubSection::unknown && p.subsection != subsection)
|
|
continue;
|
|
|
|
if (p.generic_name == generic_name)
|
|
return p;
|
|
}
|
|
return StorageProperty(); // check with .empty()
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_model_name() const
|
|
{
|
|
return (model_name_.has_value() ? model_name_.value() : "");
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_family_name() const
|
|
{
|
|
return (family_name_.has_value() ? family_name_.value() : "");
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_serial_number() const
|
|
{
|
|
return (serial_number_.has_value() ? serial_number_.value() : "");
|
|
}
|
|
|
|
|
|
|
|
bool StorageDevice::get_is_hdd() const
|
|
{
|
|
return hdd_.has_value() ? hdd_.value() : false;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_info_output(std::string s)
|
|
{
|
|
info_output_ = std::move(s);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_info_output() const
|
|
{
|
|
return info_output_;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_full_output(std::string s)
|
|
{
|
|
full_output_ = std::move(s);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_full_output() const
|
|
{
|
|
return full_output_;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_is_manually_added(bool b)
|
|
{
|
|
is_manually_added_ = b;
|
|
}
|
|
|
|
|
|
|
|
bool StorageDevice::get_is_manually_added() const
|
|
{
|
|
return is_manually_added_;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_test_is_active(bool b)
|
|
{
|
|
bool changed = (test_is_active_ != b);
|
|
test_is_active_ = b;
|
|
if (changed) {
|
|
signal_changed.emit(this); // so that everybody stops any test-aborting operations.
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool StorageDevice::get_test_is_active() const
|
|
{
|
|
return test_is_active_;
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_save_filename() const
|
|
{
|
|
std::string model = this->get_model_name(); // may be empty
|
|
std::string serial = this->get_serial_number();
|
|
std::string date = hz::format_date("%Y-%m-%d", true);
|
|
|
|
auto filename_format = rconfig::get_data<std::string>("gui/smartctl_output_filename_format");
|
|
hz::string_replace(filename_format, "{serial}", serial);
|
|
hz::string_replace(filename_format, "{model}", model);
|
|
hz::string_replace(filename_format, "{date}", date);
|
|
|
|
return hz::fs_filename_make_safe(filename_format);
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::get_device_options() const
|
|
{
|
|
if (is_virtual_) {
|
|
debug_out_warn("app", DBG_FUNC_MSG << "Cannot get device options of a virtual device.\n");
|
|
return std::string();
|
|
}
|
|
|
|
// If we have some special type or option, specify it on the command line (like "-d scsi").
|
|
// Note that the latter "-d" option overrides the former.
|
|
|
|
// lowest priority - the detected type
|
|
std::vector<std::string> args;
|
|
if (!get_type_argument().empty()) {
|
|
args.push_back("-d " + get_type_argument());
|
|
}
|
|
// extra args, as specified manually in CLI or when adding the drive
|
|
if (!get_extra_arguments().empty()) {
|
|
args.push_back(get_extra_arguments());
|
|
}
|
|
|
|
// config options, as specified in preferences.
|
|
std::string config_options = app_get_device_option(get_device(), get_type_argument());
|
|
if (!config_options.empty()) {
|
|
args.push_back(config_options);
|
|
}
|
|
|
|
return hz::string_join(args, " ");
|
|
}
|
|
|
|
|
|
|
|
std::string StorageDevice::execute_device_smartctl(const std::string& command_options,
|
|
const std::shared_ptr<CmdexSync>& smartctl_ex, std::string& smartctl_output, bool check_type)
|
|
{
|
|
// don't forbid running on currently tested drive - we need to call this from the test code.
|
|
|
|
if (is_virtual_) {
|
|
debug_out_warn("app", DBG_FUNC_MSG << "Cannot execute smartctl on a virtual device.\n");
|
|
return _("Cannot execute smartctl on a virtual device.");
|
|
}
|
|
|
|
std::string device = get_device();
|
|
|
|
std::string error_msg = execute_smartctl(device, this->get_device_options(),
|
|
command_options, smartctl_ex, smartctl_output);
|
|
|
|
if (!error_msg.empty()) {
|
|
debug_out_warn("app", DBG_FUNC_MSG << "Smartctl binary did not execute cleanly.\n");
|
|
|
|
// Smartctl 5.39 cvs/svn version defaults to usb type on at least linux and windows.
|
|
// This means that the old SCSI identify command isn't executed by default,
|
|
// and there is no information about the device manufacturer/etc... in the output.
|
|
// We detect this and set the device type to scsi to at least have _some_ info.
|
|
if (check_type && this->get_detected_type() == DetectedType::unknown
|
|
&& app_pcre_match("/specify device type with the -d option/mi", smartctl_output)) {
|
|
this->set_detected_type(DetectedType::invalid);
|
|
}
|
|
|
|
return error_msg;
|
|
}
|
|
|
|
return std::string();
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_parse_status(ParseStatus value)
|
|
{
|
|
parse_status_ = value;
|
|
}
|
|
|
|
|
|
|
|
void StorageDevice::set_properties(std::vector<StorageProperty> props)
|
|
{
|
|
properties_ = std::move(props);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// @}
|