summaryrefslogtreecommitdiffstats
path: root/src/detection/physicaldisk
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/physicaldisk')
-rw-r--r--src/detection/physicaldisk/physicaldisk.h17
-rw-r--r--src/detection/physicaldisk/physicaldisk_apple.c191
-rw-r--r--src/detection/physicaldisk/physicaldisk_bsd.c127
-rw-r--r--src/detection/physicaldisk/physicaldisk_haiku.c104
-rw-r--r--src/detection/physicaldisk/physicaldisk_linux.c202
-rw-r--r--src/detection/physicaldisk/physicaldisk_nbsd.c324
-rw-r--r--src/detection/physicaldisk/physicaldisk_nosupport.c5
-rw-r--r--src/detection/physicaldisk/physicaldisk_obsd.c279
-rw-r--r--src/detection/physicaldisk/physicaldisk_sunos.c107
-rw-r--r--src/detection/physicaldisk/physicaldisk_windows.c349
10 files changed, 1705 insertions, 0 deletions
diff --git a/src/detection/physicaldisk/physicaldisk.h b/src/detection/physicaldisk/physicaldisk.h
new file mode 100644
index 0000000..3481063
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk.h
@@ -0,0 +1,17 @@
+#include "fastfetch.h"
+#include "modules/physicaldisk/option.h"
+
+#define FF_PHYSICALDISK_TEMP_UNSET (-DBL_MAX)
+
+typedef struct FFPhysicalDiskResult {
+ FFstrbuf name;
+ FFstrbuf interconnect;
+ FFstrbuf serial;
+ FFstrbuf devPath;
+ FFstrbuf revision;
+ FFPhysicalDiskType type;
+ uint64_t size;
+ double temperature;
+} FFPhysicalDiskResult;
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options);
diff --git a/src/detection/physicaldisk/physicaldisk_apple.c b/src/detection/physicaldisk/physicaldisk_apple.c
new file mode 100644
index 0000000..f4c5cd6
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_apple.c
@@ -0,0 +1,191 @@
+#include "physicaldisk.h"
+#include "common/apple/cf_helpers.h"
+
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOBSD.h>
+#include <IOKit/storage/IOMedia.h>
+#include <IOKit/storage/IOBlockStorageDriver.h>
+#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
+#include <IOKit/storage/IOStorageProtocolCharacteristics.h>
+#ifdef MAC_OS_X_VERSION_10_15
+ #include <IOKit/storage/nvme/NVMeSMARTLibExternal.h>
+#endif
+
+#ifdef MAC_OS_X_VERSION_10_15
+static inline void wrapIoDestroyPlugInInterface(IOCFPlugInInterface*** pluginInf) {
+ assert(pluginInf);
+ if (*pluginInf) {
+ IODestroyPlugInInterface(*pluginInf);
+ }
+}
+#endif
+
+static const char* detectSsdTemp(io_service_t entryPhysical, double* temp) {
+#ifdef MAC_OS_X_VERSION_10_15
+ FF_A_CLEANUP(wrapIoDestroyPlugInInterface) IOCFPlugInInterface** pluginInf = NULL;
+ int32_t score;
+ if (IOCreatePlugInInterfaceForService(entryPhysical, kIONVMeSMARTUserClientTypeID, kIOCFPlugInInterfaceID, &pluginInf, &score) != kIOReturnSuccess) {
+ return "IOCreatePlugInInterfaceForService() failed";
+ }
+
+ IONVMeSMARTInterface** smartInf = NULL;
+ if ((*pluginInf)->QueryInterface(pluginInf, CFUUIDGetUUIDBytes(kIONVMeSMARTInterfaceID), (LPVOID) &smartInf) != kIOReturnSuccess) {
+ return "QueryInterface() failed";
+ }
+
+ NVMeSMARTData smartData;
+ const char* error = NULL;
+ if ((*smartInf)->SMARTReadData(smartInf, &smartData) == kIOReturnSuccess) {
+ *temp = smartData.TEMPERATURE - 273;
+ } else {
+ error = "SMARTReadData() failed";
+ }
+
+ (*pluginInf)->Release(smartInf);
+ return error;
+#else
+ return "No support for old MacOS version";
+#endif
+}
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = 0;
+ if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching(kIOBlockStorageDriverClass), &iterator) != KERN_SUCCESS) {
+ return "IOServiceGetMatchingServices() failed";
+ }
+
+ io_registry_entry_t registryEntry;
+ while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDriver = registryEntry;
+
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryMedia = IO_OBJECT_NULL;
+ IORegistryEntryGetChildEntry(entryDriver, kIOServicePlane, &entryMedia);
+
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryPhysical = 0;
+ if (IORegistryEntryGetParentEntry(entryDriver, kIOServicePlane, &entryPhysical) != KERN_SUCCESS) {
+ continue;
+ }
+
+ FF_STRBUF_AUTO_DESTROY deviceName = ffStrbufCreate();
+
+ if (entryMedia != IO_OBJECT_NULL) {
+ io_name_t name;
+ if (IORegistryEntryGetName(entryMedia, name) == KERN_SUCCESS) {
+ ffStrbufSetS(&deviceName, name);
+ }
+ }
+
+ if (deviceName.length == 0) {
+ FF_CFTYPE_AUTO_RELEASE CFDictionaryRef deviceCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyDeviceCharacteristicsKey), NULL, kNilOptions);
+ if (deviceCharacteristics) {
+ if (ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyVendorNameKey), &deviceName) == NULL && deviceName.length > 0) {
+ ffStrbufAppendC(&deviceName, ' ');
+ }
+
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
+ if (ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductNameKey), &name) == NULL) {
+ ffStrbufAppend(&deviceName, &name);
+ }
+ }
+ }
+
+ if (options->namePrefix.length && ffStrbufStartsWith(&deviceName, &options->namePrefix) != 0) {
+ continue;
+ }
+
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+
+ FF_STRBUF_AUTO_DESTROY interconnect = ffStrbufCreate();
+ FF_CFTYPE_AUTO_RELEASE CFDictionaryRef protocolCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyProtocolCharacteristicsKey), NULL, kNilOptions);
+ if (protocolCharacteristics) {
+ if (ffCfDictGetString(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectTypeKey), &interconnect) == NULL) {
+ if (ffStrbufEqualS(&interconnect, kIOPropertyPhysicalInterconnectTypeVirtual)) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ FF_STRBUF_AUTO_DESTROY location = ffStrbufCreate();
+ if (ffCfDictGetString(protocolCharacteristics, CFSTR(kIOPropertyPhysicalInterconnectLocationKey), &location) == NULL) {
+ ffStrbufAppendS(&interconnect, " - ");
+ ffStrbufAppend(&interconnect, &location);
+ }
+ }
+ }
+ }
+
+ uint64_t size = 0;
+ if (entryMedia != IO_OBJECT_NULL) {
+ FF_CFTYPE_AUTO_RELEASE CFNumberRef mediaSize = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOMediaSizeKey), NULL, kNilOptions);
+ if (mediaSize) {
+ ffCfNumGetInt64(mediaSize, (int64_t*) &size);
+ }
+ }
+
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInitMove(&device->name, &deviceName);
+ ffStrbufInit(&device->devPath);
+ ffStrbufInitMove(&device->interconnect, &interconnect);
+ device->type = type;
+ device->size = size;
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+
+ if (entryMedia != IO_OBJECT_NULL) {
+ FF_CFTYPE_AUTO_RELEASE CFBooleanRef removable = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOMediaRemovableKey), NULL, kNilOptions);
+ if (removable) {
+ device->type |= CFBooleanGetValue(removable) ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
+ }
+
+ FF_CFTYPE_AUTO_RELEASE CFBooleanRef writable = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOMediaWritableKey), NULL, kNilOptions);
+ if (writable) {
+ device->type |= CFBooleanGetValue(writable) ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY;
+ }
+
+ FF_CFTYPE_AUTO_RELEASE CFStringRef bsdName = IORegistryEntryCreateCFProperty(entryMedia, CFSTR(kIOBSDNameKey), NULL, kNilOptions);
+ if (bsdName) {
+ ffCfStrGetString(bsdName, &device->devPath);
+ ffStrbufPrependS(&device->devPath, "/dev/");
+ }
+ }
+
+ FF_CFTYPE_AUTO_RELEASE CFDictionaryRef deviceCharacteristics = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyDeviceCharacteristicsKey), NULL, kNilOptions);
+ if (deviceCharacteristics) {
+ ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductSerialNumberKey), &device->serial);
+ ffStrbufTrimSpace(&device->serial);
+ ffCfDictGetString(deviceCharacteristics, CFSTR(kIOPropertyProductRevisionLevelKey), &device->revision);
+ ffStrbufTrimRightSpace(&device->revision);
+
+ if (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL)) {
+ CFStringRef mediumType = (CFStringRef) CFDictionaryGetValue(deviceCharacteristics, CFSTR(kIOPropertyMediumTypeKey));
+ if (mediumType) {
+ if (CFStringCompare(mediumType, CFSTR(kIOPropertyMediumTypeSolidStateKey), 0) == 0) {
+ device->type |= FF_PHYSICALDISK_TYPE_SSD;
+ } else if (CFStringCompare(mediumType, CFSTR(kIOPropertyMediumTypeRotationalKey), 0) == 0) {
+ device->type |= FF_PHYSICALDISK_TYPE_HDD;
+ }
+ }
+ }
+ }
+
+#ifdef MAC_OS_X_VERSION_10_15
+ if (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL) && options->temp) {
+ FF_CFTYPE_AUTO_RELEASE CFBooleanRef nvmeSMARTCapable = IORegistryEntryCreateCFProperty(entryPhysical, CFSTR(kIOPropertyNVMeSMARTCapableKey), NULL, kNilOptions);
+ if (nvmeSMARTCapable && CFBooleanGetValue(nvmeSMARTCapable)) {
+ detectSsdTemp(entryPhysical, &device->temperature);
+ }
+ }
+#endif
+ }
+
+ return NULL;
+}
diff --git a/src/detection/physicaldisk/physicaldisk_bsd.c b/src/detection/physicaldisk/physicaldisk_bsd.c
new file mode 100644
index 0000000..5a87768
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_bsd.c
@@ -0,0 +1,127 @@
+#include "physicaldisk.h"
+
+#if __has_include(<libgeom.h>)
+
+ #include "common/strutil.h"
+
+ #include <devstat.h>
+ #include <memory.h>
+ #include <fcntl.h>
+ #include <sys/ioctl.h>
+ #include <sys/disk.h>
+ #include <libgeom.h>
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ struct gmesh geomTree;
+ if (geom_gettree(&geomTree) < 0) {
+ return "geom_gettree() failed";
+ }
+
+ if (geom_stats_open() < 0) {
+ return "geom_stats_open() failed";
+ }
+
+ void* snap = geom_stats_snapshot_get();
+ struct devstat* snapIter;
+ while ((snapIter = geom_stats_snapshot_next(snap)) != NULL) {
+ if (snapIter->device_type & DEVSTAT_TYPE_PASS) {
+ continue;
+ }
+ struct gident* geomId = geom_lookupid(&geomTree, snapIter->id);
+ if (geomId == NULL) {
+ continue;
+ }
+ if (geomId->lg_what != ISPROVIDER) {
+ continue;
+ }
+ struct gprovider* provider = (struct gprovider*) geomId->lg_ptr;
+ if (provider->lg_geom->lg_rank != 1) {
+ continue;
+ }
+
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+ if (!ffStrEquals(provider->lg_geom->lg_class->lg_name, "DISK")) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ }
+ uint64_t size = (uint64_t) provider->lg_mediasize;
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateS(provider->lg_name);
+ FF_STRBUF_AUTO_DESTROY identifier = ffStrbufCreate();
+ for (struct gconfig* ptr = provider->lg_config.lh_first; ptr; ptr = ptr->lg_config.le_next) {
+ if (ffStrEquals(ptr->lg_name, "descr")) {
+ ffStrbufSetS(&name, ptr->lg_val);
+ } else if (ffStrEquals(ptr->lg_name, "rotationrate") && !ffStrEquals(ptr->lg_val, "unknown")) {
+ type |= ffStrEquals(ptr->lg_val, "0") ? FF_PHYSICALDISK_TYPE_SSD : FF_PHYSICALDISK_TYPE_HDD;
+ } else if (ffStrEquals(ptr->lg_name, "ident")) {
+ ffStrbufSetS(&identifier, ptr->lg_val);
+ } else if (ffStrEquals(ptr->lg_name, "access")) {
+ if (ffStrEquals(ptr->lg_val, "read-only")) {
+ type |= FF_PHYSICALDISK_TYPE_READONLY;
+ } else if (ffStrEquals(ptr->lg_val, "read-write")) {
+ type |= FF_PHYSICALDISK_TYPE_READWRITE;
+ }
+ }
+ }
+
+ if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
+ continue;
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInitF(&device->devPath, "/dev/%s", provider->lg_name);
+ ffStrbufInitMove(&device->serial, &identifier);
+ ffStrbufTrimSpace(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInit(&device->interconnect);
+ ffStrbufInitMove(&device->name, &name);
+ device->size = size;
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+
+ switch (snapIter->device_type & DEVSTAT_TYPE_IF_MASK) {
+ case DEVSTAT_TYPE_IF_SCSI:
+ ffStrbufSetStatic(&device->interconnect, "SCSI");
+ break;
+ case DEVSTAT_TYPE_IF_IDE:
+ ffStrbufSetStatic(&device->interconnect, "IDE");
+ break;
+ case DEVSTAT_TYPE_IF_OTHER:
+ ffStrbufSetStatic(&device->interconnect, "OTHER");
+ break;
+
+ // https://github.com/freebsd/freebsd-src/commit/d282baddb0b029ca8466d23ac51e95c918442535
+ case 0x040 /*DEVSTAT_TYPE_IF_NVME*/:
+ ffStrbufSetStatic(&device->interconnect, "NVMe");
+ break;
+ }
+
+ if (!(device->type & FF_PHYSICALDISK_TYPE_READONLY) && !(device->type & FF_PHYSICALDISK_TYPE_READWRITE)) {
+ int acr = 1, acw = 1; // Number of partitions mounted for reading or writing
+ if (sscanf(provider->lg_mode, "r%dw%de%*d", &acr, &acw) == 2 && acr) {
+ type |= acw ? FF_PHYSICALDISK_TYPE_READWRITE : FF_PHYSICALDISK_TYPE_READONLY;
+ }
+ }
+
+ device->type = type;
+ }
+
+ geom_stats_snapshot_free(snap);
+ geom_stats_close();
+
+ return NULL;
+}
+#else
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ return "Fastfetch was compiled without libgeom support";
+}
+#endif
diff --git a/src/detection/physicaldisk/physicaldisk_haiku.c b/src/detection/physicaldisk/physicaldisk_haiku.c
new file mode 100644
index 0000000..e44ddce
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_haiku.c
@@ -0,0 +1,104 @@
+#include "physicaldisk.h"
+
+#include "common/io.h"
+#include "common/strutil.h"
+
+#include <OS.h>
+#include <StorageDefs.h>
+#include <Drivers.h>
+#include <sys/ioctl.h>
+
+static const char* searchRawDeviceFile(FFstrbuf* path, const char* diskType, FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_AUTO_CLOSE_DIR DIR* dir = opendir(path->chars);
+ if (!dir) {
+ return "detectDiskType: opendir() failed";
+ }
+ uint32_t baseLen = path->length;
+
+ struct dirent* entry;
+ while ((entry = readdir(dir))) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ ffStrbufAppendC(path, '/');
+ ffStrbufAppendS(path, entry->d_name);
+
+ struct stat st;
+ if (stat(path->chars, &st) != 0) {
+ ffStrbufSubstrBefore(path, baseLen);
+ continue;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ searchRawDeviceFile(path, diskType, result, options);
+ } else if (ffStrEquals(entry->d_name, "raw")) {
+ FF_AUTO_CLOSE_FD int rawfd = open(path->chars, O_RDONLY | O_CLOEXEC);
+ if (rawfd < 0) {
+ continue;
+ }
+
+ device_geometry geometry;
+ if (ioctl(rawfd, B_GET_GEOMETRY, &geometry, sizeof(geometry)) < 0) {
+ continue;
+ }
+
+ char name[B_OS_NAME_LENGTH];
+ if (ioctl(rawfd, B_GET_DEVICE_NAME, name, sizeof(name)) != 0) {
+ // ioctl reports `not a tty` for NVME drives for some reason
+ snprintf(name, sizeof(name), "Unknown %s drive", diskType);
+ }
+
+ if (options->namePrefix.length && strncmp(name, options->namePrefix.chars, options->namePrefix.length) != 0) {
+ continue;
+ }
+
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+ uint64_t size = (uint64_t) geometry.cylinder_count * geometry.head_count * geometry.sectors_per_track * geometry.bytes_per_sector;
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInitS(&device->name, name);
+ ffStrbufInitCopy(&device->devPath, path);
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInitS(&device->interconnect, diskType);
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+ device->type = type |
+ (geometry.read_only ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE) |
+ (geometry.removable ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED);
+ device->size = size;
+ }
+
+ ffStrbufSubstrBefore(path, baseLen);
+ }
+ return NULL;
+}
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_AUTO_CLOSE_DIR DIR* dir = opendir("/dev/disk");
+ if (!dir) {
+ return "opendir(/dev/disk) failed";
+ }
+
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(64);
+ ffStrbufAppendS(&path, "/dev/disk/");
+ uint32_t baseLen = path.length;
+
+ struct dirent* entry;
+ while ((entry = readdir(dir))) {
+ if (entry->d_name[0] == '.' || ffStrEquals(entry->d_name, "virtual")) {
+ continue;
+ }
+ ffStrbufAppendS(&path, entry->d_name);
+ searchRawDeviceFile(&path, entry->d_name, result, options);
+ ffStrbufSubstrBefore(&path, baseLen);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/physicaldisk/physicaldisk_linux.c b/src/detection/physicaldisk/physicaldisk_linux.c
new file mode 100644
index 0000000..f8126b9
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_linux.c
@@ -0,0 +1,202 @@
+#include "physicaldisk.h"
+#include "common/io.h"
+#include "common/properties.h"
+#include "common/strutil.h"
+
+#include <ctype.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static double detectNvmeTemp(int devfd) {
+ char pathHwmon[] = "hwmon$/temp1_input";
+
+ for (char c = '0'; c <= '9'; c++) // hopefully there's only one digit
+ {
+ pathHwmon[strlen("hwmon")] = c;
+ char buffer[64];
+ ssize_t size = ffReadFileDataRelative(devfd, pathHwmon, ARRAY_SIZE(buffer), buffer);
+ if (size > 0) {
+ buffer[size] = '\0';
+ double temp = strtod(buffer, NULL);
+ return temp > 0 && temp < 10000000 /*VMware*/ ? temp / 1000 : FF_PHYSICALDISK_TEMP_UNSET;
+ }
+ }
+
+ return FF_PHYSICALDISK_TEMP_UNSET;
+}
+
+static void parsePhysicalDisk(int dfd, const char* devName, FFPhysicalDiskOptions* options, FFlist* result) {
+ uint64_t size = 0;
+
+ {
+ char blkSize[32];
+ ssize_t fileSize = ffReadFileDataRelative(dfd, "size", ARRAY_SIZE(blkSize) - 1, blkSize);
+ if (fileSize > 0) {
+ blkSize[fileSize] = 0;
+ size = (uint64_t) strtoul(blkSize, NULL, 10) * 512;
+ }
+ }
+
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ return;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ int devfd = openat(dfd, "device", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
+
+ if (devfd < 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
+ return;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ }
+
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
+
+ if (devfd > 0) {
+ if (ffAppendFileBufferRelative(devfd, "vendor", &name)) {
+ ffStrbufTrimRightSpace(&name);
+ if (name.length > 0) {
+ ffStrbufAppendC(&name, ' ');
+ }
+ }
+
+ ffAppendFileBufferRelative(devfd, "model", &name);
+ ffStrbufTrimRightSpace(&name);
+
+ if (name.length == 0) {
+ ffStrbufSetS(&name, devName);
+ }
+
+ if (ffStrStartsWith(devName, "nvme")) {
+ int devid, nsid;
+ if (sscanf(devName, "nvme%dn%d", &devid, &nsid) == 2) {
+ bool multiNs = nsid > 1;
+ if (!multiNs) {
+ char pathSysBlock[32];
+ snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), "/dev/nvme%dn2", devid);
+ multiNs = access(pathSysBlock, F_OK) == 0;
+ }
+ if (multiNs) {
+ // In Asahi Linux, there are multiple namespaces for the same NVMe drive.
+ ffStrbufAppendF(&name, " - %d", nsid);
+ }
+ }
+ }
+
+ if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
+ return;
+ }
+ } else {
+ ffStrbufSetS(&name, devName);
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInitMove(&device->name, &name);
+ ffStrbufInitF(&device->devPath, "/dev/%s", devName);
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInit(&device->interconnect);
+ device->type = type;
+ device->size = size;
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+
+ bool isVirtio = false;
+ if (devfd > 0) {
+ if (ffStrStartsWith(devName, "nvme")) {
+ ffStrbufSetStatic(&device->interconnect, "NVMe");
+ } else if (ffStrStartsWith(devName, "mmcblk")) {
+ ffStrbufSetStatic(&device->interconnect, "MMC");
+ } else {
+ char pathSysDeviceLink[64];
+ snprintf(pathSysDeviceLink, ARRAY_SIZE(pathSysDeviceLink), "/sys/block/%s/device", devName);
+ char pathSysDeviceReal[PATH_MAX];
+ if (realpath(pathSysDeviceLink, pathSysDeviceReal)) {
+ if (strstr(pathSysDeviceReal, "/usb") != NULL) {
+ ffStrbufSetStatic(&device->interconnect, "USB");
+ } else if (strstr(pathSysDeviceReal, "/ata") != NULL) {
+ ffStrbufSetStatic(&device->interconnect, "ATA");
+ } else if (strstr(pathSysDeviceReal, "/scsi") != NULL) {
+ ffStrbufSetStatic(&device->interconnect, "SCSI");
+ } else if (strstr(pathSysDeviceReal, "/nvme") != NULL) {
+ ffStrbufSetStatic(&device->interconnect, "NVMe");
+ } else if (strstr(pathSysDeviceReal, "/virtio") != NULL) {
+ ffStrbufSetStatic(&device->interconnect, "VirtIO");
+ isVirtio = true; // VirtIO devices are virtual, but we still want to report it
+ } else {
+ if (ffAppendFileBufferRelative(devfd, "transport", &device->interconnect)) {
+ ffStrbufTrimRightSpace(&device->interconnect);
+ }
+ }
+ }
+ }
+ } else {
+ ffStrbufSetStatic(&device->interconnect, "Virtual");
+ }
+
+ if (devfd > 0 && !isVirtio) {
+ char isRotationalChar = '1';
+ if (ffReadFileDataRelative(dfd, "queue/rotational", 1, &isRotationalChar) > 0) {
+ device->type |= isRotationalChar == '1' ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD;
+ }
+
+ if (ffReadFileBufferRelative(devfd, "serial", &device->serial)) {
+ ffStrbufTrimSpace(&device->serial);
+ }
+
+ if (ffReadFileBufferRelative(devfd, "firmware_rev", &device->revision) ||
+ ffReadFileBufferRelative(devfd, "rev", &device->revision)) {
+ ffStrbufTrimRightSpace(&device->revision);
+ }
+
+ if (options->temp) {
+ device->temperature = detectNvmeTemp(devfd);
+ }
+ }
+
+ {
+ char removableChar = '0';
+ if (ffReadFileDataRelative(dfd, "removable", 1, &removableChar) > 0) {
+ device->type |= removableChar == '1' ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
+ }
+ }
+
+ {
+ char roChar = '0';
+ if (ffReadFileDataRelative(dfd, "ro", 1, &roChar) > 0) {
+ device->type |= roChar == '1' ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE;
+ }
+ }
+}
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_AUTO_CLOSE_DIR DIR* sysBlockDirp = opendir("/sys/block/");
+ if (sysBlockDirp == NULL) {
+ return "opendir(\"/sys/block/\") == NULL";
+ }
+
+ struct dirent* sysBlockEntry;
+ while ((sysBlockEntry = readdir(sysBlockDirp)) != NULL) {
+ const char* const devName = sysBlockEntry->d_name;
+
+ if (devName[0] == '.') {
+ continue;
+ }
+
+ char pathSysBlock[sizeof("/sys/block/") + sizeof(sysBlockEntry->d_name)];
+ snprintf(pathSysBlock, ARRAY_SIZE(pathSysBlock), "/sys/block/%s", devName);
+
+ int dfd = openat(dirfd(sysBlockDirp), devName, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
+ if (dfd > 0) {
+ parsePhysicalDisk(dfd, devName, options, result);
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/physicaldisk/physicaldisk_nbsd.c b/src/detection/physicaldisk/physicaldisk_nbsd.c
new file mode 100644
index 0000000..0733f3b
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_nbsd.c
@@ -0,0 +1,324 @@
+#define DKTYPENAMES
+#include "physicaldisk.h"
+#include "common/io.h"
+#include "common/sysctl.h"
+#include "common/debug.h"
+
+#include <util.h>
+#include <prop/proplib.h>
+#include <sys/ioctl.h>
+#include <sys/disklabel.h>
+#include <sys/dkio.h>
+#include <sys/scsiio.h>
+#include <dev/scsipi/scsipi_all.h>
+#include <dev/scsipi/scsi_spc.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static inline const char* retstsToStr(uint8_t retsts) {
+ switch (retsts) {
+ case SCCMD_OK:
+ return "OK";
+ case SCCMD_TIMEOUT:
+ return "TIMEOUT";
+ case SCCMD_BUSY:
+ return "BUSY";
+ case SCCMD_SENSE:
+ return "SENSE";
+ case SCCMD_UNKNOWN:
+ return "UNKNOWN";
+ default:
+ return "?";
+ }
+}
+
+#ifndef NDEBUG
+static inline const char* senseKeyToStr(uint8_t key) {
+ switch (key) {
+ case 0x0:
+ return "No Sense";
+ case 0x1:
+ return "Recovered Error";
+ case 0x2:
+ return "Not Ready";
+ case 0x3:
+ return "Medium Error";
+ case 0x4:
+ return "Hardware Error";
+ case 0x5:
+ return "Illegal Request";
+ case 0x6:
+ return "Unit Attention";
+ case 0x7:
+ return "Data Protect";
+ case 0x8:
+ return "Blank Check";
+ case 0x9:
+ return "Vendor Specific";
+ case 0xA:
+ return "Copy Aborted";
+ case 0xB:
+ return "Aborted Command";
+ case 0xC:
+ return "Equal";
+ case 0xD:
+ return "Volume Overflow";
+ case 0xE:
+ return "Miscompare";
+ case 0xF:
+ return "Completed";
+ default:
+ return "Unknown";
+ }
+}
+
+static void logScsiSense(const char* diskName, const char* operation, const scsireq_t* req) {
+ uint8_t responseCode = req->sense[0] & 0x7F;
+ uint8_t senseKey;
+ uint8_t asc;
+ uint8_t ascq;
+
+ if (responseCode == 0x72 || responseCode == 0x73) {
+ senseKey = req->sense[1] & 0x0F;
+ asc = req->sense[2];
+ ascq = req->sense[3];
+ } else {
+ senseKey = req->sense[2] & 0x0F;
+ asc = req->sense[12];
+ ascq = req->sense[13];
+ }
+
+ FF_STRBUF_AUTO_DESTROY rawSense = ffStrbufCreate();
+ for (size_t i = 0; i < req->senselen_used; ++i) {
+ if (i) {
+ ffStrbufAppendC(&rawSense, ' ');
+ }
+ ffStrbufAppendF(&rawSense, "%02X", req->sense[i]);
+ }
+
+ FF_DEBUG(
+ "%s for %s reported SENSE: response=0x%02X, key=0x%X (%s), ASC=0x%02X, ASCQ=0x%02X, raw=[%s]",
+ operation,
+ diskName,
+ responseCode,
+ senseKey,
+ senseKeyToStr(senseKey),
+ asc,
+ ascq,
+ rawSense.length ? rawSense.chars : "empty");
+}
+#else
+ #define logScsiSense(...) ((void) 0)
+#endif
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_STRBUF_AUTO_DESTROY diskNames = ffStrbufCreate();
+ FF_DEBUG("Querying disk names via sysctl hw.disknames");
+ const char* error = ffSysctlGetString("hw.disknames", &diskNames);
+ if (error) {
+ FF_DEBUG("ffSysctlGetString(hw.disknames) failed: %s", error);
+ return error;
+ }
+
+ FF_DEBUG("Disk names: %s", diskNames.chars);
+
+ char* diskName = NULL;
+ size_t len = 0;
+ while (ffStrbufGetdelim(&diskName, &len, ' ', &diskNames)) {
+ FF_DEBUG("Probing disk: %s", diskName);
+
+ if (options->namePrefix.length && !ffStrbufStartsWith(&(FFstrbuf) {
+ .chars = diskName,
+ .length = (uint32_t) len,
+ },
+ &options->namePrefix)) {
+ FF_DEBUG("Skipping %s due to namePrefix filter", diskName);
+ continue;
+ }
+
+ char devPath[256];
+ FF_AUTO_CLOSE_FD int f = opendisk(diskName, O_RDONLY, devPath, ARRAY_SIZE(devPath), 0);
+ if (f < 0) {
+ if (errno == EACCES) {
+ FF_DEBUG("opendisk(%s) failed: permission denied", diskName);
+ return "Permission denied; root required";
+ } else {
+ FF_DEBUG("opendisk(%s) failed: %s", diskName, strerror(errno));
+ continue;
+ }
+ }
+
+ FF_DEBUG("Opened %s as %s", diskName, devPath);
+
+ struct disklabel dl;
+ if (ioctl(f, DIOCGDINFO, &dl) < 0) {
+ FF_DEBUG("ioctl(DIOCGDINFO) failed for %s: %s", diskName, strerror(errno));
+ continue;
+ }
+
+ unsigned long sectorsPerUnit, sectorSize;
+
+ const char* devType = NULL;
+
+ prop_dictionary_t dict = NULL;
+ if (prop_dictionary_recv_ioctl(f, DIOCGDISKINFO, &dict) == 0) {
+ FF_DEBUG("DIOCGDISKINFO succeeded for %s", diskName);
+ prop_dictionary_get_string(dict, "type", &devType);
+
+ prop_dictionary_t geometry;
+ if (prop_dictionary_get_dict(dict, "geometry", &geometry)) {
+ prop_dictionary_get_ulong(geometry, "sectors-per-unit", &sectorsPerUnit);
+ prop_dictionary_get_ulong(geometry, "sector-size", &sectorSize);
+ } else {
+ FF_DEBUG("No geometry in diskinfo for %s, falling back to disklabel", diskName);
+ sectorsPerUnit = dl.d_secperunit;
+ sectorSize = dl.d_secsize;
+ }
+ } else {
+ FF_DEBUG("DIOCGDISKINFO failed for %s, falling back to disklabel", diskName);
+ sectorsPerUnit = dl.d_secperunit;
+ sectorSize = dl.d_secsize;
+ }
+
+ FFPhysicalDiskType type = dl.d_flags & D_REMOVABLE ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
+
+ switch (dl.d_type) {
+ case DKTYPE_VND:
+ case DKTYPE_LD:
+ case DKTYPE_RAID:
+ case DKTYPE_CGD:
+ case DKTYPE_VINUM:
+ case DKTYPE_DM:
+ case DKTYPE_RUMPD:
+ case DKTYPE_MD: {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
+ FF_DEBUG("Skipping virtual disk %s due to hideType", diskName);
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ break;
+ }
+ }
+
+ uint64_t size = sectorsPerUnit * sectorSize;
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ FF_DEBUG("Skipping unused disk %s due to hideType", diskName);
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInitS(&device->name, devType ?: dl.d_packname);
+ ffStrbufInitS(&device->devPath, devPath);
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInitS(&device->interconnect, dktypenames[dl.d_type]);
+ device->type = type;
+ device->size = size;
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+ FF_DEBUG("Added disk entry: name='%s', devPath='%s', interconnect='%s', size=%llu",
+ device->name.chars,
+ device->devPath.chars,
+ device->interconnect.chars,
+ (unsigned long long) device->size);
+
+ if (dict) {
+ prop_object_release(dict);
+ dict = NULL;
+ }
+
+ struct scsipi_inquiry_data inquiry = {};
+ scsireq_t req = {
+ .cmd = { [0] = INQUIRY, [4] = sizeof(inquiry) },
+ .cmdlen = 6,
+ .databuf = (caddr_t) &inquiry,
+ .datalen = sizeof(inquiry),
+ .timeout = 1000,
+ .flags = SCCMD_READ,
+ };
+ if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
+ if (req.retsts == SCCMD_OK) {
+ ffStrbufClear(&device->name);
+ ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.vendor), inquiry.vendor);
+ ffStrbufTrimRight(&device->name, '\0');
+ ffStrbufTrimRight(&device->name, ' ');
+ ffStrbufTrimRight(&device->name, ',');
+ ffStrbufAppendC(&device->name, ' ');
+ ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.product), inquiry.product);
+ ffStrbufTrimRight(&device->name, '\0');
+ ffStrbufTrimRight(&device->name, ' ');
+
+ ffStrbufSetNS(&device->revision, (uint32_t) ARRAY_SIZE(inquiry.revision), inquiry.revision);
+ ffStrbufTrimRight(&device->name, '\0');
+ ffStrbufTrimRight(&device->revision, ' ');
+ FF_DEBUG("SCSI inquiry for %s: name='%s', revision='%s'", diskName, device->name.chars, device->revision.chars);
+ } else {
+ FF_DEBUG("SCSI inquiry retsts != SCCMD_OK for %s (%d)", diskName, req.retsts);
+ continue;
+ }
+ } else {
+ FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI inquiry on %s: %s", diskName, strerror(errno));
+ continue;
+ }
+
+#ifdef SINQ_EVPD // Available since NetBSD 11.0
+ struct {
+ struct scsipi_inquiry_evpd_header header;
+ struct scsipi_inquiry_evpd_serial body;
+ } evpd = {};
+ req = (scsireq_t) {
+ .cmd = { [0] = INQUIRY, [1] = SINQ_EVPD, [2] = SINQ_VPD_UNIT_SERIAL, [4] = sizeof(evpd) },
+ .cmdlen = 6,
+ .databuf = (caddr_t) &evpd,
+ .datalen = sizeof(evpd),
+ .timeout = 1000,
+ .flags = SCCMD_READ,
+ };
+ if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
+ if (req.retsts == SCCMD_OK && evpd.header.pagecode == SINQ_VPD_UNIT_SERIAL) {
+ for (uint8_t i = 0; i < evpd.header.length[1]; ++i) {
+ ffStrbufAppendF(&device->serial, "%02X", evpd.body.serial_number[i]);
+ }
+ ffStrbufTrimRight(&device->serial, ' ');
+ FF_DEBUG("SCSI serial for %s: %s", diskName, device->serial.chars);
+ } else if (req.retsts == SCCMD_SENSE) {
+ logScsiSense(diskName, "SCSI serial page", &req);
+ } else {
+ FF_DEBUG("SCSI serial page unavailable for %s (retsts=%s, pagecode=%u)", diskName, retstsToStr(req.retsts), (unsigned) evpd.header.pagecode);
+ }
+ } else {
+ FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI serial on %s: %s", diskName, strerror(errno));
+ }
+#endif
+
+ struct scsi_mode_parameter_header_6 mode = {};
+ req = (scsireq_t) {
+ .cmd = { [0] = SCSI_MODE_SENSE_6, [2] = SMS_PAGE_MASK, [4] = sizeof(mode) },
+ .cmdlen = 6,
+ .databuf = (caddr_t) &mode,
+ .datalen = sizeof(mode),
+ .timeout = 1000,
+ .flags = SCCMD_READ,
+ };
+ if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
+ if (req.retsts == SCCMD_OK && mode.data_length > 0) {
+ device->type |= mode.dev_spec & 0x80 ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE;
+ FF_DEBUG("SCSI mode for %s indicates: %s", diskName, mode.dev_spec & 0x80 ? "readonly" : "readwrite");
+ } else if (req.retsts == SCCMD_SENSE) {
+ logScsiSense(diskName, "SCSI mode sense", &req);
+ } else {
+ FF_DEBUG("SCSI mode sense unavailable for %s (retsts=%s, data_length=%u)", diskName, retstsToStr(req.retsts), (unsigned) mode.data_length);
+ }
+ } else {
+ FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI mode sense on %s: %s", diskName, strerror(errno));
+ }
+
+ FF_DEBUG("Detected disk '%s' (%s), type=%u", device->name.chars, diskName, (unsigned) device->type);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/physicaldisk/physicaldisk_nosupport.c b/src/detection/physicaldisk/physicaldisk_nosupport.c
new file mode 100644
index 0000000..26e0ce8
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_nosupport.c
@@ -0,0 +1,5 @@
+#include "physicaldisk.h"
+
+const char* ffDetectPhysicalDisk(FF_A_UNUSED FFlist* result, FF_A_UNUSED FFPhysicalDiskOptions* options) {
+ return "Not supported on this platform";
+}
diff --git a/src/detection/physicaldisk/physicaldisk_obsd.c b/src/detection/physicaldisk/physicaldisk_obsd.c
new file mode 100644
index 0000000..6e89a99
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_obsd.c
@@ -0,0 +1,279 @@
+#include "physicaldisk.h"
+#include "common/io.h"
+#include "common/sysctl.h"
+#include "common/debug.h"
+
+#include <util.h>
+#include <sys/ioctl.h>
+#include <sys/disklabel.h>
+#include <sys/dkio.h>
+#include <sys/scsiio.h>
+#include <scsi/scsi_all.h>
+#include <fcntl.h>
+#include <errno.h>
+
+static inline const char* retstsToStr(uint8_t retsts) {
+ switch (retsts) {
+ case SCCMD_OK:
+ return "OK";
+ case SCCMD_TIMEOUT:
+ return "TIMEOUT";
+ case SCCMD_BUSY:
+ return "BUSY";
+ case SCCMD_SENSE:
+ return "SENSE";
+ case SCCMD_UNKNOWN:
+ return "UNKNOWN";
+ default:
+ return "?";
+ }
+}
+
+#ifndef NDEBUG
+static inline const char* senseKeyToStr(uint8_t key) {
+ switch (key) {
+ case 0x0:
+ return "No Sense";
+ case 0x1:
+ return "Recovered Error";
+ case 0x2:
+ return "Not Ready";
+ case 0x3:
+ return "Medium Error";
+ case 0x4:
+ return "Hardware Error";
+ case 0x5:
+ return "Illegal Request";
+ case 0x6:
+ return "Unit Attention";
+ case 0x7:
+ return "Data Protect";
+ case 0x8:
+ return "Blank Check";
+ case 0x9:
+ return "Vendor Specific";
+ case 0xA:
+ return "Copy Aborted";
+ case 0xB:
+ return "Aborted Command";
+ case 0xC:
+ return "Equal";
+ case 0xD:
+ return "Volume Overflow";
+ case 0xE:
+ return "Miscompare";
+ case 0xF:
+ return "Completed";
+ default:
+ return "Unknown";
+ }
+}
+
+static void logScsiSense(const char* diskName, const char* operation, const scsireq_t* req) {
+ uint8_t responseCode = req->sense[0] & 0x7F;
+ uint8_t senseKey;
+ uint8_t asc;
+ uint8_t ascq;
+
+ if (responseCode == 0x72 || responseCode == 0x73) {
+ senseKey = req->sense[1] & 0x0F;
+ asc = req->sense[2];
+ ascq = req->sense[3];
+ } else {
+ senseKey = req->sense[2] & 0x0F;
+ asc = req->sense[12];
+ ascq = req->sense[13];
+ }
+
+ FF_STRBUF_AUTO_DESTROY rawSense = ffStrbufCreate();
+ for (size_t i = 0; i < req->senselen_used; ++i) {
+ if (i) {
+ ffStrbufAppendC(&rawSense, ' ');
+ }
+ ffStrbufAppendF(&rawSense, "%02X", req->sense[i]);
+ }
+
+ FF_DEBUG(
+ "%s for %s reported SENSE: response=0x%02X, key=0x%X (%s), ASC=0x%02X, ASCQ=0x%02X, raw=[%s]",
+ operation,
+ diskName,
+ responseCode,
+ senseKey,
+ senseKeyToStr(senseKey),
+ asc,
+ ascq,
+ rawSense.length ? rawSense.chars : "empty");
+}
+#else
+ #define logScsiSense(...) ((void) 0)
+#endif
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_STRBUF_AUTO_DESTROY diskNames = ffStrbufCreate();
+ FF_DEBUG("Querying disk names via sysctl hw.disknames");
+ const char* error = ffSysctlGetString(CTL_HW, HW_DISKNAMES, &diskNames);
+ if (error) {
+ FF_DEBUG("ffSysctlGetString(HW_DISKNAMES) failed: %s", error);
+ return error;
+ }
+
+ FF_DEBUG("Disk names: %s", diskNames.chars);
+
+ char* diskName = NULL;
+ size_t len = 0;
+ while (ffStrbufGetdelim(&diskName, &len, ',', &diskNames)) {
+ char* colon = strchr(diskName, ':');
+ if (colon) {
+ *colon = '\0';
+ }
+
+ FF_DEBUG("Probing disk: %s", diskName);
+
+ if (options->namePrefix.length && !ffStrbufStartsWith(&(FFstrbuf) {
+ .chars = diskName,
+ .length = (uint32_t) len,
+ },
+ &options->namePrefix)) {
+ FF_DEBUG("Skipping %s due to namePrefix filter", diskName);
+ continue;
+ }
+
+ char* devPath = NULL;
+ FF_AUTO_CLOSE_FD int f = opendev(diskName, O_RDONLY, OPENDEV_PART, &devPath);
+ if (f < 0) {
+ if (errno == EACCES) {
+ FF_DEBUG("opendev(%s) failed: permission denied", diskName);
+ return "Permission denied; root required";
+ } else {
+ FF_DEBUG("opendev(%s) failed: %s", diskName, strerror(errno));
+ continue; // Unknown error
+ }
+ }
+
+ FF_DEBUG("Opened %s as %s", diskName, devPath);
+
+ struct disklabel dl;
+ if (ioctl(f, DIOCGPDINFO, &dl) < 0) {
+ FF_DEBUG("ioctl(DIOCGPDINFO) failed for %s: %s", diskName, strerror(errno));
+ continue;
+ }
+
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+ if (dl.d_type == DTYPE_VND || dl.d_type == DTYPE_RDROOT) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
+ FF_DEBUG("Skipping virtual disk %s due to hideType", diskName);
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ }
+
+ uint64_t size = DL_GETDSIZE(&dl) * dl.d_secsize;
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ FF_DEBUG("Skipping unused disk %s due to hideType", diskName);
+ continue;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInitS(&device->name, dl.d_packname);
+ ffStrbufInitS(&device->devPath, devPath);
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInitS(&device->interconnect, dl.d_typename);
+ device->type = type;
+ device->size = size;
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+ FF_DEBUG("Added disk entry: name='%s', devPath='%s', interconnect='%s', size=%llu",
+ device->name.chars,
+ device->devPath.chars,
+ device->interconnect.chars,
+ (unsigned long long) device->size);
+
+ struct scsi_inquiry_data inquiry = {};
+ struct scsireq req = {
+ .cmd = { [0] = INQUIRY, [4] = sizeof(inquiry) },
+ .cmdlen = 6,
+ .databuf = (caddr_t) &inquiry,
+ .datalen = sizeof(inquiry),
+ .timeout = 1000,
+ .flags = SCCMD_READ,
+ };
+ if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
+ if (req.retsts == SCCMD_OK) {
+ ffStrbufClear(&device->name);
+ ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.vendor), inquiry.vendor);
+ ffStrbufTrimRight(&device->name, '\0');
+ ffStrbufTrimRight(&device->name, ' ');
+ ffStrbufAppendC(&device->name, ' ');
+ ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.product), inquiry.product);
+ ffStrbufTrimRight(&device->name, '\0');
+ ffStrbufTrimRight(&device->name, ' ');
+
+ ffStrbufSetNS(&device->revision, (uint32_t) ARRAY_SIZE(inquiry.revision), inquiry.revision);
+ ffStrbufTrimRight(&device->revision, '\0');
+ ffStrbufTrimRight(&device->revision, ' ');
+ device->type |= inquiry.dev_qual2 & SID_REMOVABLE ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
+ FF_DEBUG("SCSI inquiry for %s: name='%s', revision='%s'", diskName, device->name.chars, device->revision.chars);
+ } else {
+ FF_DEBUG("SCSI inquiry retsts != SCCMD_OK for %s (%d)", diskName, req.retsts);
+ }
+ } else {
+ FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI inquiry on %s: %s", diskName, strerror(errno));
+ }
+
+ struct scsi_vpd_serial evpd = {};
+ req = (struct scsireq) {
+ .cmd = { [0] = INQUIRY, [1] = SI_EVPD, [2] = SI_PG_SERIAL, [4] = sizeof(evpd) },
+ .cmdlen = 6,
+ .databuf = (caddr_t) &evpd,
+ .datalen = sizeof(evpd),
+ .timeout = 1000,
+ .flags = SCCMD_READ,
+ };
+ if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
+ if (req.retsts == SCCMD_OK && evpd.hdr.page_code == SI_PG_SERIAL) {
+ for (uint8_t i = 0; i < evpd.hdr.page_length[1]; ++i) {
+ ffStrbufAppendF(&device->serial, "%02X", evpd.serial[i]);
+ }
+ ffStrbufTrimSpace(&device->serial);
+ FF_DEBUG("SCSI serial for %s: %s", diskName, device->serial.chars);
+ } else if (req.retsts == SCCMD_SENSE) {
+ logScsiSense(diskName, "SCSI serial page", &req);
+ } else {
+ FF_DEBUG("SCSI serial page unavailable for %s (retsts=%s, pagecode=%u)", diskName, retstsToStr(req.retsts), (unsigned) evpd.hdr.page_code);
+ }
+ } else {
+ FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI serial on %s: %s", diskName, strerror(errno));
+ }
+
+ struct scsi_mode_header mode = {};
+ req = (scsireq_t) {
+ .cmd = { [0] = MODE_SENSE, [2] = SMS_PAGE_CODE, [4] = sizeof(mode) },
+ .cmdlen = 6,
+ .databuf = (caddr_t) &mode,
+ .datalen = sizeof(mode),
+ .timeout = 1000,
+ .flags = SCCMD_READ,
+ };
+ if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
+ if (req.retsts == SCCMD_OK) {
+ device->type |= mode.dev_spec & 0x80 ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE;
+ FF_DEBUG("SCSI mode for %s indicates: %s", diskName, mode.dev_spec & 0x80 ? "readonly" : "readwrite");
+ } else if (req.retsts == SCCMD_SENSE) {
+ logScsiSense(diskName, "SCSI mode sense", &req);
+ } else {
+ FF_DEBUG("SCSI mode sense unavailable for %s (retsts=%s, data_length=%u)", diskName, retstsToStr(req.retsts), (unsigned) mode.data_length);
+ }
+ } else {
+ FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI mode sense on %s: %s", diskName, strerror(errno));
+ }
+
+ FF_DEBUG("Detected disk '%s' (%s), type=%u", device->name.chars, diskName, (unsigned) device->type);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/physicaldisk/physicaldisk_sunos.c b/src/detection/physicaldisk/physicaldisk_sunos.c
new file mode 100644
index 0000000..d929d5a
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_sunos.c
@@ -0,0 +1,107 @@
+#include "physicaldisk.h"
+#include "common/strutil.h"
+#include "sys/scsi/generic/inquiry.h"
+
+#include <libdevinfo.h>
+#include <sys/stat.h>
+
+struct FFWalkTreeBundle {
+ FFPhysicalDiskOptions* options;
+ FFlist* disks;
+};
+
+static int walkDevTree(di_node_t node, di_minor_t minor, struct FFWalkTreeBundle* bundle) {
+ FFPhysicalDiskOptions* options = bundle->options;
+ FFlist* result = bundle->disks;
+
+ if (di_minor_spectype(minor) != S_IFCHR || !ffStrEquals(di_minor_name(minor), "a,raw")) {
+ return DI_WALK_CONTINUE;
+ }
+
+ char* productId;
+ char* vendorId;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-product-id", &productId) > 0 && di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-vendor-id", &vendorId) > 0) {
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateF("%s %s", vendorId, productId);
+ if (options->namePrefix.length && !ffStrbufStartsWithIgnCase(&name, &options->namePrefix)) {
+ return DI_WALK_CONTINUE;
+ }
+
+ int* value;
+
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+ uint64_t size = 0;
+ int64_t* nblocks;
+ if (di_prop_lookup_int64(DDI_DEV_T_ANY, node, "device-nblocks", &nblocks) > 0) {
+ if (*nblocks == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ return DI_WALK_CONTINUE;
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-blksize", &value) > 0) {
+ size = (uint64_t) ((uint64_t) *nblocks * (uint64_t) *value);
+ }
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInitMove(&device->name, &name);
+ ffStrbufInitF(&device->devPath, "/devices%s", di_devfs_path(node));
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInit(&device->interconnect);
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+ device->type = type;
+ device->size = size;
+
+ char* buf;
+ bool usb = false;
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-serial-no", &buf) > 0) {
+ ffStrbufSetS(&device->serial, buf);
+ ffStrbufTrimSpace(&device->serial);
+ } else {
+ di_node_t parent = di_parent_node(node);
+ if (parent != DI_NODE_NIL && di_prop_lookup_strings(DDI_DEV_T_ANY, parent, "usb-serialno", &buf) > 0) {
+ ffStrbufSetS(&device->serial, buf);
+ usb = true;
+ }
+ }
+ if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "inquiry-revision-id", &buf) > 0) {
+ ffStrbufSetS(&device->revision, buf);
+ ffStrbufTrimRightSpace(&device->revision);
+ }
+
+ if (usb) {
+ ffStrbufSetStatic(&device->interconnect, "USB");
+ } else if (di_prop_lookup_strings(DDI_DEV_T_ANY, node, "class", &buf) > 0) {
+ ffStrbufSetS(&device->interconnect, buf);
+ } else {
+ di_node_t parent = di_parent_node(node);
+ if (parent != DI_NODE_NIL && di_prop_lookup_strings(DDI_DEV_T_ANY, parent, "model", &buf) > 0) {
+ ffStrbufSetS(&device->interconnect, buf);
+ }
+ }
+
+ device->type |= di_prop_find(DDI_DEV_T_ANY, node, "removable-media") ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
+
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-solid-state", &value) > 0) {
+ device->type |= *value ? FF_PHYSICALDISK_TYPE_SSD : FF_PHYSICALDISK_TYPE_HDD;
+ }
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "inquiry-device-type", &value) > 0) {
+ device->type |= *value == DTYPE_DIRECT ? FF_PHYSICALDISK_TYPE_READWRITE : *value == DTYPE_RODIRECT ? FF_PHYSICALDISK_TYPE_READONLY
+ : 0;
+ }
+ }
+
+ return DI_WALK_CONTINUE;
+}
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ di_node_t rootNode = di_init("/", DINFOCPYALL);
+ if (rootNode == DI_NODE_NIL) {
+ return "di_init() failed";
+ }
+ di_walk_minor(rootNode, DDI_NT_BLOCK, DI_WALK_CLDFIRST, &(struct FFWalkTreeBundle) { options, result }, (void*) walkDevTree);
+ di_fini(rootNode);
+
+ return NULL;
+}
diff --git a/src/detection/physicaldisk/physicaldisk_windows.c b/src/detection/physicaldisk/physicaldisk_windows.c
new file mode 100644
index 0000000..5d4f7f7
--- /dev/null
+++ b/src/detection/physicaldisk/physicaldisk_windows.c
@@ -0,0 +1,349 @@
+#include "physicaldisk.h"
+#include "common/io.h"
+#include "common/windows/unicode.h"
+#include "common/mallocHelper.h"
+#include "common/debug.h"
+
+#include <stdalign.h>
+#include <windows.h>
+#include <winioctl.h>
+#include <cfgmgr32.h>
+
+static const char* detectPhysicalDisk(const char* physicalType, const wchar_t* szDevice, FFlist* result, FFPhysicalDiskOptions* options) {
+ FF_AUTO_CLOSE_FD HANDLE hDevice = CreateFileW(szDevice, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
+ if (hDevice == INVALID_HANDLE_VALUE) {
+ return "CreateFileW() failed";
+ }
+
+ DWORD retSize;
+ FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
+ FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
+
+ const char* interconnect = NULL;
+ uint64_t size = 0;
+ {
+ alignas(DISK_GEOMETRY_EX) uint8_t dgeBuffer[4096];
+ if (DeviceIoControl(
+ hDevice,
+ IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
+ NULL,
+ 0,
+ dgeBuffer,
+ sizeof(dgeBuffer),
+ &retSize,
+ NULL)) {
+ const DISK_GEOMETRY_EX* dge = (const DISK_GEOMETRY_EX*) dgeBuffer;
+ size = (uint64_t) dge->DiskSize.QuadPart;
+ } else if (DeviceIoControl(
+ hDevice,
+ IOCTL_DISK_GET_DRIVE_GEOMETRY,
+ NULL,
+ 0,
+ dgeBuffer,
+ sizeof(dgeBuffer),
+ &retSize,
+ NULL) &&
+ retSize >= sizeof(DISK_GEOMETRY)) {
+ const DISK_GEOMETRY* dg = (const DISK_GEOMETRY*) dgeBuffer;
+ size = (uint64_t) dg->BytesPerSector * dg->SectorsPerTrack * dg->TracksPerCylinder * (uint64_t) dg->Cylinders.QuadPart;
+ switch (dg->MediaType) {
+ case F3_1Pt44_512:
+ case F3_2Pt88_512:
+ case F3_20Pt8_512:
+ case F3_720_512:
+ case F3_120M_512:
+ case F3_640_512:
+ case F3_1Pt2_512:
+ case F3_1Pt23_1024:
+ case F3_128Mb_512:
+ case F3_230Mb_512:
+ case F3_200Mb_512:
+ case F3_240M_512:
+ case F3_32M_512:
+ ffStrbufSetStatic(&name, "3.5-inch Floppy Disk");
+ break;
+ case F5_1Pt2_512:
+ case F5_360_512:
+ case F5_320_512:
+ case F5_320_1024:
+ case F5_180_512:
+ case F5_160_512:
+ case F5_640_512:
+ case F5_720_512:
+ case F5_1Pt23_1024:
+ ffStrbufSetStatic(&name, "5.25-inch Floppy Disk");
+ break;
+ case F8_256_128:
+ ffStrbufSetStatic(&name, "8-inch Floppy Disk");
+ break;
+ default:
+ return "Unsupported media type";
+ }
+ interconnect = "Floppy Controller";
+ type |= FF_PHYSICALDISK_TYPE_HDD | FF_PHYSICALDISK_TYPE_REMOVABLE;
+ }
+ }
+ if (size == 0) {
+ if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
+ return "Skipping unknown disk with size 0";
+ }
+
+ type |= FF_PHYSICALDISK_TYPE_UNUSED;
+ }
+
+ const STORAGE_DEVICE_DESCRIPTOR* sdd = NULL;
+ alignas(STORAGE_DEVICE_DESCRIPTOR) uint8_t sddBuffer[4096];
+ if (!interconnect) {
+ if (DeviceIoControl(
+ hDevice,
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &(STORAGE_PROPERTY_QUERY) {
+ .PropertyId = StorageDeviceProperty,
+ .QueryType = PropertyStandardQuery,
+ },
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &sddBuffer,
+ sizeof(sddBuffer),
+ &retSize,
+ NULL) ||
+ retSize == 0) {
+ sdd = (const STORAGE_DEVICE_DESCRIPTOR*) sddBuffer;
+
+ switch (sdd->BusType) {
+ case BusTypeScsi:
+ interconnect = "SCSI";
+ break;
+ case BusTypeAtapi:
+ interconnect = "ATAPI";
+ break;
+ case BusTypeAta:
+ interconnect = "ATA";
+ break;
+ case BusType1394:
+ interconnect = "IEEE 1394";
+ break;
+ case BusTypeSsa:
+ interconnect = "SSA";
+ break;
+ case BusTypeFibre:
+ interconnect = "Fibre";
+ break;
+ case BusTypeUsb:
+ interconnect = "USB";
+ break;
+ case BusTypeRAID:
+ interconnect = "RAID";
+ break;
+ case BusTypeiScsi:
+ interconnect = "iSCSI";
+ break;
+ case BusTypeSas:
+ interconnect = "SAS";
+ break;
+ case BusTypeSata:
+ interconnect = "SATA";
+ break;
+ case BusTypeSd:
+ interconnect = "SD";
+ break;
+ case BusTypeMmc:
+ interconnect = "MMC";
+ break;
+ case BusTypeVirtual:
+ interconnect = "Virtual";
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ break;
+ case BusTypeFileBackedVirtual:
+ interconnect = "File Backed Virtual";
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ break;
+ case BusTypeSpaces:
+ interconnect = "Storage Spaces";
+ type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
+ break;
+ case BusTypeNvme:
+ interconnect = "NVMe";
+ break;
+ case BusTypeSCM:
+ interconnect = "SCM";
+ break;
+ case BusTypeUfs:
+ interconnect = "UFS";
+ break;
+ case 0x14 /*BusTypeNvmeof*/:
+ interconnect = "NVMe-oF";
+ break;
+ default:
+ interconnect = "Unknown";
+ break;
+ }
+
+ if (type & FF_PHYSICALDISK_TYPE_VIRTUAL && options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
+ return "Skipping virtual disk";
+ }
+
+ if (sdd->VendorIdOffset != 0) {
+ ffStrbufSetS(&name, (const char*) sdd + sdd->VendorIdOffset);
+ ffStrbufTrim(&name, ' ');
+ }
+ if (sdd->ProductIdOffset != 0) {
+ if (name.length) {
+ ffStrbufAppendC(&name, ' ');
+ }
+
+ ffStrbufAppendS(&name, (const char*) sdd + sdd->ProductIdOffset);
+ ffStrbufTrimRight(&name, ' ');
+ }
+ }
+ if (!name.length) {
+ ffStrbufSetStatic(&name, physicalType);
+ }
+ }
+
+ if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
+ return "Name prefix mismatch";
+ }
+
+ FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
+ ffStrbufInit(&device->serial);
+ ffStrbufInit(&device->revision);
+ ffStrbufInitMove(&device->name, &name);
+ ffStrbufInit(&device->devPath);
+ ffStrbufInitStatic(&device->interconnect, interconnect);
+ device->type = type;
+ device->size = size;
+ device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
+
+ ffStrbufSetWS(&device->devPath, szDevice);
+
+ if (sdd) {
+ if (sdd->SerialNumberOffset != 0) {
+ ffStrbufSetS(&device->serial, (const char*) sdd + sdd->SerialNumberOffset);
+ ffStrbufTrimSpace(&device->serial);
+ }
+
+ if (sdd->ProductRevisionOffset != 0) {
+ ffStrbufSetS(&device->revision, (const char*) sdd + sdd->ProductRevisionOffset);
+ ffStrbufTrimRightSpace(&device->revision);
+ }
+
+ device->type |= sdd->RemovableMedia ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
+ }
+
+ {
+ alignas(GET_MEDIA_TYPES) uint8_t buffer[4096];
+ GET_MEDIA_TYPES* gmt = (GET_MEDIA_TYPES*) buffer;
+ if (DeviceIoControl(
+ hDevice,
+ IOCTL_STORAGE_GET_MEDIA_TYPES_EX,
+ NULL,
+ 0,
+ gmt,
+ sizeof(buffer),
+ &retSize,
+ NULL) &&
+ gmt->MediaInfoCount > 0) {
+ // DiskInfo and RemovableDiskInfo have the same structures. TapeInfo doesn't.
+ if (gmt->DeviceType != FILE_DEVICE_TAPE) {
+ __auto_type diskInfo = &gmt->MediaInfo[0].DeviceSpecific.DiskInfo;
+ if (diskInfo->MediaCharacteristics & MEDIA_READ_ONLY) {
+ device->type |= FF_PHYSICALDISK_TYPE_READONLY;
+ } else if (diskInfo->MediaCharacteristics & MEDIA_READ_WRITE) {
+ device->type |= FF_PHYSICALDISK_TYPE_READWRITE;
+ }
+ } else {
+ __auto_type tapeInfo = &gmt->MediaInfo[0].DeviceSpecific.TapeInfo;
+ if (tapeInfo->MediaCharacteristics & MEDIA_READ_ONLY) {
+ device->type |= FF_PHYSICALDISK_TYPE_READONLY;
+ } else if (tapeInfo->MediaCharacteristics & MEDIA_READ_WRITE) {
+ device->type |= FF_PHYSICALDISK_TYPE_READWRITE;
+ }
+ }
+ }
+ }
+
+ if (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL) && !(device->type & FF_PHYSICALDISK_TYPE_HDD)) {
+ DEVICE_SEEK_PENALTY_DESCRIPTOR dspd = {};
+ if (DeviceIoControl(
+ hDevice,
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &(STORAGE_PROPERTY_QUERY) {
+ .PropertyId = StorageDeviceSeekPenaltyProperty,
+ .QueryType = PropertyStandardQuery,
+ },
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &dspd,
+ sizeof(dspd),
+ &retSize,
+ NULL) &&
+ retSize == sizeof(dspd)) {
+ device->type |= dspd.IncursSeekPenalty ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD;
+ }
+
+ if (options->temp) {
+ STORAGE_TEMPERATURE_DATA_DESCRIPTOR stdd = {};
+ if (DeviceIoControl(
+ hDevice,
+ IOCTL_STORAGE_QUERY_PROPERTY,
+ &(STORAGE_PROPERTY_QUERY) {
+ .PropertyId = StorageDeviceTemperatureProperty,
+ .QueryType = PropertyStandardQuery,
+ },
+ sizeof(STORAGE_PROPERTY_QUERY),
+ &stdd,
+ sizeof(stdd),
+ &retSize,
+ NULL) &&
+ retSize == sizeof(stdd)) {
+ device->temperature = stdd.TemperatureInfo[0].Temperature;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static void detectPhysicalDisksByInterfaceClass(const char* type, const GUID* interfaceClassGuid, FFlist* result, FFPhysicalDiskOptions* options) {
+ ULONG cchDeviceInterfaces = 0;
+ if (CM_Get_Device_Interface_List_SizeW(
+ &cchDeviceInterfaces,
+ (LPGUID) interfaceClassGuid,
+ NULL,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS ||
+ cchDeviceInterfaces <= 1) {
+ return;
+ }
+
+ wchar_t* FF_AUTO_FREE mszDeviceInterfaces = (wchar_t*) malloc(cchDeviceInterfaces * sizeof(wchar_t));
+ if (!mszDeviceInterfaces) {
+ return;
+ }
+
+ if (CM_Get_Device_Interface_ListW(
+ (LPGUID) interfaceClassGuid,
+ NULL,
+ mszDeviceInterfaces,
+ cchDeviceInterfaces,
+ CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) {
+ return;
+ }
+
+ // MULTI_SZ: "str1\0str2\0...\0\0"
+ for (const wchar_t* p = mszDeviceInterfaces; *p; p += wcslen(p) + 1) {
+ FF_DEBUG("Probing %s: %ls", type, p);
+ FF_A_UNUSED const char* error = detectPhysicalDisk(type, p, result, options);
+ if (error == NULL) {
+ FF_DEBUG("Detected device \"%s\"", FF_LIST_LAST(FFPhysicalDiskResult, *result)->name.chars);
+ } else {
+ FF_DEBUG("Failed to detect device %s: %s", type, error);
+ }
+ }
+}
+
+const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
+ detectPhysicalDisksByInterfaceClass("Floppy", &GUID_DEVINTERFACE_FLOPPY, result, options);
+ detectPhysicalDisksByInterfaceClass("Disk", &GUID_DEVINTERFACE_DISK, result, options);
+ detectPhysicalDisksByInterfaceClass("CD-ROM", &GUID_DEVINTERFACE_CDROM, result, options);
+ detectPhysicalDisksByInterfaceClass("Tape", &GUID_DEVINTERFACE_TAPE, result, options);
+ return NULL;
+}