master ohmu/fastfetch-openbsd / src / detection / physicaldisk / physicaldisk_windows.c
ohmu  ·  2026-10-15
  1#include "physicaldisk.h"
  2#include "common/io.h"
  3#include "common/windows/unicode.h"
  4#include "common/mallocHelper.h"
  5#include "common/debug.h"
  6
  7#include <stdalign.h>
  8#include <windows.h>
  9#include <winioctl.h>
 10#include <cfgmgr32.h>
 11
 12static const char* detectPhysicalDisk(const char* physicalType, const wchar_t* szDevice, FFlist* result, FFPhysicalDiskOptions* options) {
 13    FF_AUTO_CLOSE_FD HANDLE hDevice = CreateFileW(szDevice, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
 14    if (hDevice == INVALID_HANDLE_VALUE) {
 15        FF_DEBUG("CreateFileW() failed: %s", ffDebugWin32Error(GetLastError()));
 16        return "CreateFileW() failed";
 17    }
 18
 19    DWORD retSize;
 20    FFPhysicalDiskType type = FF_PHYSICALDISK_TYPE_NONE;
 21    FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate();
 22
 23    const char* interconnect = nullptr;
 24    uint64_t size = 0;
 25    {
 26        alignas(DISK_GEOMETRY_EX) uint8_t dgeBuffer[4096];
 27        if (DeviceIoControl(
 28                hDevice,
 29                IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
 30                nullptr,
 31                0,
 32                dgeBuffer,
 33                sizeof(dgeBuffer),
 34                &retSize,
 35                nullptr)) {
 36            const DISK_GEOMETRY_EX* dge = (const DISK_GEOMETRY_EX*) dgeBuffer;
 37            size = (uint64_t) dge->DiskSize.QuadPart;
 38        } else if (DeviceIoControl(
 39                       hDevice,
 40                       IOCTL_DISK_GET_DRIVE_GEOMETRY,
 41                       nullptr,
 42                       0,
 43                       dgeBuffer,
 44                       sizeof(dgeBuffer),
 45                       &retSize,
 46                       nullptr) &&
 47            retSize >= sizeof(DISK_GEOMETRY)) {
 48            const DISK_GEOMETRY* dg = (const DISK_GEOMETRY*) dgeBuffer;
 49            size = (uint64_t) dg->BytesPerSector * dg->SectorsPerTrack * dg->TracksPerCylinder * (uint64_t) dg->Cylinders.QuadPart;
 50            switch (dg->MediaType) {
 51                case F3_1Pt44_512:
 52                case F3_2Pt88_512:
 53                case F3_20Pt8_512:
 54                case F3_720_512:
 55                case F3_120M_512:
 56                case F3_640_512:
 57                case F3_1Pt2_512:
 58                case F3_1Pt23_1024:
 59                case F3_128Mb_512:
 60                case F3_230Mb_512:
 61                case F3_200Mb_512:
 62                case F3_240M_512:
 63                case F3_32M_512:
 64                    ffStrbufSetStatic(&name, "3.5-inch Floppy Disk");
 65                    break;
 66                case F5_1Pt2_512:
 67                case F5_360_512:
 68                case F5_320_512:
 69                case F5_320_1024:
 70                case F5_180_512:
 71                case F5_160_512:
 72                case F5_640_512:
 73                case F5_720_512:
 74                case F5_1Pt23_1024:
 75                    ffStrbufSetStatic(&name, "5.25-inch Floppy Disk");
 76                    break;
 77                case F8_256_128:
 78                    ffStrbufSetStatic(&name, "8-inch Floppy Disk");
 79                    break;
 80                default:
 81                    return "Unsupported media type";
 82            }
 83            interconnect = "Floppy Controller";
 84            type |= FF_PHYSICALDISK_TYPE_HDD | FF_PHYSICALDISK_TYPE_REMOVABLE;
 85        }
 86    }
 87    if (size == 0) {
 88        if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
 89            return "Skipping unknown disk with size 0";
 90        }
 91
 92        type |= FF_PHYSICALDISK_TYPE_UNUSED;
 93    }
 94
 95    const STORAGE_DEVICE_DESCRIPTOR* sdd = nullptr;
 96    alignas(STORAGE_DEVICE_DESCRIPTOR) uint8_t sddBuffer[4096];
 97    if (!interconnect) {
 98        if (DeviceIoControl(
 99                hDevice,
100                IOCTL_STORAGE_QUERY_PROPERTY,
101                &(STORAGE_PROPERTY_QUERY) {
102                    .PropertyId = StorageDeviceProperty,
103                    .QueryType = PropertyStandardQuery,
104                },
105                sizeof(STORAGE_PROPERTY_QUERY),
106                &sddBuffer,
107                sizeof(sddBuffer),
108                &retSize,
109                nullptr) ||
110            retSize == 0) {
111            sdd = (const STORAGE_DEVICE_DESCRIPTOR*) sddBuffer;
112
113            switch (sdd->BusType) {
114                case BusTypeScsi:
115                    interconnect = "SCSI";
116                    break;
117                case BusTypeAtapi:
118                    interconnect = "ATAPI";
119                    break;
120                case BusTypeAta:
121                    interconnect = "ATA";
122                    break;
123                case BusType1394:
124                    interconnect = "IEEE 1394";
125                    break;
126                case BusTypeSsa:
127                    interconnect = "SSA";
128                    break;
129                case BusTypeFibre:
130                    interconnect = "Fibre";
131                    break;
132                case BusTypeUsb:
133                    interconnect = "USB";
134                    break;
135                case BusTypeRAID:
136                    interconnect = "RAID";
137                    break;
138                case BusTypeiScsi:
139                    interconnect = "iSCSI";
140                    break;
141                case BusTypeSas:
142                    interconnect = "SAS";
143                    break;
144                case BusTypeSata:
145                    interconnect = "SATA";
146                    break;
147                case BusTypeSd:
148                    interconnect = "SD";
149                    break;
150                case BusTypeMmc:
151                    interconnect = "MMC";
152                    break;
153                case BusTypeVirtual:
154                    interconnect = "Virtual";
155                    type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
156                    break;
157                case BusTypeFileBackedVirtual:
158                    interconnect = "File Backed Virtual";
159                    type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
160                    break;
161                case BusTypeSpaces:
162                    interconnect = "Storage Spaces";
163                    type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
164                    break;
165                case BusTypeNvme:
166                    interconnect = "NVMe";
167                    break;
168                case BusTypeSCM:
169                    interconnect = "SCM";
170                    break;
171                case BusTypeUfs:
172                    interconnect = "UFS";
173                    break;
174                case 0x14 /*BusTypeNvmeof*/:
175                    interconnect = "NVMe-oF";
176                    break;
177                default:
178                    interconnect = "Unknown";
179                    break;
180            }
181
182            if (type & FF_PHYSICALDISK_TYPE_VIRTUAL && options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
183                return "Skipping virtual disk";
184            }
185
186            if (sdd->VendorIdOffset != 0) {
187                ffStrbufSetS(&name, (const char*) sdd + sdd->VendorIdOffset);
188                ffStrbufTrim(&name, ' ');
189            }
190            if (sdd->ProductIdOffset != 0) {
191                if (name.length) {
192                    ffStrbufAppendC(&name, ' ');
193                }
194
195                ffStrbufAppendS(&name, (const char*) sdd + sdd->ProductIdOffset);
196                ffStrbufTrimRight(&name, ' ');
197            }
198        }
199        if (!name.length) {
200            ffStrbufSetStatic(&name, physicalType);
201        }
202    }
203
204    if (options->namePrefix.length && !ffStrbufStartsWith(&name, &options->namePrefix)) {
205        return "Name prefix mismatch";
206    }
207
208    FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
209    ffStrbufInit(&device->serial);
210    ffStrbufInit(&device->revision);
211    ffStrbufInitMove(&device->name, &name);
212    ffStrbufInit(&device->devPath);
213    ffStrbufInitStatic(&device->interconnect, interconnect);
214    device->type = type;
215    device->size = size;
216    device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
217
218    ffStrbufSetWS(&device->devPath, szDevice);
219
220    if (sdd) {
221        if (sdd->SerialNumberOffset != 0) {
222            ffStrbufSetS(&device->serial, (const char*) sdd + sdd->SerialNumberOffset);
223            ffStrbufTrimSpace(&device->serial);
224        }
225
226        if (sdd->ProductRevisionOffset != 0) {
227            ffStrbufSetS(&device->revision, (const char*) sdd + sdd->ProductRevisionOffset);
228            ffStrbufTrimRightSpace(&device->revision);
229        }
230
231        device->type |= sdd->RemovableMedia ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
232    }
233
234    {
235        alignas(GET_MEDIA_TYPES) uint8_t buffer[4096];
236        GET_MEDIA_TYPES* gmt = (GET_MEDIA_TYPES*) buffer;
237        if (DeviceIoControl(
238                hDevice,
239                IOCTL_STORAGE_GET_MEDIA_TYPES_EX,
240                nullptr,
241                0,
242                gmt,
243                sizeof(buffer),
244                &retSize,
245                nullptr) &&
246            gmt->MediaInfoCount > 0) {
247            // DiskInfo and RemovableDiskInfo have the same structures. TapeInfo doesn't.
248            if (gmt->DeviceType != FILE_DEVICE_TAPE) {
249                auto diskInfo = &gmt->MediaInfo[0].DeviceSpecific.DiskInfo;
250                if (diskInfo->MediaCharacteristics & MEDIA_READ_ONLY) {
251                    device->type |= FF_PHYSICALDISK_TYPE_READONLY;
252                } else if (diskInfo->MediaCharacteristics & MEDIA_READ_WRITE) {
253                    device->type |= FF_PHYSICALDISK_TYPE_READWRITE;
254                }
255            } else {
256                auto tapeInfo = &gmt->MediaInfo[0].DeviceSpecific.TapeInfo;
257                if (tapeInfo->MediaCharacteristics & MEDIA_READ_ONLY) {
258                    device->type |= FF_PHYSICALDISK_TYPE_READONLY;
259                } else if (tapeInfo->MediaCharacteristics & MEDIA_READ_WRITE) {
260                    device->type |= FF_PHYSICALDISK_TYPE_READWRITE;
261                }
262            }
263        }
264    }
265
266    if (!(device->type & FF_PHYSICALDISK_TYPE_VIRTUAL) && !(device->type & FF_PHYSICALDISK_TYPE_HDD)) {
267        DEVICE_SEEK_PENALTY_DESCRIPTOR dspd = {};
268        if (DeviceIoControl(
269                hDevice,
270                IOCTL_STORAGE_QUERY_PROPERTY,
271                &(STORAGE_PROPERTY_QUERY) {
272                    .PropertyId = StorageDeviceSeekPenaltyProperty,
273                    .QueryType = PropertyStandardQuery,
274                },
275                sizeof(STORAGE_PROPERTY_QUERY),
276                &dspd,
277                sizeof(dspd),
278                &retSize,
279                nullptr) &&
280            retSize == sizeof(dspd)) {
281            device->type |= dspd.IncursSeekPenalty ? FF_PHYSICALDISK_TYPE_HDD : FF_PHYSICALDISK_TYPE_SSD;
282        }
283
284        if (options->temp) {
285            STORAGE_TEMPERATURE_DATA_DESCRIPTOR stdd = {};
286            if (DeviceIoControl(
287                    hDevice,
288                    IOCTL_STORAGE_QUERY_PROPERTY,
289                    &(STORAGE_PROPERTY_QUERY) {
290                        .PropertyId = StorageDeviceTemperatureProperty,
291                        .QueryType = PropertyStandardQuery,
292                    },
293                    sizeof(STORAGE_PROPERTY_QUERY),
294                    &stdd,
295                    sizeof(stdd),
296                    &retSize,
297                    nullptr) &&
298                retSize == sizeof(stdd)) {
299                device->temperature = stdd.TemperatureInfo[0].Temperature;
300            }
301        }
302    }
303
304    return nullptr;
305}
306
307static void detectPhysicalDisksByInterfaceClass(const char* type, const GUID* interfaceClassGuid, FFlist* result, FFPhysicalDiskOptions* options) {
308    ULONG cchDeviceInterfaces = 0;
309    if (CM_Get_Device_Interface_List_SizeW(
310            &cchDeviceInterfaces,
311            (LPGUID) interfaceClassGuid,
312            nullptr,
313            CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS ||
314        cchDeviceInterfaces <= 1) {
315        return;
316    }
317
318    FF_AUTO_FREE wchar_t* mszDeviceInterfaces = (wchar_t*) malloc(cchDeviceInterfaces * sizeof(wchar_t));
319    if (!mszDeviceInterfaces) {
320        return;
321    }
322
323    if (CM_Get_Device_Interface_ListW(
324            (LPGUID) interfaceClassGuid,
325            nullptr,
326            mszDeviceInterfaces,
327            cchDeviceInterfaces,
328            CM_GET_DEVICE_INTERFACE_LIST_PRESENT) != CR_SUCCESS) {
329        return;
330    }
331
332    // MULTI_SZ: "str1\0str2\0...\0\0"
333    for (const wchar_t* p = mszDeviceInterfaces; *p; p += wcslen(p) + 1) {
334        FF_DEBUG("Probing %s: %ls", type, p);
335        [[maybe_unused]] const char* error = detectPhysicalDisk(type, p, result, options);
336        if (error == nullptr) {
337            FF_DEBUG("Detected device \"%s\"", FF_LIST_LAST(FFPhysicalDiskResult, *result)->name.chars);
338        } else {
339            FF_DEBUG("Failed to detect device %s: %s", type, error);
340        }
341    }
342}
343
344const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
345    detectPhysicalDisksByInterfaceClass("Floppy", &GUID_DEVINTERFACE_FLOPPY, result, options);
346    detectPhysicalDisksByInterfaceClass("Disk", &GUID_DEVINTERFACE_DISK, result, options);
347    detectPhysicalDisksByInterfaceClass("CD-ROM", &GUID_DEVINTERFACE_CDROM, result, options);
348    detectPhysicalDisksByInterfaceClass("Tape", &GUID_DEVINTERFACE_TAPE, result, options);
349    return nullptr;
350}