ohmu
·
2026-10-15
1#define DKTYPENAMES
2#include "physicaldisk.h"
3#include "common/io.h"
4#include "common/sysctl.h"
5#include "common/debug.h"
6
7#include <util.h>
8#include <prop/proplib.h>
9#include <sys/ioctl.h>
10#include <sys/disklabel.h>
11#include <sys/dkio.h>
12#include <sys/scsiio.h>
13#include <dev/scsipi/scsipi_all.h>
14#include <dev/scsipi/scsi_spc.h>
15#include <fcntl.h>
16#include <errno.h>
17
18static inline const char* retstsToStr(uint8_t retsts) {
19 switch (retsts) {
20 case SCCMD_OK:
21 return "OK";
22 case SCCMD_TIMEOUT:
23 return "TIMEOUT";
24 case SCCMD_BUSY:
25 return "BUSY";
26 case SCCMD_SENSE:
27 return "SENSE";
28 case SCCMD_UNKNOWN:
29 return "UNKNOWN";
30 default:
31 return "?";
32 }
33}
34
35#ifndef NDEBUG
36static inline const char* senseKeyToStr(uint8_t key) {
37 switch (key) {
38 case 0x0:
39 return "No Sense";
40 case 0x1:
41 return "Recovered Error";
42 case 0x2:
43 return "Not Ready";
44 case 0x3:
45 return "Medium Error";
46 case 0x4:
47 return "Hardware Error";
48 case 0x5:
49 return "Illegal Request";
50 case 0x6:
51 return "Unit Attention";
52 case 0x7:
53 return "Data Protect";
54 case 0x8:
55 return "Blank Check";
56 case 0x9:
57 return "Vendor Specific";
58 case 0xA:
59 return "Copy Aborted";
60 case 0xB:
61 return "Aborted Command";
62 case 0xC:
63 return "Equal";
64 case 0xD:
65 return "Volume Overflow";
66 case 0xE:
67 return "Miscompare";
68 case 0xF:
69 return "Completed";
70 default:
71 return "Unknown";
72 }
73}
74
75static void logScsiSense(const char* diskName, const char* operation, const scsireq_t* req) {
76 uint8_t responseCode = req->sense[0] & 0x7F;
77 uint8_t senseKey;
78 uint8_t asc;
79 uint8_t ascq;
80
81 if (responseCode == 0x72 || responseCode == 0x73) {
82 senseKey = req->sense[1] & 0x0F;
83 asc = req->sense[2];
84 ascq = req->sense[3];
85 } else {
86 senseKey = req->sense[2] & 0x0F;
87 asc = req->sense[12];
88 ascq = req->sense[13];
89 }
90
91 FF_STRBUF_AUTO_DESTROY rawSense = ffStrbufCreate();
92 for (size_t i = 0; i < req->senselen_used; ++i) {
93 if (i) {
94 ffStrbufAppendC(&rawSense, ' ');
95 }
96 ffStrbufAppendF(&rawSense, "%02X", req->sense[i]);
97 }
98
99 FF_DEBUG(
100 "%s for %s reported SENSE: response=0x%02X, key=0x%X (%s), ASC=0x%02X, ASCQ=0x%02X, raw=[%s]",
101 operation,
102 diskName,
103 responseCode,
104 senseKey,
105 senseKeyToStr(senseKey),
106 asc,
107 ascq,
108 rawSense.length ? rawSense.chars : "empty");
109}
110#else
111 #define logScsiSense(...) ((void) 0)
112#endif
113
114const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
115 FF_STRBUF_AUTO_DESTROY diskNames = ffStrbufCreate();
116 FF_DEBUG("Querying disk names via sysctl hw.disknames");
117 const char* error = ffSysctlGetString("hw.disknames", &diskNames);
118 if (error) {
119 FF_DEBUG("ffSysctlGetString(hw.disknames) failed: %s", error);
120 return error;
121 }
122
123 FF_DEBUG("Disk names: %s", diskNames.chars);
124
125 char* diskName = nullptr;
126 size_t len = 0;
127 while (ffStrbufGetdelim(&diskName, &len, ' ', &diskNames)) {
128 FF_DEBUG("Probing disk: %s", diskName);
129
130 if (options->namePrefix.length && !ffStrbufStartsWith(&(FFstrbuf) {
131 .chars = diskName,
132 .length = (uint32_t) len,
133 },
134 &options->namePrefix)) {
135 FF_DEBUG("Skipping %s due to namePrefix filter", diskName);
136 continue;
137 }
138
139 char devPath[256];
140 FF_AUTO_CLOSE_FD int f = opendisk(diskName, O_RDONLY, devPath, ARRAY_SIZE(devPath), 0);
141 if (f < 0) {
142 if (errno == EACCES) {
143 FF_DEBUG("opendisk(%s) failed: permission denied", diskName);
144 return "Permission denied; root required";
145 } else {
146 FF_DEBUG("opendisk(%s) failed: %s", diskName, strerror(errno));
147 continue;
148 }
149 }
150
151 FF_DEBUG("Opened %s as %s", diskName, devPath);
152
153 struct disklabel dl;
154 if (ioctl(f, DIOCGDINFO, &dl) < 0) {
155 FF_DEBUG("ioctl(DIOCGDINFO) failed for %s: %s", diskName, strerror(errno));
156 continue;
157 }
158
159 unsigned long sectorsPerUnit, sectorSize;
160
161 const char* devType = nullptr;
162
163 prop_dictionary_t dict = nullptr;
164 if (prop_dictionary_recv_ioctl(f, DIOCGDISKINFO, &dict) == 0) {
165 FF_DEBUG("DIOCGDISKINFO succeeded for %s", diskName);
166 prop_dictionary_get_string(dict, "type", &devType);
167
168 prop_dictionary_t geometry;
169 if (prop_dictionary_get_dict(dict, "geometry", &geometry)) {
170 prop_dictionary_get_ulong(geometry, "sectors-per-unit", §orsPerUnit);
171 prop_dictionary_get_ulong(geometry, "sector-size", §orSize);
172 } else {
173 FF_DEBUG("No geometry in diskinfo for %s, falling back to disklabel", diskName);
174 sectorsPerUnit = dl.d_secperunit;
175 sectorSize = dl.d_secsize;
176 }
177 } else {
178 FF_DEBUG("DIOCGDISKINFO failed for %s, falling back to disklabel", diskName);
179 sectorsPerUnit = dl.d_secperunit;
180 sectorSize = dl.d_secsize;
181 }
182
183 FFPhysicalDiskType type = dl.d_flags & D_REMOVABLE ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
184
185 switch (dl.d_type) {
186 case DKTYPE_VND:
187 case DKTYPE_LD:
188 case DKTYPE_RAID:
189 case DKTYPE_CGD:
190 case DKTYPE_VINUM:
191 case DKTYPE_DM:
192 case DKTYPE_RUMPD:
193 case DKTYPE_MD: {
194 if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
195 FF_DEBUG("Skipping virtual disk %s due to hideType", diskName);
196 continue;
197 }
198
199 type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
200 break;
201 }
202 }
203
204 uint64_t size = sectorsPerUnit * sectorSize;
205 if (size == 0) {
206 if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
207 FF_DEBUG("Skipping unused disk %s due to hideType", diskName);
208 continue;
209 }
210
211 type |= FF_PHYSICALDISK_TYPE_UNUSED;
212 }
213
214 FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
215 ffStrbufInitS(&device->name, devType ?: dl.d_packname);
216 ffStrbufInitS(&device->devPath, devPath);
217 ffStrbufInit(&device->serial);
218 ffStrbufInit(&device->revision);
219 ffStrbufInitS(&device->interconnect, dktypenames[dl.d_type]);
220 device->type = type;
221 device->size = size;
222 device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
223 FF_DEBUG("Added disk entry: name='%s', devPath='%s', interconnect='%s', size=%llu",
224 device->name.chars,
225 device->devPath.chars,
226 device->interconnect.chars,
227 (unsigned long long) device->size);
228
229 if (dict) {
230 prop_object_release(dict);
231 dict = nullptr;
232 }
233
234 struct scsipi_inquiry_data inquiry = {};
235 scsireq_t req = {
236 .cmd = { [0] = INQUIRY, [4] = sizeof(inquiry) },
237 .cmdlen = 6,
238 .databuf = (caddr_t) &inquiry,
239 .datalen = sizeof(inquiry),
240 .timeout = 1000,
241 .flags = SCCMD_READ,
242 };
243 if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
244 if (req.retsts == SCCMD_OK) {
245 ffStrbufClear(&device->name);
246 ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.vendor), inquiry.vendor);
247 ffStrbufTrimRight(&device->name, '\0');
248 ffStrbufTrimRight(&device->name, ' ');
249 ffStrbufTrimRight(&device->name, ',');
250 ffStrbufAppendC(&device->name, ' ');
251 ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.product), inquiry.product);
252 ffStrbufTrimRight(&device->name, '\0');
253 ffStrbufTrimRight(&device->name, ' ');
254
255 ffStrbufSetNS(&device->revision, (uint32_t) ARRAY_SIZE(inquiry.revision), inquiry.revision);
256 ffStrbufTrimRight(&device->name, '\0');
257 ffStrbufTrimRight(&device->revision, ' ');
258 FF_DEBUG("SCSI inquiry for %s: name='%s', revision='%s'", diskName, device->name.chars, device->revision.chars);
259 } else {
260 FF_DEBUG("SCSI inquiry retsts != SCCMD_OK for %s (%d)", diskName, req.retsts);
261 continue;
262 }
263 } else {
264 FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI inquiry on %s: %s", diskName, strerror(errno));
265 continue;
266 }
267
268#ifdef SINQ_EVPD // Available since NetBSD 11.0
269 struct {
270 struct scsipi_inquiry_evpd_header header;
271 struct scsipi_inquiry_evpd_serial body;
272 } evpd = {};
273 req = (scsireq_t) {
274 .cmd = { [0] = INQUIRY, [1] = SINQ_EVPD, [2] = SINQ_VPD_UNIT_SERIAL, [4] = sizeof(evpd) },
275 .cmdlen = 6,
276 .databuf = (caddr_t) &evpd,
277 .datalen = sizeof(evpd),
278 .timeout = 1000,
279 .flags = SCCMD_READ,
280 };
281 if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
282 if (req.retsts == SCCMD_OK && evpd.header.pagecode == SINQ_VPD_UNIT_SERIAL) {
283 for (uint8_t i = 0; i < evpd.header.length[1]; ++i) {
284 ffStrbufAppendF(&device->serial, "%02X", evpd.body.serial_number[i]);
285 }
286 ffStrbufTrimRight(&device->serial, ' ');
287 FF_DEBUG("SCSI serial for %s: %s", diskName, device->serial.chars);
288 } else if (req.retsts == SCCMD_SENSE) {
289 logScsiSense(diskName, "SCSI serial page", &req);
290 } else {
291 FF_DEBUG("SCSI serial page unavailable for %s (retsts=%s, pagecode=%u)", diskName, retstsToStr(req.retsts), (unsigned) evpd.header.pagecode);
292 }
293 } else {
294 FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI serial on %s: %s", diskName, strerror(errno));
295 }
296#endif
297
298 struct scsi_mode_parameter_header_6 mode = {};
299 req = (scsireq_t) {
300 .cmd = { [0] = SCSI_MODE_SENSE_6, [2] = SMS_PAGE_MASK, [4] = sizeof(mode) },
301 .cmdlen = 6,
302 .databuf = (caddr_t) &mode,
303 .datalen = sizeof(mode),
304 .timeout = 1000,
305 .flags = SCCMD_READ,
306 };
307 if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
308 if (req.retsts == SCCMD_OK && mode.data_length > 0) {
309 device->type |= mode.dev_spec & 0x80 ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE;
310 FF_DEBUG("SCSI mode for %s indicates: %s", diskName, mode.dev_spec & 0x80 ? "readonly" : "readwrite");
311 } else if (req.retsts == SCCMD_SENSE) {
312 logScsiSense(diskName, "SCSI mode sense", &req);
313 } else {
314 FF_DEBUG("SCSI mode sense unavailable for %s (retsts=%s, data_length=%u)", diskName, retstsToStr(req.retsts), (unsigned) mode.data_length);
315 }
316 } else {
317 FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI mode sense on %s: %s", diskName, strerror(errno));
318 }
319
320 FF_DEBUG("Detected disk '%s' (%s), type=%u", device->name.chars, diskName, (unsigned) device->type);
321 }
322
323 return nullptr;
324}