master ohmu/fastfetch-openbsd / src / detection / physicaldisk / physicaldisk_nbsd.c
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", &sectorsPerUnit);
171                prop_dictionary_get_ulong(geometry, "sector-size", &sectorSize);
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}