From 3d2fe7fd50d42ebe890ea15266a2e2ed170e7fae Mon Sep 17 00:00:00 2001 From: Alexander Shaduri Date: Sat, 26 Aug 2017 16:47:11 +0000 Subject: [PATCH] Windows: Show drive letters for each device. --- gsmartcontrol/TODO | 7 -- .../src/applib/storage_detector_win32.cpp | 109 ++++++++++++++++-- gsmartcontrol/src/applib/storage_device.cpp | 25 ++++ gsmartcontrol/src/applib/storage_device.h | 14 ++- gsmartcontrol/src/gsc_info_window.cpp | 9 +- gsmartcontrol/src/gsc_main_window.cpp | 19 +-- gsmartcontrol/src/gsc_main_window_iconview.h | 3 + 7 files changed, 156 insertions(+), 30 deletions(-) diff --git a/gsmartcontrol/TODO b/gsmartcontrol/TODO index 2ee5545..681f117 100644 --- a/gsmartcontrol/TODO +++ b/gsmartcontrol/TODO @@ -30,13 +30,6 @@ TODO: Implement PolKit support. -Format large numbers with thousands separators (from locale) - includes LBAs and seek error rates. - -For each drive show its logical drives (C, D, ...) or mount points. - Probably use a tooltip for this, since that's a lot of info. - Maybe show this in Info window as well. - Don't rely on smartctl return code (2), parse the output instead. diff --git a/gsmartcontrol/src/applib/storage_detector_win32.cpp b/gsmartcontrol/src/applib/storage_detector_win32.cpp index e50f431..f0fb60a 100644 --- a/gsmartcontrol/src/applib/storage_detector_win32.cpp +++ b/gsmartcontrol/src/applib/storage_detector_win32.cpp @@ -17,6 +17,9 @@ #include // CreateFileA(), CloseHandle(), etc... #include #include +#include +#include +#include #include "hz/win32_tools.h" #include "hz/string_sprintf.h" @@ -88,12 +91,65 @@ smartctl --scan-open output for win32 with 3ware RAID: namespace { +/// Check which physical drives each drive letter (C, D, ...) spans across. +std::map > win32_get_drive_letter_map() +{ + std::map > drive_letter_map; + + std::bitset<32> drives(GetLogicalDrives()); + + // Check which drives are fixed + std::vector good_drives; + for (char c = 'A'; c <= 'Z'; ++c) { + if (drives[c - 'A']) { // drive is present + debug_out_dump("app", "Windows drive found: " << c << ".\n"); + if (GetDriveType((c + string(":\\")).c_str()) == DRIVE_FIXED) { + debug_out_dump("app", "Windows drive " << c << " is fixed.\n"); + good_drives.push_back(c); + } + } + } + + // Try to open each drive, check its disk extents + for (std::size_t i = 0; i < good_drives.size(); ++i) { + char drive = good_drives[i]; + string drive_str = string("\\\\.\\") + drive + ":"; + HANDLE h = CreateFileA( + drive_str.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, NULL); + if (h == INVALID_HANDLE_VALUE) { + debug_out_warn("app", "Windows drive " << drive << " cannot be opened.\n"); + continue; + } + DWORD bytesReturned = 0; + VOLUME_DISK_EXTENTS vde; + if (!DeviceIoControl( + h, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + NULL, 0, &vde, sizeof(vde), &bytesReturned, NULL)) { + debug_out_warn("app", "Windows drive " << drive << " is not mapped to any physical drives.\n"); + continue; + } + + std::set drive_numbers; + for (int i = 0; i < vde.NumberOfDiskExtents; ++i) { + drive_numbers.insert(vde.Extents[i].DiskNumber); + debug_out_dump("app", "Windows drive " << drive << " corresponds to physical drive " << vde.Extents[i].DiskNumber << ".\n"); + } + drive_letter_map[drive] = drive_numbers; + } + + return drive_letter_map; +} + + /// Run "smartctl --scan-open" and pick the devices which have /// a port parameter. We don't pick the others because the may /// conflict with pd* devices, and we like pd* better than sd*. std::string get_scan_open_multiport_devices(std::vector& drives, - ExecutorFactoryRefPtr ex_factory, std::set& equivalent_pds) + ExecutorFactoryRefPtr ex_factory, + const std::map >& drive_letter_map, + std::set& equivalent_pds) { debug_out_info("app", "Getting multi-port devices through smartctl --scan-open...\n"); @@ -148,14 +204,26 @@ std::string get_scan_open_multiport_devices(std::vector& dr for (std::size_t i = 0; i < lines.size(); ++i) { std::string dev, port_str, type; if (port_re.PartialMatch(hz::string_trim_copy(lines.at(i)), &dev, &port_str, &type)) { - std::string letter; - if (dev_re.PartialMatch(dev, &letter)) { + std::string sd_letter; + int drive_num = -1; + if (dev_re.PartialMatch(dev, &sd_letter)) { // don't use pd* devices equivalent to these sd* devices. - equivalent_pds.insert(letter.at(0) - 'a'); + drive_num = sd_letter.at(0) - 'a'; + equivalent_pds.insert(drive_num); } std::string full_dev = dev + "," + port_str; - drives.push_back(StorageDeviceRefPtr(new StorageDevice(full_dev, type))); + StorageDeviceRefPtr drive(new StorageDevice(full_dev, type)); + + std::vector drive_letters; + for (std::map >::const_iterator iter = drive_letter_map.begin(); iter != drive_letter_map.end(); ++iter) { + if (iter->second.count(drive_num) > 0) { + drive_letters.push_back(iter->first); + } + } + drive->set_drive_letters(drive_letters); + + drives.push_back(drive); } } @@ -562,12 +630,18 @@ std::string detect_drives_win32(std::vector& drives, Execut std::vector error_msgs; std::string error_msg; + // Construct drive letter map + debug_out_info("app", "Checking which drive corresponds to which \\\\.\\PhysicalDriveN device...\n"); + std::map > drive_letter_map = win32_get_drive_letter_map(); + + hz::intrusive_ptr smartctl_ex = ex_factory->create_executor(ExecutorFactory::ExecutorSmartctl); // Fetch multiport devices using --scan-open. + // Note that this may return duplicates (e.g. /dev/sda and /dev/csmi0,0) std::set used_pds; - error_msg = get_scan_open_multiport_devices(drives, ex_factory, used_pds); + error_msg = get_scan_open_multiport_devices(drives, ex_factory, drive_letter_map, used_pds); if (!error_msg.empty()) { error_msgs.push_back(error_msg); } @@ -576,7 +650,7 @@ std::string detect_drives_win32(std::vector& drives, Execut 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; + std::map 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()) { @@ -585,7 +659,8 @@ std::string detect_drives_win32(std::vector& drives, Execut } 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()); + std::string drive_serial_id = drives.at(i)->get_model_name() + "_" + drives.at(i)->get_serial_number(); + serials[drive_serial_id] = drives.at(i); } // See if there are any areca devices. This is not implemented yet by smartctl (as of 6.0), @@ -597,7 +672,7 @@ std::string detect_drives_win32(std::vector& drives, Execut } - // Scan PhysicalDrive entries + // Scan PhysicalDriveN entries debug_out_info("app", "Starting sequential scan of \\\\.\\PhysicalDriveN devices...\n"); @@ -636,6 +711,15 @@ std::string detect_drives_win32(std::vector& drives, Execut StorageDeviceRefPtr drive(new StorageDevice(hz::string_sprintf("pd%d", drive_num))); + std::vector letters; + for (std::map >::iterator iter = drive_letter_map.begin(); iter != drive_letter_map.end(); ++iter) { + if (iter->second.count(drive_num) > 0) { + letters.push_back(iter->first); + } + } + drive->set_drive_letters(letters); + debug_out_dump("app", "Drive letters for: " << drive->get_device() << ": " << drive->format_drive_letters() << ".\n"); + // Sometimes, a single physical drive may be accessible from both "/.//PhysicalDriveN" // and "/.//Scsi2" (e.g. pd0 and csmi2,1). Prefer the port-having ones (which is from --scan-open), // they contain more information. @@ -646,11 +730,14 @@ std::string detect_drives_win32(std::vector& drives, Execut debug_out_info("app", "Smartctl returned with an error: " << local_error << "\n"); // Don't exit, just report it. } + + std::string drive_serial_id = drive->get_model_name() + "_" + drive->get_serial_number(); // A serial may be empty if "-q noserial" was given to smartctl. - if (!drive->get_serial_number().empty() - && serials.count(drive->get_model_name() + "_" + drive->get_serial_number()) > 0) { + if (!drive->get_serial_number().empty() && serials.count(drive_serial_id) > 0) { debug_out_info("app", "Skipping drive due to duplicate S/N: model: \"" << drive->get_model_name() << "\", S/N: \"" << drive->get_serial_number() << "\".\n"); + // Copy the drive letters over to previously detected one (since we can't detect drive letters there). + serials[drive_serial_id]->set_drive_letters(drive->get_drive_letters()); continue; } } diff --git a/gsmartcontrol/src/applib/storage_device.cpp b/gsmartcontrol/src/applib/storage_device.cpp index 57cdb83..f1c64f0 100644 --- a/gsmartcontrol/src/applib/storage_device.cpp +++ b/gsmartcontrol/src/applib/storage_device.cpp @@ -654,6 +654,31 @@ std::string StorageDevice::get_extra_arguments() const +void StorageDevice::set_drive_letters(const std::vector< char >& letters) +{ + drive_letters_ = letters; +} + + + +const std::vector< char >& StorageDevice::get_drive_letters() const +{ + return drive_letters_; +} + + + +string StorageDevice::format_drive_letters() const +{ + std::vector drive_letters_decorated; + for (std::size_t i = 0; i < drive_letters_.size(); ++i) { + drive_letters_decorated.push_back(std::string() + (char)std::toupper(drive_letters_[i]) + ":"); + } + return hz::string_join(drive_letters_decorated, ", "); +} + + + bool StorageDevice::get_is_virtual() const { return is_virtual_; diff --git a/gsmartcontrol/src/applib/storage_device.h b/gsmartcontrol/src/applib/storage_device.h index 884fdc2..8bd142f 100644 --- a/gsmartcontrol/src/applib/storage_device.h +++ b/gsmartcontrol/src/applib/storage_device.h @@ -165,6 +165,16 @@ class StorageDevice : public hz::intrusive_ptr_referenced { std::string get_extra_arguments() const; + /// Set windows drive letters for this drive + void set_drive_letters(const std::vector& letters); + + /// Get windows drive letters for this drive + const std::vector& get_drive_letters() const; + + /// Get comma-separated win32 drive letters (if present) + std::string format_drive_letters() const; + + /// Get "virtual" status bool get_is_virtual() const; @@ -261,10 +271,12 @@ class StorageDevice : public hz::intrusive_ptr_referenced { std::string info_output_; ///< "smartctl --info" output std::string full_output_; ///< "smartctl --all" output - std::string device_; ///< e.g. /dev/sda. empty if virtual. + std::string device_; ///< e.g. /dev/sda or pd0. empty if virtual. std::string type_arg_; ///< Device type (for -d smartctl parameter), as specified when adding the device. std::string extra_args_; ///< Extra parameters for smartctl, as specified when adding the device. + std::vector drive_letters_; ///< Windows drive letters (if detected) + bool is_virtual_; ///< If true, then this is not a real device - merely a loaded description of it. std::string virtual_file_; ///< A file (smartctl data) the virtual device was loaded from bool is_manually_added_; ///< StorageDevice doesn't use it, but it's useful for its users. diff --git a/gsmartcontrol/src/gsc_info_window.cpp b/gsmartcontrol/src/gsc_info_window.cpp index b425ae3..58e5e73 100644 --- a/gsmartcontrol/src/gsc_info_window.cpp +++ b/gsmartcontrol/src/gsc_info_window.cpp @@ -371,14 +371,17 @@ void GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests { - std::string device = drive->get_device_with_type(); - std::string model = (drive->get_model_name().empty() ? "Unknown model" : drive->get_model_name()); + std::string device = Glib::Markup::escape_text(drive->get_device_with_type()); + std::string model = Glib::Markup::escape_text(drive->get_model_name().empty() ? "Unknown model" : drive->get_model_name()); + std::string drive_letters = Glib::Markup::escape_text(drive->format_drive_letters()); this->set_title("Device Information - " + device + ": " + model + " - GSmartControl"); // Gtk::Label* device_name_label = lookup_widget("device_name_label"); if (device_name_label) { - device_name_label->set_markup("Device: " + device + " Model: " + model); + device_name_label->set_markup( + "Device: " + device + (drive_letters.empty() ? "" : (" (" + drive_letters + ")")) + + " Model: " + model); } } diff --git a/gsmartcontrol/src/gsc_main_window.cpp b/gsmartcontrol/src/gsc_main_window.cpp index 5efaff3..e86184d 100644 --- a/gsmartcontrol/src/gsc_main_window.cpp +++ b/gsmartcontrol/src/gsc_main_window.cpp @@ -964,16 +964,19 @@ void GscMainWindow::update_status_widgets() return; } - std::string device = (drive->get_is_virtual() ? ("Virtual: " + drive->get_virtual_filename()) : drive->get_device_with_type()); - std::string size = drive->get_device_size_str(); - std::string model = (drive->get_model_name().empty() ? std::string("Unknown model") : drive->get_model_name()); - std::string family = (drive->get_family_name().empty() ? "Unknown" : drive->get_family_name()); - std::string family_fallback = (drive->get_family_name().empty() ? model : drive->get_family_name()); + std::string device = Glib::Markup::escape_text(drive->get_is_virtual() ? ("Virtual: " + drive->get_virtual_filename()) : drive->get_device_with_type()); + std::string size = Glib::Markup::escape_text(drive->get_device_size_str()); + std::string model = Glib::Markup::escape_text(drive->get_model_name().empty() ? std::string("Unknown model") : drive->get_model_name()); + std::string family = Glib::Markup::escape_text(drive->get_family_name().empty() ? "Unknown" : drive->get_family_name()); + std::string family_fallback = Glib::Markup::escape_text(drive->get_family_name().empty() ? model : drive->get_family_name()); + std::string drive_letters_str = Glib::Markup::escape_text(drive->format_drive_letters()); - - std::string info_str = device + (size.empty() ? "" : (", " + size)) + (model.empty() ? "" : (", " + model)); + std::string info_str = device + + (drive_letters_str.empty() ? "" : (" (" + drive_letters_str + ")")) + + (size.empty() ? "" : (", " + size)) + + (model.empty() ? "" : (", " + model)); if (name_label) { - name_label->set_text(info_str); + name_label->set_markup(info_str); app_gtkmm_set_widget_tooltip(*name_label, info_str, false); // in case it doesn't fit } diff --git a/gsmartcontrol/src/gsc_main_window_iconview.h b/gsmartcontrol/src/gsc_main_window_iconview.h index f318078..14e3eb8 100644 --- a/gsmartcontrol/src/gsc_main_window_iconview.h +++ b/gsmartcontrol/src/gsc_main_window_iconview.h @@ -263,6 +263,9 @@ class GscMainWindowIconView : public Gtk::IconView { } else { tooltip_strs.push_back("Device: " + Glib::Markup::escape_text(drive->get_device_with_type()) + ""); } + if (!drive->format_drive_letters().empty()) { + tooltip_strs.push_back("Drive letters: " + Glib::Markup::escape_text(drive->format_drive_letters()) + ""); + } if (!drive->get_serial_number().empty()) { tooltip_strs.push_back("Serial number: " + Glib::Markup::escape_text(drive->get_serial_number()) + ""); }