diff --git a/gsmartcontrol/TODO b/gsmartcontrol/TODO index 780eb62..3921b5c 100644 --- a/gsmartcontrol/TODO +++ b/gsmartcontrol/TODO @@ -40,6 +40,8 @@ Add debian/ubuntu's su-to-root support to gsmartcontrol-root. Add support for SSD smartctl attribute names (they're totally different). Check smartctl sources, they print those names. + Use smartctl-returned names for searching by default. If it's "Unknown_Attribute" + or unknown name, then use our ID db. Testing: @@ -172,7 +174,7 @@ RAID: HKEY_USERS\.DEFAULT\Software\3ware\3DM2, InstallPath Another option for detection (whether it's 3ware) would be getting \\.\PhysicalDrive0 properties, like smartctl does. - Newer (post-5.39.1) smartctl supports --scan-open, which will give us: + Newer (> 5.39.1) smartctl supports --scan-open, which will give us: /dev/sda,0 -d ata (opened) /dev/sda,1 -d ata (opened) -d 3ware is not needed under Windows. We should treat sda as pd0 diff --git a/gsmartcontrol/src/applib/Makefile.am b/gsmartcontrol/src/applib/Makefile.am index bc8d19c..9560f7a 100644 --- a/gsmartcontrol/src/applib/Makefile.am +++ b/gsmartcontrol/src/applib/Makefile.am @@ -3,12 +3,14 @@ METASOURCES = AUTO noinst_LIBRARIES = libapplib.a libapplib_a_SOURCES = app_gtkmm_utils.cpp app_pango_utils.cpp cmdex.cpp \ cmdex_sync.cpp cmdex_sync_gui.cpp gui_utils.cpp selftest.cpp smartctl_executor.cpp \ - smartctl_parser.cpp storage_detector.cpp storage_device.cpp storage_property.cpp \ + smartctl_parser.cpp storage_detector.cpp storage_detector_linux.cpp storage_detector_other.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 gui_utils.h \ selftest.h smartctl_executor.h smartctl_executor_gui.h smartctl_parser.h \ - storage_detector.h storage_device.h storage_property.h storage_property_colors.h \ + storage_detector.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 wrapping_label.h # don't use absolute path for the current dir's .a, because the make diff --git a/gsmartcontrol/src/applib/storage_detector.cpp b/gsmartcontrol/src/applib/storage_detector.cpp index 0691c92..0b723cd 100644 --- a/gsmartcontrol/src/applib/storage_detector.cpp +++ b/gsmartcontrol/src/applib/storage_detector.cpp @@ -6,536 +6,15 @@ #include "hz/hz_config.h" // CONFIG_* -#include // std::boolalpha -#include // std::find -#include // std::fgets(), std::FILE -#include // ENXIO - -#if defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD - #include // getrawpartition() -#endif - -#if defined CONFIG_KERNEL_FAMILY_WINDOWS - #include // CreateFileA(), CloseHandle(), etc... -#endif - #include "hz/debug.h" -#include "hz/fs_dir.h" -#include "hz/fs_file.h" -#include "hz/string_sprintf.h" -#include "hz/local_algo.h" // hz::shell_sort() -#include "rconfig/rconfig_mini.h" #include "app_pcrecpp.h" #include "smartctl_executor.h" #include "storage_detector.h" - -namespace { - - - // Note: Since 3ware RAID controllers need additional flags on most (all?) OSes, - // and they may ignore the actual devices names, I have no idea how to support them. - - -#if defined CONFIG_KERNEL_LINUX - - - // Linux 2.6 with udev. Scan /dev/disk/by-id - the directory entries there - // are symlinks to respective /dev devices. Some devices have multiple - // links pointing to them, so unique filter is needed. - /* - Sample listing: - ------------------------------------------------------------ - # ls -1 /dev/disk/by-id - ata-ST31000340AS_9QJ0FFG7 - ata-ST31000340AS_9QJ0FFG7-part1 - ata-ST31000340AS_9QJ0FFG7-part2 - ata-ST3500630AS_9QG0R38D - ata-ST3500630AS_9QG0R38D-part1 - scsi-SATA_ST31000340AS_9QJ0FFG7 - scsi-SATA_ST31000340AS_9QJ0FFG7-part1 - scsi-SATA_ST31000340AS_9QJ0FFG7-part2 - scsi-SATA_ST3500630AS_9QG0R38D - scsi-SATA_ST3500630AS_9QG0R38D-part1 - */ - inline std::string detect_drives_linux_udev_byid(std::vector& devices) - { - debug_out_info("app", DBG_FUNC_MSG << "Detecting through device scan directory /dev/disk/by-id...\n"); - - std::string dev_dir; // this defaults to "/dev/disk/by-id" - if (!rconfig::get_data("system/linux_udev_byid_path", dev_dir) || dev_dir.empty()) { - debug_out_warn("app", DBG_FUNC_MSG << "Device scan directory path is not set.\n"); - return "Device scan directory path is not set."; - } - - hz::Dir dir(dev_dir); - - std::vector all_devices; - if (!dir.list(all_devices, false)) { // this outputs to debug too. - std::string error_msg = dir.get_error_utf8(); - if (!dir.exists()) { - debug_out_warn("app", DBG_FUNC_MSG << "Device scan directory doesn't exist.\n"); - } else { - debug_out_error("app", DBG_FUNC_MSG << "Cannot list directory entries.\n"); - } - return error_msg; - } - - std::vector blacklist; - blacklist.push_back("/-part[0-9]+$/"); - - // filter-out the ones with "partN" in them - for (unsigned int i = 0; i < all_devices.size(); ++i) { - std::string entry = all_devices[i]; - if (entry == "." || entry == "..") - continue; - - // platform blacklist - bool blacked = false; - for (std::vector::const_iterator iter = blacklist.begin(); iter != blacklist.end(); ++iter) { - if (app_pcre_match(*iter, entry)) { - blacked = true; - break; - } - } - if (blacked) - continue; - - hz::FsPath path = dev_dir + hz::DIR_SEPARATOR_S + all_devices[i]; - - // those are usually relative links, so find out where they are pointing to. - std::string link_dest; - if (path.get_link_destination(link_dest)) { - path = (hz::path_is_absolute(link_dest) ? link_dest : (dev_dir + hz::DIR_SEPARATOR_S + link_dest)); - path.compress(); // compress in case there are ../-s. - } - - // skip number-ended devices, in case we couldn't filter out the scan directory correctly - // (it's user-settable after all). - // if (app_pcre_match("/[0-9]+$/", path.str())) - // continue; - - if (std::find(devices.begin(), devices.end(), path.str()) == devices.end()) // there may be duplicates - devices.push_back(path.str()); - } - - return std::string(); - } - - - - // Procfs files don't support SEEK_END or ftello() (I think). Anyway, they can't - // be read through hz::File::get_contents, so use this function instead. - inline bool read_proc_partitions_file(hz::File& file, std::vector& lines) - { - if (!file.open("rb")) // closed automatically - return false; // the error message is in File itself. - - std::FILE* fp = file.get_handle(); - if (!fp) - return false; - - char line[256]; - while (std::fgets(line, static_cast(sizeof(line)), fp) != NULL) { - if (*line != '\0') - lines.push_back(line); - } - - return true; - } - - - - // Linux (tested with 2.4 and 2.6) /proc/partitions. Parses the file, appends /dev to each entry. - // Note that file format changed from 2.4 to 2.6 (some statistics fields moved to another file). - // No /proc/partitions on at least freebsd, solaris and osx, afaik. - /* - Sample 1 (2.4, devfs, with statistics): - ------------------------------------------------------------ - # cat /proc/partitions - major minor #blocks name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq - - 3 0 60051600 ide/host0/bus0/target0/lun0/disc 72159 136746 1668720 467190 383039 658435 8342136 1659840 -429 17038808 22703194 - 3 1 1638598 ide/host0/bus0/target0/lun0/part1 0 0 0 0 0 0 0 0 0 0 0 - 3 2 1028160 ide/host0/bus0/target0/lun0/part2 0 0 0 0 0 0 0 0 0 0 0 - 3 3 3092512 ide/host0/bus0/target0/lun0/part3 0 0 0 0 0 0 0 0 0 0 0 - 3 4 1 ide/host0/bus0/target0/lun0/part4 0 0 0 0 0 0 0 0 0 0 0 - 3 5 514048 ide/host0/bus0/target0/lun0/part5 1458 7877 74680 9070 2140 18285 166272 42490 0 10100 52000 - ------------------------------------------------------------ - Sample 2 (2.6): - ------------------------------------------------------------ - # cat /proc/partitions - major minor #blocks name - - 1 0 4096 ram0 - 8 0 156290904 sda - 8 1 39070048 sda1 - 8 2 7560 sda2 - 8 3 1 sda3 - ------------------------------------------------------------ - Sample 3 (2.6 on nokia n8xx, spaces may be missing): - ------------------------------------------------------------ - # cat /proc/partitions - major minor #blocks name - - 31 0 128 mtdblock0 - 31 1 384 mtdblock1 - 31 2 2048 mtdblock2 - 31 3 2048 mtdblock3 - 31 4 257536 mtdblock4 - 254 0 3932160 mmcblk0 - 254 1 3928064 mmcblk0p1 - 254 8 1966080 mmcblk1 - 254 9 2007032 mmcblk1p1 - */ - inline std::string detect_drives_linux_proc_partitions(std::vector& devices) - { - debug_out_info("app", DBG_FUNC_MSG << "Detecting through /proc/partitions...\n"); - - std::string parts_file; - if (!rconfig::get_data("system/linux_proc_partitions_path", parts_file) || parts_file.empty()) { - debug_out_warn("app", DBG_FUNC_MSG << "Partitions file path is not set.\n"); - return "Partitions file path is not set."; - } - - hz::File file(parts_file); -// std::string contents; - std::vector lines; -// if (!f.get_contents(contents)) { - if (!read_proc_partitions_file(file, lines)) { // this outputs to debug too - std::string error_msg = file.get_error_utf8(); // save before calling other file functions - if (!file.exists()) { - debug_out_warn("app", DBG_FUNC_MSG << "Partitions file doesn't exist.\n"); - } else { - debug_out_error("app", DBG_FUNC_MSG << "Partitions file exists but cannot be read.\n"); - } - return error_msg; - } - -// debug_out_dump("app", DBG_FUNC_MSG << "Dumping partitions file:\n" << contents << "\n"); - -// hz::string_split(contents, '\n', lines, true); - - std::vector blacklist; - // fixme: not sure about how partitions are visible with twa0. - blacklist.push_back("/d[a-z][0-9]+$/"); // sda1, hdb2 - partitions. twa0 and twe1 are drives, not partitions. - blacklist.push_back("/ram[0-9]+$/"); // ramdisks? - blacklist.push_back("/loop[0-9]*$/"); // not sure if loop devices go there, but anyway... - blacklist.push_back("/part[0-9]+$/"); // devfs had them - blacklist.push_back("/p[0-9]+$/"); // partitions are usually marked this way - blacklist.push_back("/md[0-9]*$/"); // linux software raid - blacklist.push_back("/dm-[0-9]*$/"); // linux device mapper - - - for (unsigned int i = 0; i < lines.size(); ++i) { - std::string line = hz::string_trim_copy(lines[i]); - if (line.empty() || app_pcre_match("/^major/", line)) // file header - continue; - - std::string dev; - if (!app_pcre_match("/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/", line, &dev) || dev.empty()) { - debug_out_warn("app", DBG_FUNC_MSG << "Cannot parse line \"" << line << "\".\n"); - continue; - } - - // platform blacklist - bool blacked = false; - for (std::vector::const_iterator iter = blacklist.begin(); iter != blacklist.end(); ++iter) { - if (app_pcre_match(*iter, dev)) { - blacked = true; - break; - } - } - if (blacked) - continue; - - std::string path = "/dev/" + dev; // let's just hope the it's /dev. - if (std::find(devices.begin(), devices.end(), path) == devices.end()) // there may be duplicates - devices.push_back(path); - } - - return std::string(); - } - - - -#elif defined CONFIG_KERNEL_FAMILY_WINDOWS - - - // smartctl accepts various variants, the most straight being pdN, - // (or /dev/pdN, /dev/ being optional) where N comes from - // "\\.\PhysicalDriveN" (winnt only). - // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx - - inline std::string detect_drives_win32(std::vector& devices) - { - for (int drive_num = 0; ; ++drive_num) { - std::string name = hz::string_sprintf("\\\\.\\PhysicalDrive%d", drive_num); - - // If the drive is openable, then it's there. - // NOTE: Administrative privileges are required to open it. - // Yes, CreateFile() is open, not create. Yes, it's silly (ah, win32...). - // We don't use any long/unopenable files here, so use the ANSI version. - HANDLE h = CreateFileA(name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, NULL); - - // The numbers seem to be consecutive, so break on first invalid. - if (h == INVALID_HANDLE_VALUE) - break; - - CloseHandle(h); - - devices.push_back(hz::string_sprintf("pd%d", drive_num)); - } - - return std::string(); - } - - - - -#else // FreeBSD, Solaris, etc... . - - - inline std::string detect_drives_dev(std::vector& devices) - { - debug_out_info("app", DBG_FUNC_MSG << "Detecting through /dev...\n"); - - std::string sdev_config_path; - #if defined CONFIG_KERNEL_SOLARIS - sdev_config_path = "system/solaris_dev_path"; - #else // other unixes - sdev_config_path = "system/unix_sdev_path"; - #endif - - std::string dev_dir; // defaults to /dev for freebsd, /dev/rdsk for solaris. - if (!rconfig::get_data(sdev_config_path, dev_dir) || dev_dir.empty()) { - debug_out_warn("app", DBG_FUNC_MSG << "Device directory path is not set.\n"); - return "Device directory path is not set."; - } - - hz::Dir dir(dev_dir); - - std::vector all_devices; - if (!dir.list(all_devices, false, hz::DirSortNone())) { // this outputs to debug too. - std::string error_msg = dir.get_error_utf8(); - if (!dir.exists()) { - debug_out_warn("app", DBG_FUNC_MSG << "Device directory doesn't exist.\n"); - } else { - debug_out_error("app", DBG_FUNC_MSG << "Cannot list directory entries.\n"); - } - return error_msg; - } - - - // platform whitelist - std::vector whitelist; - - - #if defined CONFIG_KERNEL_FREEBSD || defined CONFIG_KERNEL_DRAGONFLY - // FreeBSD example device names: - // * /dev/ad0 - ide disk 0. /dev/ad0a - non-slice dos partition 1. - // * /dev/da1s1e - scsi disk 1 (second disk), slice 1 (aka dos partition 1), - // bsd partition e (multiple bsd partitions may be inside one dos partition). - // * /dev/acd0 - first cdrom. /dev/acd0c - ?. - // Info: http://www.freebsd.org/doc/en/books/handbook/disks-naming.html - - // Of two machines I had access to, freebsd 6.3 had only real devices, - // while freebsd 4.10 had lots of dummy ones (ad1, ad2, ad3, da0, da1, da2, da3, sa0, ...). - - whitelist.push_back("/^ad[0-9]+$/"); // adN without suffix - fbsd ide - whitelist.push_back("/^da[0-9]+$/"); // daN without suffix - fbsd scsi, usb - // whitelist.push_back("/ ^sa[0-9]+$/"); // saN without suffix - fbsd scsi tape - // whitelist.push_back("/^ast[0-9]+$/"); // astN without suffix - fbsd ide tape - whitelist.push_back("/^aacd[0-9]+$/"); // fbsd adaptec raid - whitelist.push_back("/^mlxd[0-9]+$/"); // fbsd mylex raid - whitelist.push_back("/^mlyd[0-9]+$/"); // fbsd mylex raid - whitelist.push_back("/^amrd[0-9]+$/"); // fbsd AMI raid - whitelist.push_back("/^idad[0-9]+$/"); // fbsd compaq raid - whitelist.push_back("/^twed[0-9]+$/"); // fbsd 3ware raid - // these are checked by smartctl, but they are not mentioned in freebsd docs: - whitelist.push_back("/^tw[ae][0-9]+$/"); // fbsd 3ware raid - // unused: acd - ide cdrom, cd - scsi cdrom, mcd - mitsumi cdrom, scd - sony cdrom, - // fd - floppy, fla - diskonchip flash. - - - #elif defined CONFIG_KERNEL_SOLARIS - - // /dev/rdsk contains "raw" physical char devices, as opposed to - // "filesystem" block devices in /dev/dsk. smartctl needs rdsk. - - // x86: - - // /dev/rdsk/c0t0d0p0:1 - // Where c0 is the controller number. - // t0 is the target (SCSI ID number) (omit for ATAPI) - // d0 is always 0 for SCSI, the drive # for ATAPI - // p0 is the partition (p0 is the entire disk, or p1 - p4) - // :1 is the logical drive (c - z or 1 - 24) (that is, logical partitions inside extended partition) - - // /dev/rdsk/c0d0p2:1 - // where p2 means the extended partition (type 0x05) is partition 2 (out of 1-4) and - // ":1" is the 2nd extended partition (:0 would be the first extended partition). -- not sure about this! - - // p0 whole physical disk - // p1 - p4 Four primary fdisk partitions - // p5 - p30 26 logical drives in extended DOS partition (not implemented by Solaris) - // s0 - s15 16 slices in the Solaris FDISK partition (SPARC has only 8) - // On Solaris, by convention, s2 is the whole Solaris partition. - - // SPARC: There are no *p* devices on sparc afaik, only slices. By convention, - // s2 is used as a "whole" disk there (but not with EFI partitions!). - // So, c0d0s2 is the whole disk there. - // x86 has both c0d0s2 and c0d0p2, where s2 is a whole solaris partition. - - // NOTE: It seems that smartctl searches for s0 (aka root partition) in the end. I'm not sure - // what implications this has for x86, but we replicate this behaviour. - - // Note: Cdroms are ATAPI, so they pose as SCSI, as opposed to IDE hds. - - // TODO: No idea how to implement /dev/rmt (scsi tape devices), - // I have no files in that directory. - - whitelist.push_back("/^c[0-9]+(?:t[0-9]+)?d[0-9]+s0$/"); - - - - #elif defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD - - // OpenBSD / NetBSD have /dev/wdNc for IDE/ATA, /dev/sdNc for SCSI disk, - // /dev/stNc for scsi tape. N is [0-9]+. "c" means "whole disk" (not sure about - // different architectures though). There are no "sdN" devices, only "sdNP". - // Another manual says that wd0d would be a whole disk, while wd0c is its - // bsd part only (on x86) (only on netbsd?). Anyway, getrawpartition() gives - // us the letter we need. - // There is no additional level of names for BSD subpartitions (unlike freebsd). - // cd0a is cdrom. - // Dummy devices are present. - - // Note: This detection may take a while (probably due to open() check). - - char whole_part = 'a' + getrawpartition(); // from bsd's util.h - - whitelist.push_back(hz::string_sprintf("/^wd[0-9]+%c$/", whole_part)); - whitelist.push_back(hz::string_sprintf("/^sd[0-9]+%c$/", whole_part)); - whitelist.push_back(hz::string_sprintf("/^st[0-9]+%c$/", whole_part)); - - - - #elif defined CONFIG_KERNEL_DARWIN - - // Darwin has /dev/disk0, /dev/disk0s1, etc... - // Only real devices are present, so no need for additional checks. - - whitelist.push_back("/^disk[0-9]+$/"); - - - - #elif defined CONFIG_KERNEL_QNX - - // QNX has /dev/hd0, /dev/hd0t78 (partition?). - // Afaik, IDE and SCSI have the same prefix. fd for floppy, cd for cdrom. - // Not sure about the tapes. - // Only real devices are present, so no need for additional checks. - - whitelist.push_back("/^hd[0-9]+$/"); - - - #endif // unix platforms - - - std::vector matched_devices; - - for (unsigned int i = 0; i < all_devices.size(); ++i) { - std::string entry = all_devices[i]; - if (entry == "." || entry == "..") - continue; - - bool matched = false; - for (std::vector::const_iterator iter = whitelist.begin(); iter != whitelist.end(); ++iter) { - if (app_pcre_match(*iter, entry)) { - matched = true; - break; - } - } - if (!matched) - continue; - - hz::FsPath path = dev_dir + hz::DIR_SEPARATOR_S + all_devices[i]; - - // In case these are links, trace them to originals. - // We don't replace /dev files with real devices - it leads to really bad paths (pci ids for solaris, etc...). - std::string link_dest; - hz::FsPath real_dev_path; - if (path.get_link_destination(link_dest)) { - real_dev_path = (hz::path_is_absolute(link_dest) ? link_dest : (dev_dir + hz::DIR_SEPARATOR_S + link_dest)); - real_dev_path.compress(); // compress in case there are ../-s. - - // solaris has dangling links for non-existant devices, so filter them out. - if (!real_dev_path.exists()) - continue; - } - - matched_devices.push_back(path.str()); - } - - - // List ones who need dummy device filtering - #if defined CONFIG_KERNEL_FREEBSD || defined CONFIG_KERNEL_DRAGONFLY \ - || defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD - // Since we may have encountered dummy devices, we should check - // if they really exist. Unfortunately, this involves opening them, which - // is not good with some media (e.g. it may hang on audio cd (freebsd, maybe others), etc...). - // That's why we don't whitelist cd devices. Let's hope usb and others are - // ok with this. Dummy devices give errno ENXIO (6, Device not configured) on *bsd. - // In Linux ENXIO is (6, No such device or address). - - // Don't do this on solaris - we can't distinguish between cdroms and hds there. - - // If there are less than 4 devices, they are probably not dummy (newer freebsd). - bool open_needed = (matched_devices.size() >= 4); - if (open_needed) { - debug_out_info("app", DBG_FUNC_MSG << "Number of matched devices is " - << matched_devices.size() << ", will try to filter non-existent ones out.\n"); - } else { - debug_out_info("app", DBG_FUNC_MSG << "Number of matched devices is " - << matched_devices.size() << ", no need for filtering them out.\n"); - } - - for (unsigned int i = 0; i < matched_devices.size(); ++i) { - hz::File dev_file(matched_devices[i]); - - // we check errno because OS may deny us for some other reason on valid devices. - if (open_needed && !dev_file.open("rb") && dev_file.get_errno() == ENXIO) { - debug_out_dump("app", DBG_FUNC_MSG << "Device \"" << dev_file.str() << "\" failed to open, ignoring.\n"); - continue; - } - - if (open_needed) - debug_out_dump("app", DBG_FUNC_MSG << "Device \"" << dev_file.str() << "\" opened successfully, adding to device list.\n"); - - devices.push_back(dev_file.str()); - } - - #else // not *BSD - for (unsigned int i = 0; i < matched_devices.size(); ++i) { - devices.push_back(matched_devices[i]); - } - - #endif - - hz::shell_sort(devices.begin(), devices.end()); - - return std::string(); - } - - -#endif // platforms - - - -} // anon. ns +#include "storage_detector_linux.h" +#include "storage_detector_win32.h" +#include "storage_detector_other.h" @@ -586,7 +65,7 @@ std::string StorageDetector::detect(std::vector& drives) #else // freebsd, etc... if (!found) { - error_msg = detect_drives_dev(devices); // bsd, etc... . scans /dev. + error_msg = detect_drives_other(devices); // bsd, etc... . scans /dev. if (error_msg.empty() && !devices.empty()) { found = true; } diff --git a/gsmartcontrol/src/applib/storage_detector_linux.cpp b/gsmartcontrol/src/applib/storage_detector_linux.cpp new file mode 100644 index 0000000..c384355 --- /dev/null +++ b/gsmartcontrol/src/applib/storage_detector_linux.cpp @@ -0,0 +1,250 @@ +/************************************************************************** + Copyright: + (C) 2008 - 2011 Alexander Shaduri + License: See LICENSE_gsmartcontrol.txt +***************************************************************************/ + +#include "storage_detector_linux.h" + +#if defined CONFIG_KERNEL_LINUX + + +#include // std::find +#include // std::fgets(), std::FILE +#include // ENXIO + +#include "hz/debug.h" +#include "hz/fs_path_utils.h" +#include "hz/fs_file.h" +#include "rconfig/rconfig_mini.h" + +#include "app_pcrecpp.h" + + + + +// Linux 2.6 with udev. Scan /dev/disk/by-id - the directory entries there +// are symlinks to respective /dev devices. Some devices have multiple +// links pointing to them, so unique filter is needed. +/* +Sample listing: +------------------------------------------------------------ +# ls -1 /dev/disk/by-id +ata-ST31000340AS_9QJ0FFG7 +ata-ST31000340AS_9QJ0FFG7-part1 +ata-ST31000340AS_9QJ0FFG7-part2 +ata-ST3500630AS_9QG0R38D +ata-ST3500630AS_9QG0R38D-part1 +scsi-SATA_ST31000340AS_9QJ0FFG7 +scsi-SATA_ST31000340AS_9QJ0FFG7-part1 +scsi-SATA_ST31000340AS_9QJ0FFG7-part2 +scsi-SATA_ST3500630AS_9QG0R38D +scsi-SATA_ST3500630AS_9QG0R38D-part1 +*/ +/* +// We don't use udev anymore - not all distros have it, and e.g. Ubuntu +// has it all wrong (two symlinks (sda, sdb) pointing both to sdb). +std::string detect_drives_linux_udev_byid(std::vector& devices) +{ + debug_out_info("app", DBG_FUNC_MSG << "Detecting through device scan directory /dev/disk/by-id...\n"); + + std::string dev_dir; // this defaults to "/dev/disk/by-id" + if (!rconfig::get_data("system/linux_udev_byid_path", dev_dir) || dev_dir.empty()) { + debug_out_warn("app", DBG_FUNC_MSG << "Device scan directory path is not set.\n"); + return "Device scan directory path is not set."; + } + + hz::Dir dir(dev_dir); + + std::vector all_devices; + if (!dir.list(all_devices, false)) { // this outputs to debug too. + std::string error_msg = dir.get_error_utf8(); + if (!dir.exists()) { + debug_out_warn("app", DBG_FUNC_MSG << "Device scan directory doesn't exist.\n"); + } else { + debug_out_error("app", DBG_FUNC_MSG << "Cannot list directory entries.\n"); + } + return error_msg; + } + + std::vector blacklist; + blacklist.push_back("/-part[0-9]+$/"); + + // filter-out the ones with "partN" in them + for (unsigned int i = 0; i < all_devices.size(); ++i) { + std::string entry = all_devices[i]; + if (entry == "." || entry == "..") + continue; + + // platform blacklist + bool blacked = false; + for (std::vector::const_iterator iter = blacklist.begin(); iter != blacklist.end(); ++iter) { + if (app_pcre_match(*iter, entry)) { + blacked = true; + break; + } + } + if (blacked) + continue; + + hz::FsPath path = dev_dir + hz::DIR_SEPARATOR_S + all_devices[i]; + + // those are usually relative links, so find out where they are pointing to. + std::string link_dest; + if (path.get_link_destination(link_dest)) { + path = (hz::path_is_absolute(link_dest) ? link_dest : (dev_dir + hz::DIR_SEPARATOR_S + link_dest)); + path.compress(); // compress in case there are ../-s. + } + + // skip number-ended devices, in case we couldn't filter out the scan directory correctly + // (it's user-settable after all). +// if (app_pcre_match("/[0-9]+$/", path.str())) +// continue; + + if (std::find(devices.begin(), devices.end(), path.str()) == devices.end()) // there may be duplicates + devices.push_back(path.str()); + } + + return std::string(); +} +*/ + + + +// Procfs files don't support SEEK_END or ftello() (I think). Anyway, they can't +// be read through hz::File::get_contents, so use this function instead. +inline bool read_proc_partitions_file(hz::File& file, std::vector& lines) +{ + if (!file.open("rb")) // closed automatically + return false; // the error message is in File itself. + + std::FILE* fp = file.get_handle(); + if (!fp) + return false; + + char line[256]; + while (std::fgets(line, static_cast(sizeof(line)), fp) != NULL) { + if (*line != '\0') + lines.push_back(line); + } + + return true; +} + + + +// Linux (tested with 2.4 and 2.6) /proc/partitions. Parses the file, appends /dev to each entry. +// Note that file format changed from 2.4 to 2.6 (some statistics fields moved to another file). +// No /proc/partitions on at least freebsd, solaris and osx, afaik. +/* +Sample 1 (2.4, devfs, with statistics): +------------------------------------------------------------ +# cat /proc/partitions +major minor #blocks name rio rmerge rsect ruse wio wmerge wsect wuse running use aveq + +3 0 60051600 ide/host0/bus0/target0/lun0/disc 72159 136746 1668720 467190 383039 658435 8342136 1659840 -429 17038808 22703194 +3 1 1638598 ide/host0/bus0/target0/lun0/part1 0 0 0 0 0 0 0 0 0 0 0 +3 2 1028160 ide/host0/bus0/target0/lun0/part2 0 0 0 0 0 0 0 0 0 0 0 +3 3 3092512 ide/host0/bus0/target0/lun0/part3 0 0 0 0 0 0 0 0 0 0 0 +3 4 1 ide/host0/bus0/target0/lun0/part4 0 0 0 0 0 0 0 0 0 0 0 +3 5 514048 ide/host0/bus0/target0/lun0/part5 1458 7877 74680 9070 2140 18285 166272 42490 0 10100 52000 +------------------------------------------------------------ +Sample 2 (2.6): +------------------------------------------------------------ +# cat /proc/partitions +major minor #blocks name + + 1 0 4096 ram0 + 8 0 156290904 sda + 8 1 39070048 sda1 + 8 2 7560 sda2 + 8 3 1 sda3 +------------------------------------------------------------ +Sample 3 (2.6 on nokia n8xx, spaces may be missing): +------------------------------------------------------------ +# cat /proc/partitions +major minor #blocks name + +31 0 128 mtdblock0 +31 1 384 mtdblock1 +31 2 2048 mtdblock2 +31 3 2048 mtdblock3 +31 4 257536 mtdblock4 +254 0 3932160 mmcblk0 +254 1 3928064 mmcblk0p1 +254 8 1966080 mmcblk1 +254 9 2007032 mmcblk1p1 +*/ +std::string detect_drives_linux_proc_partitions(std::vector& devices) +{ + debug_out_info("app", DBG_FUNC_MSG << "Detecting through /proc/partitions...\n"); + + std::string parts_file; + if (!rconfig::get_data("system/linux_proc_partitions_path", parts_file) || parts_file.empty()) { + debug_out_warn("app", DBG_FUNC_MSG << "Partitions file path is not set.\n"); + return "Partitions file path is not set."; + } + + hz::File file(parts_file); +// std::string contents; + std::vector lines; +// if (!f.get_contents(contents)) { + if (!read_proc_partitions_file(file, lines)) { // this outputs to debug too + std::string error_msg = file.get_error_utf8(); // save before calling other file functions + if (!file.exists()) { + debug_out_warn("app", DBG_FUNC_MSG << "Partitions file doesn't exist.\n"); + } else { + debug_out_error("app", DBG_FUNC_MSG << "Partitions file exists but cannot be read.\n"); + } + return error_msg; + } + +// debug_out_dump("app", DBG_FUNC_MSG << "Dumping partitions file:\n" << contents << "\n"); + +// hz::string_split(contents, '\n', lines, true); + + std::vector blacklist; + // fixme: not sure about how partitions are visible with twa0. + blacklist.push_back("/d[a-z][0-9]+$/"); // sda1, hdb2 - partitions. twa0 and twe1 are drives, not partitions. + blacklist.push_back("/ram[0-9]+$/"); // ramdisks? + blacklist.push_back("/loop[0-9]*$/"); // not sure if loop devices go there, but anyway... + blacklist.push_back("/part[0-9]+$/"); // devfs had them + blacklist.push_back("/p[0-9]+$/"); // partitions are usually marked this way + blacklist.push_back("/md[0-9]*$/"); // linux software raid + blacklist.push_back("/dm-[0-9]*$/"); // linux device mapper + + + for (unsigned int i = 0; i < lines.size(); ++i) { + std::string line = hz::string_trim_copy(lines[i]); + if (line.empty() || app_pcre_match("/^major/", line)) // file header + continue; + + std::string dev; + if (!app_pcre_match("/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/", line, &dev) || dev.empty()) { + debug_out_warn("app", DBG_FUNC_MSG << "Cannot parse line \"" << line << "\".\n"); + continue; + } + + // platform blacklist + bool blacked = false; + for (std::vector::const_iterator iter = blacklist.begin(); iter != blacklist.end(); ++iter) { + if (app_pcre_match(*iter, dev)) { + blacked = true; + break; + } + } + if (blacked) + continue; + + std::string path = "/dev/" + dev; // let's just hope the it's /dev. + if (std::find(devices.begin(), devices.end(), path) == devices.end()) // there may be duplicates + devices.push_back(path); + } + + return std::string(); +} + + + + +#endif // CONFIG_KERNEL_LINUX diff --git a/gsmartcontrol/src/applib/storage_detector_linux.h b/gsmartcontrol/src/applib/storage_detector_linux.h new file mode 100644 index 0000000..c834939 --- /dev/null +++ b/gsmartcontrol/src/applib/storage_detector_linux.h @@ -0,0 +1,30 @@ +/************************************************************************** + Copyright: + (C) 2008 - 2011 Alexander Shaduri + License: See LICENSE_gsmartcontrol.txt +***************************************************************************/ + +#ifndef STORAGE_DETECTOR_LINUX_H +#define STORAGE_DETECTOR_LINUX_H + +#include +#include + +#include "hz/hz_config.h" // CONFIG_* + + +#if defined CONFIG_KERNEL_LINUX + + +/// Detect drives in linux using udev +// std::string detect_drives_linux_udev_byid(std::vector& devices); + + +/// Detect drives in linux using /proc/partitions +std::string detect_drives_linux_proc_partitions(std::vector& devices); + + +#endif + + +#endif diff --git a/gsmartcontrol/src/applib/storage_detector_other.cpp b/gsmartcontrol/src/applib/storage_detector_other.cpp new file mode 100644 index 0000000..5564675 --- /dev/null +++ b/gsmartcontrol/src/applib/storage_detector_other.cpp @@ -0,0 +1,263 @@ +/************************************************************************** + Copyright: + (C) 2008 - 2011 Alexander Shaduri + License: See LICENSE_gsmartcontrol.txt +***************************************************************************/ + +#include "storage_detector_win32.h" + +#if !defined CONFIG_KERNEL_LINUX && !defined CONFIG_KERNEL_FAMILY_WINDOWS + + +#if defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD + #include // getrawpartition() +#endif + +#include "hz/debug.h" +#include "hz/local_algo.h" // hz::shell_sort() +#include "hz/fs_path_utils.h" +#include "hz/fs_path.h" +#include "hz/fs_file.h" +#include "rconfig/rconfig_mini.h" +#include "app_pcrecpp.h" + + + + +std::string detect_drives_other(std::vector& devices) +{ + debug_out_info("app", DBG_FUNC_MSG << "Detecting through /dev...\n"); + + std::string sdev_config_path; + #if defined CONFIG_KERNEL_SOLARIS + sdev_config_path = "system/solaris_dev_path"; + #else // other unixes + sdev_config_path = "system/unix_sdev_path"; + #endif + + std::string dev_dir; // defaults to /dev for freebsd, /dev/rdsk for solaris. + if (!rconfig::get_data(sdev_config_path, dev_dir) || dev_dir.empty()) { + debug_out_warn("app", DBG_FUNC_MSG << "Device directory path is not set.\n"); + return "Device directory path is not set."; + } + + hz::Dir dir(dev_dir); + + std::vector all_devices; + if (!dir.list(all_devices, false, hz::DirSortNone())) { // this outputs to debug too. + std::string error_msg = dir.get_error_utf8(); + if (!dir.exists()) { + debug_out_warn("app", DBG_FUNC_MSG << "Device directory doesn't exist.\n"); + } else { + debug_out_error("app", DBG_FUNC_MSG << "Cannot list directory entries.\n"); + } + return error_msg; + } + + + // platform whitelist + std::vector whitelist; + + +#if defined CONFIG_KERNEL_FREEBSD || defined CONFIG_KERNEL_DRAGONFLY + // FreeBSD example device names: + // * /dev/ad0 - ide disk 0. /dev/ad0a - non-slice dos partition 1. + // * /dev/da1s1e - scsi disk 1 (second disk), slice 1 (aka dos partition 1), + // bsd partition e (multiple bsd partitions may be inside one dos partition). + // * /dev/acd0 - first cdrom. /dev/acd0c - ?. + // Info: http://www.freebsd.org/doc/en/books/handbook/disks-naming.html + + // Of two machines I had access to, freebsd 6.3 had only real devices, + // while freebsd 4.10 had lots of dummy ones (ad1, ad2, ad3, da0, da1, da2, da3, sa0, ...). + + whitelist.push_back("/^ad[0-9]+$/"); // adN without suffix - fbsd ide + whitelist.push_back("/^da[0-9]+$/"); // daN without suffix - fbsd scsi, usb +// whitelist.push_back("/ ^sa[0-9]+$/"); // saN without suffix - fbsd scsi tape +// whitelist.push_back("/^ast[0-9]+$/"); // astN without suffix - fbsd ide tape + whitelist.push_back("/^aacd[0-9]+$/"); // fbsd adaptec raid + whitelist.push_back("/^mlxd[0-9]+$/"); // fbsd mylex raid + whitelist.push_back("/^mlyd[0-9]+$/"); // fbsd mylex raid + whitelist.push_back("/^amrd[0-9]+$/"); // fbsd AMI raid + whitelist.push_back("/^idad[0-9]+$/"); // fbsd compaq raid + whitelist.push_back("/^twed[0-9]+$/"); // fbsd 3ware raid + // these are checked by smartctl, but they are not mentioned in freebsd docs: + whitelist.push_back("/^tw[ae][0-9]+$/"); // fbsd 3ware raid + // unused: acd - ide cdrom, cd - scsi cdrom, mcd - mitsumi cdrom, scd - sony cdrom, + // fd - floppy, fla - diskonchip flash. + + +#elif defined CONFIG_KERNEL_SOLARIS + + // /dev/rdsk contains "raw" physical char devices, as opposed to + // "filesystem" block devices in /dev/dsk. smartctl needs rdsk. + + // x86: + + // /dev/rdsk/c0t0d0p0:1 + // Where c0 is the controller number. + // t0 is the target (SCSI ID number) (omit for ATAPI) + // d0 is always 0 for SCSI, the drive # for ATAPI + // p0 is the partition (p0 is the entire disk, or p1 - p4) + // :1 is the logical drive (c - z or 1 - 24) (that is, logical partitions inside extended partition) + + // /dev/rdsk/c0d0p2:1 + // where p2 means the extended partition (type 0x05) is partition 2 (out of 1-4) and + // ":1" is the 2nd extended partition (:0 would be the first extended partition). -- not sure about this! + + // p0 whole physical disk + // p1 - p4 Four primary fdisk partitions + // p5 - p30 26 logical drives in extended DOS partition (not implemented by Solaris) + // s0 - s15 16 slices in the Solaris FDISK partition (SPARC has only 8) + // On Solaris, by convention, s2 is the whole Solaris partition. + + // SPARC: There are no *p* devices on sparc afaik, only slices. By convention, + // s2 is used as a "whole" disk there (but not with EFI partitions!). + // So, c0d0s2 is the whole disk there. + // x86 has both c0d0s2 and c0d0p2, where s2 is a whole solaris partition. + + // NOTE: It seems that smartctl searches for s0 (aka root partition) in the end. I'm not sure + // what implications this has for x86, but we replicate this behaviour. + + // Note: Cdroms are ATAPI, so they pose as SCSI, as opposed to IDE hds. + + // TODO: No idea how to implement /dev/rmt (scsi tape devices), + // I have no files in that directory. + + whitelist.push_back("/^c[0-9]+(?:t[0-9]+)?d[0-9]+s0$/"); + + + +#elif defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD + + // OpenBSD / NetBSD have /dev/wdNc for IDE/ATA, /dev/sdNc for SCSI disk, + // /dev/stNc for scsi tape. N is [0-9]+. "c" means "whole disk" (not sure about + // different architectures though). There are no "sdN" devices, only "sdNP". + // Another manual says that wd0d would be a whole disk, while wd0c is its + // bsd part only (on x86) (only on netbsd?). Anyway, getrawpartition() gives + // us the letter we need. + // There is no additional level of names for BSD subpartitions (unlike freebsd). + // cd0a is cdrom. + // Dummy devices are present. + + // Note: This detection may take a while (probably due to open() check). + + char whole_part = 'a' + getrawpartition(); // from bsd's util.h + + whitelist.push_back(hz::string_sprintf("/^wd[0-9]+%c$/", whole_part)); + whitelist.push_back(hz::string_sprintf("/^sd[0-9]+%c$/", whole_part)); + whitelist.push_back(hz::string_sprintf("/^st[0-9]+%c$/", whole_part)); + + + +#elif defined CONFIG_KERNEL_DARWIN + + // Darwin has /dev/disk0, /dev/disk0s1, etc... + // Only real devices are present, so no need for additional checks. + + whitelist.push_back("/^disk[0-9]+$/"); + + + +#elif defined CONFIG_KERNEL_QNX + + // QNX has /dev/hd0, /dev/hd0t78 (partition?). + // Afaik, IDE and SCSI have the same prefix. fd for floppy, cd for cdrom. + // Not sure about the tapes. + // Only real devices are present, so no need for additional checks. + + whitelist.push_back("/^hd[0-9]+$/"); + + +#endif // unix platforms + + + std::vector matched_devices; + + for (unsigned int i = 0; i < all_devices.size(); ++i) { + std::string entry = all_devices[i]; + if (entry == "." || entry == "..") + continue; + + bool matched = false; + for (std::vector::const_iterator iter = whitelist.begin(); iter != whitelist.end(); ++iter) { + if (app_pcre_match(*iter, entry)) { + matched = true; + break; + } + } + if (!matched) + continue; + + hz::FsPath path = dev_dir + hz::DIR_SEPARATOR_S + all_devices[i]; + + // In case these are links, trace them to originals. + // We don't replace /dev files with real devices - it leads to really bad paths (pci ids for solaris, etc...). + std::string link_dest; + hz::FsPath real_dev_path; + if (path.get_link_destination(link_dest)) { + real_dev_path = (hz::path_is_absolute(link_dest) ? link_dest : (dev_dir + hz::DIR_SEPARATOR_S + link_dest)); + real_dev_path.compress(); // compress in case there are ../-s. + + // solaris has dangling links for non-existant devices, so filter them out. + if (!real_dev_path.exists()) + continue; + } + + matched_devices.push_back(path.str()); + } + + + // List ones who need dummy device filtering + #if defined CONFIG_KERNEL_FREEBSD || defined CONFIG_KERNEL_DRAGONFLY \ + || defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD + // Since we may have encountered dummy devices, we should check + // if they really exist. Unfortunately, this involves opening them, which + // is not good with some media (e.g. it may hang on audio cd (freebsd, maybe others), etc...). + // That's why we don't whitelist cd devices. Let's hope usb and others are + // ok with this. Dummy devices give errno ENXIO (6, Device not configured) on *bsd. + // In Linux ENXIO is (6, No such device or address). + + // Don't do this on solaris - we can't distinguish between cdroms and hds there. + + // If there are less than 4 devices, they are probably not dummy (newer freebsd). + bool open_needed = (matched_devices.size() >= 4); + if (open_needed) { + debug_out_info("app", DBG_FUNC_MSG << "Number of matched devices is " + << matched_devices.size() << ", will try to filter non-existent ones out.\n"); + } else { + debug_out_info("app", DBG_FUNC_MSG << "Number of matched devices is " + << matched_devices.size() << ", no need for filtering them out.\n"); + } + + for (unsigned int i = 0; i < matched_devices.size(); ++i) { + hz::File dev_file(matched_devices[i]); + + // we check errno because OS may deny us for some other reason on valid devices. + if (open_needed && !dev_file.open("rb") && dev_file.get_errno() == ENXIO) { + debug_out_dump("app", DBG_FUNC_MSG << "Device \"" << dev_file.str() << "\" failed to open, ignoring.\n"); + continue; + } + + if (open_needed) + debug_out_dump("app", DBG_FUNC_MSG << "Device \"" << dev_file.str() << "\" opened successfully, adding to device list.\n"); + + devices.push_back(dev_file.str()); + } + + #else // not *BSD + for (unsigned int i = 0; i < matched_devices.size(); ++i) { + devices.push_back(matched_devices[i]); + } + + #endif + + hz::shell_sort(devices.begin(), devices.end()); + + return std::string(); +} + + + + + +#endif // !defined CONFIG_KERNEL_LINUX && !defined CONFIG_KERNEL_FAMILY_WINDOWS diff --git a/gsmartcontrol/src/applib/storage_detector_other.h b/gsmartcontrol/src/applib/storage_detector_other.h new file mode 100644 index 0000000..57c7956 --- /dev/null +++ b/gsmartcontrol/src/applib/storage_detector_other.h @@ -0,0 +1,26 @@ +/************************************************************************** + Copyright: + (C) 2008 - 2011 Alexander Shaduri + License: See LICENSE_gsmartcontrol.txt +***************************************************************************/ + +#ifndef STORAGE_DETECTOR_OTHER_H +#define STORAGE_DETECTOR_OTHER_H + +#include +#include + +#include "hz/hz_config.h" // CONFIG_* + + +#if !defined CONFIG_KERNEL_LINUX && !defined CONFIG_KERNEL_FAMILY_WINDOWS + + +// FreeBSD, Solaris, etc... . +std::string detect_drives_other(std::vector& devices); + + +#endif + + +#endif diff --git a/gsmartcontrol/src/applib/storage_detector_win32.cpp b/gsmartcontrol/src/applib/storage_detector_win32.cpp new file mode 100644 index 0000000..3469b5f --- /dev/null +++ b/gsmartcontrol/src/applib/storage_detector_win32.cpp @@ -0,0 +1,49 @@ +/************************************************************************** + Copyright: + (C) 2008 - 2011 Alexander Shaduri + License: See LICENSE_gsmartcontrol.txt +***************************************************************************/ + +#include "storage_detector_win32.h" + +#if defined CONFIG_KERNEL_FAMILY_WINDOWS + +#include // CreateFileA(), CloseHandle(), etc... + +#include "hz/string_sprintf.h" + + + +// smartctl accepts various variants, the most straight being pdN, +// (or /dev/pdN, /dev/ being optional) where N comes from +// "\\.\PhysicalDriveN" (winnt only). +// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + +std::string detect_drives_win32(std::vector& devices) +{ + for (int drive_num = 0; ; ++drive_num) { + std::string name = hz::string_sprintf("\\\\.\\PhysicalDrive%d", drive_num); + + // If the drive is openable, then it's there. + // NOTE: Administrative privileges are required to open it. + // Yes, CreateFile() is open, not create. Yes, it's silly (ah, win32...). + // We don't use any long/unopenable files here, so use the ANSI version. + HANDLE h = CreateFileA(name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + + // The numbers seem to be consecutive, so break on first invalid. + if (h == INVALID_HANDLE_VALUE) + break; + + CloseHandle(h); + + devices.push_back(hz::string_sprintf("pd%d", drive_num)); + } + + return std::string(); +} + + + + +#endif // CONFIG_KERNEL_FAMILY_WINDOWS diff --git a/gsmartcontrol/src/applib/storage_detector_win32.h b/gsmartcontrol/src/applib/storage_detector_win32.h new file mode 100644 index 0000000..3592e74 --- /dev/null +++ b/gsmartcontrol/src/applib/storage_detector_win32.h @@ -0,0 +1,25 @@ +/************************************************************************** + Copyright: + (C) 2008 - 2011 Alexander Shaduri + License: See LICENSE_gsmartcontrol.txt +***************************************************************************/ + +#ifndef STORAGE_DETECTOR_WIN32_H +#define STORAGE_DETECTOR_WIN32_H + +#include +#include + +#include "hz/hz_config.h" // CONFIG_* + + +#if defined CONFIG_KERNEL_FAMILY_WINDOWS + + +std::string detect_drives_win32(std::vector& devices); + + +#endif + + +#endif