#define DKTYPENAMES #include "physicaldisk.h" #include "common/io.h" #include "common/sysctl.h" #include "common/debug.h" #include #include #include #include #include #include #include #include #include #include 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", §orsPerUnit); prop_dictionary_get_ulong(geometry, "sector-size", §orSize); } 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; }