master ohmu/fastfetch-openbsd / src / detection / physicaldisk / physicaldisk_obsd.c
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}