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