Files
fastfetch/src/detection/physicaldisk/physicaldisk_nbsd.c
T
2026-04-15 16:17:08 +08:00

325 lines
12 KiB
C

#define DKTYPENAMES
#include "physicaldisk.h"
#include "common/io.h"
#include "common/sysctl.h"
#include "common/debug.h"
#include <util.h>
#include <prop/proplib.h>
#include <sys/ioctl.h>
#include <sys/disklabel.h>
#include <sys/dkio.h>
#include <sys/scsiio.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsi_spc.h>
#include <fcntl.h>
#include <errno.h>
static inline const char* retstsToStr(uint8_t retsts) {
switch (retsts) {
case SCCMD_OK:
return "OK";
case SCCMD_TIMEOUT:
return "TIMEOUT";
case SCCMD_BUSY:
return "BUSY";
case SCCMD_SENSE:
return "SENSE";
case SCCMD_UNKNOWN:
return "UNKNOWN";
default:
return "?";
}
}
#ifndef NDEBUG
static inline const char* senseKeyToStr(uint8_t key) {
switch (key) {
case 0x0:
return "No Sense";
case 0x1:
return "Recovered Error";
case 0x2:
return "Not Ready";
case 0x3:
return "Medium Error";
case 0x4:
return "Hardware Error";
case 0x5:
return "Illegal Request";
case 0x6:
return "Unit Attention";
case 0x7:
return "Data Protect";
case 0x8:
return "Blank Check";
case 0x9:
return "Vendor Specific";
case 0xA:
return "Copy Aborted";
case 0xB:
return "Aborted Command";
case 0xC:
return "Equal";
case 0xD:
return "Volume Overflow";
case 0xE:
return "Miscompare";
case 0xF:
return "Completed";
default:
return "Unknown";
}
}
static void logScsiSense(const char* diskName, const char* operation, const scsireq_t* req) {
uint8_t responseCode = req->sense[0] & 0x7F;
uint8_t senseKey;
uint8_t asc;
uint8_t ascq;
if (responseCode == 0x72 || responseCode == 0x73) {
senseKey = req->sense[1] & 0x0F;
asc = req->sense[2];
ascq = req->sense[3];
} else {
senseKey = req->sense[2] & 0x0F;
asc = req->sense[12];
ascq = req->sense[13];
}
FF_STRBUF_AUTO_DESTROY rawSense = ffStrbufCreate();
for (size_t i = 0; i < req->senselen_used; ++i) {
if (i) {
ffStrbufAppendC(&rawSense, ' ');
}
ffStrbufAppendF(&rawSense, "%02X", req->sense[i]);
}
FF_DEBUG(
"%s for %s reported SENSE: response=0x%02X, key=0x%X (%s), ASC=0x%02X, ASCQ=0x%02X, raw=[%s]",
operation,
diskName,
responseCode,
senseKey,
senseKeyToStr(senseKey),
asc,
ascq,
rawSense.length ? rawSense.chars : "empty");
}
#else
#define logScsiSense(...) ((void) 0)
#endif
const char* ffDetectPhysicalDisk(FFlist* result, FFPhysicalDiskOptions* options) {
FF_STRBUF_AUTO_DESTROY diskNames = ffStrbufCreate();
FF_DEBUG("Querying disk names via sysctl hw.disknames");
const char* error = ffSysctlGetString("hw.disknames", &diskNames);
if (error) {
FF_DEBUG("ffSysctlGetString(hw.disknames) failed: %s", error);
return error;
}
FF_DEBUG("Disk names: %s", diskNames.chars);
char* diskName = NULL;
size_t len = 0;
while (ffStrbufGetdelim(&diskName, &len, ' ', &diskNames)) {
FF_DEBUG("Probing disk: %s", diskName);
if (options->namePrefix.length && !ffStrbufStartsWith(&(FFstrbuf) {
.chars = diskName,
.length = (uint32_t) len,
},
&options->namePrefix)) {
FF_DEBUG("Skipping %s due to namePrefix filter", diskName);
continue;
}
char devPath[256];
FF_AUTO_CLOSE_FD int f = opendisk(diskName, O_RDONLY, devPath, ARRAY_SIZE(devPath), 0);
if (f < 0) {
if (errno == EACCES) {
FF_DEBUG("opendisk(%s) failed: permission denied", diskName);
return "Permission denied; root required";
} else {
FF_DEBUG("opendisk(%s) failed: %s", diskName, strerror(errno));
continue;
}
}
FF_DEBUG("Opened %s as %s", diskName, devPath);
struct disklabel dl;
if (ioctl(f, DIOCGDINFO, &dl) < 0) {
FF_DEBUG("ioctl(DIOCGDINFO) failed for %s: %s", diskName, strerror(errno));
continue;
}
unsigned long sectorsPerUnit, sectorSize;
const char* devType = NULL;
prop_dictionary_t dict = NULL;
if (prop_dictionary_recv_ioctl(f, DIOCGDISKINFO, &dict) == 0) {
FF_DEBUG("DIOCGDISKINFO succeeded for %s", diskName);
prop_dictionary_get_string(dict, "type", &devType);
prop_dictionary_t geometry;
if (prop_dictionary_get_dict(dict, "geometry", &geometry)) {
prop_dictionary_get_ulong(geometry, "sectors-per-unit", &sectorsPerUnit);
prop_dictionary_get_ulong(geometry, "sector-size", &sectorSize);
} else {
FF_DEBUG("No geometry in diskinfo for %s, falling back to disklabel", diskName);
sectorsPerUnit = dl.d_secperunit;
sectorSize = dl.d_secsize;
}
} else {
FF_DEBUG("DIOCGDISKINFO failed for %s, falling back to disklabel", diskName);
sectorsPerUnit = dl.d_secperunit;
sectorSize = dl.d_secsize;
}
FFPhysicalDiskType type = dl.d_flags & D_REMOVABLE ? FF_PHYSICALDISK_TYPE_REMOVABLE : FF_PHYSICALDISK_TYPE_FIXED;
switch (dl.d_type) {
case DKTYPE_VND:
case DKTYPE_LD:
case DKTYPE_RAID:
case DKTYPE_CGD:
case DKTYPE_VINUM:
case DKTYPE_DM:
case DKTYPE_RUMPD:
case DKTYPE_MD: {
if (options->hideType & FF_PHYSICALDISK_TYPE_VIRTUAL) {
FF_DEBUG("Skipping virtual disk %s due to hideType", diskName);
continue;
}
type |= FF_PHYSICALDISK_TYPE_VIRTUAL;
break;
}
}
uint64_t size = sectorsPerUnit * sectorSize;
if (size == 0) {
if (options->hideType & FF_PHYSICALDISK_TYPE_UNUSED) {
FF_DEBUG("Skipping unused disk %s due to hideType", diskName);
continue;
}
type |= FF_PHYSICALDISK_TYPE_UNUSED;
}
FFPhysicalDiskResult* device = FF_LIST_ADD(FFPhysicalDiskResult, *result);
ffStrbufInitS(&device->name, devType ?: dl.d_packname);
ffStrbufInitS(&device->devPath, devPath);
ffStrbufInit(&device->serial);
ffStrbufInit(&device->revision);
ffStrbufInitS(&device->interconnect, dktypenames[dl.d_type]);
device->type = type;
device->size = size;
device->temperature = FF_PHYSICALDISK_TEMP_UNSET;
FF_DEBUG("Added disk entry: name='%s', devPath='%s', interconnect='%s', size=%llu",
device->name.chars,
device->devPath.chars,
device->interconnect.chars,
(unsigned long long) device->size);
if (dict) {
prop_object_release(dict);
dict = NULL;
}
struct scsipi_inquiry_data inquiry = {};
scsireq_t req = {
.cmd = { [0] = INQUIRY, [4] = sizeof(inquiry) },
.cmdlen = 6,
.databuf = (caddr_t) &inquiry,
.datalen = sizeof(inquiry),
.timeout = 1000,
.flags = SCCMD_READ,
};
if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
if (req.retsts == SCCMD_OK) {
ffStrbufClear(&device->name);
ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.vendor), inquiry.vendor);
ffStrbufTrimRight(&device->name, '\0');
ffStrbufTrimRight(&device->name, ' ');
ffStrbufTrimRight(&device->name, ',');
ffStrbufAppendC(&device->name, ' ');
ffStrbufAppendNS(&device->name, (uint32_t) ARRAY_SIZE(inquiry.product), inquiry.product);
ffStrbufTrimRight(&device->name, '\0');
ffStrbufTrimRight(&device->name, ' ');
ffStrbufSetNS(&device->revision, (uint32_t) ARRAY_SIZE(inquiry.revision), inquiry.revision);
ffStrbufTrimRight(&device->name, '\0');
ffStrbufTrimRight(&device->revision, ' ');
FF_DEBUG("SCSI inquiry for %s: name='%s', revision='%s'", diskName, device->name.chars, device->revision.chars);
} else {
FF_DEBUG("SCSI inquiry retsts != SCCMD_OK for %s (%d)", diskName, req.retsts);
continue;
}
} else {
FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI inquiry on %s: %s", diskName, strerror(errno));
continue;
}
#ifdef SINQ_EVPD // Available since NetBSD 11.0
struct {
struct scsipi_inquiry_evpd_header header;
struct scsipi_inquiry_evpd_serial body;
} evpd = {};
req = (scsireq_t) {
.cmd = { [0] = INQUIRY, [1] = SINQ_EVPD, [2] = SINQ_VPD_UNIT_SERIAL, [4] = sizeof(evpd) },
.cmdlen = 6,
.databuf = (caddr_t) &evpd,
.datalen = sizeof(evpd),
.timeout = 1000,
.flags = SCCMD_READ,
};
if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
if (req.retsts == SCCMD_OK && evpd.header.pagecode == SINQ_VPD_UNIT_SERIAL) {
for (uint8_t i = 0; i < evpd.header.length[1]; ++i) {
ffStrbufAppendF(&device->serial, "%02X", evpd.body.serial_number[i]);
}
ffStrbufTrimRight(&device->serial, ' ');
FF_DEBUG("SCSI serial for %s: %s", diskName, device->serial.chars);
} else if (req.retsts == SCCMD_SENSE) {
logScsiSense(diskName, "SCSI serial page", &req);
} else {
FF_DEBUG("SCSI serial page unavailable for %s (retsts=%s, pagecode=%u)", diskName, retstsToStr(req.retsts), (unsigned) evpd.header.pagecode);
}
} else {
FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI serial on %s: %s", diskName, strerror(errno));
}
#endif
struct scsi_mode_parameter_header_6 mode = {};
req = (scsireq_t) {
.cmd = { [0] = SCSI_MODE_SENSE_6, [2] = SMS_PAGE_MASK, [4] = sizeof(mode) },
.cmdlen = 6,
.databuf = (caddr_t) &mode,
.datalen = sizeof(mode),
.timeout = 1000,
.flags = SCCMD_READ,
};
if (ioctl(f, SCIOCCOMMAND, &req) == 0) {
if (req.retsts == SCCMD_OK && mode.data_length > 0) {
device->type |= mode.dev_spec & 0x80 ? FF_PHYSICALDISK_TYPE_READONLY : FF_PHYSICALDISK_TYPE_READWRITE;
FF_DEBUG("SCSI mode for %s indicates: %s", diskName, mode.dev_spec & 0x80 ? "readonly" : "readwrite");
} else if (req.retsts == SCCMD_SENSE) {
logScsiSense(diskName, "SCSI mode sense", &req);
} else {
FF_DEBUG("SCSI mode sense unavailable for %s (retsts=%s, data_length=%u)", diskName, retstsToStr(req.retsts), (unsigned) mode.data_length);
}
} else {
FF_DEBUG("ioctl(SCIOCCOMMAND) failed for SCSI mode sense on %s: %s", diskName, strerror(errno));
}
FF_DEBUG("Detected disk '%s' (%s), type=%u", device->name.chars, diskName, (unsigned) device->type);
}
return NULL;
}