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}