Compare commits

...

5 Commits

Author SHA1 Message Date
Sergey Kiselev b1b291b1b2 SATA: implement historical standard ceiling for legacy drive links 3 months ago
Sergey Kiselev 6114f03313 Identity: implement native libcam versioning and path inquiry tracing 3 months ago
Sergey Kiselev 8d9b050dd9 Identity: implement generic error logging and permission checks 3 months ago
Sergey Kiselev b8569c9514 ATA/SATA: cross-verify logical & transport layers for robust versioning 3 months ago
Sergey Kiselev 17fa6c2c04 Fix: enforce strict backward compatibility for older Perl and Lua runtimes 3 months ago
  1. 2
      examples/tree_view.lua
  2. 26
      include/bsdiskinfo.h
  3. 4
      modules/perl/bsdiskinfo.xs
  4. 47
      src/cli.c
  5. 320
      src/libbsdiskinfo.c

2
examples/tree_view.lua

@ -91,7 +91,7 @@ for _, disk in ipairs(disks) do
local temp = "N/A"
for _, attr in ipairs(smart) do
if attr.id == 194 or attr.id == 190 then
temp = string.format("%d°C", math.tointeger(attr.raw_val & 0xFF) or attr.raw_val)
temp = string.format("%d°C", math.floor(attr.raw_val % 256) or attr.raw_val)
break
end
end

26
include/bsdiskinfo.h

@ -33,14 +33,18 @@
#include <sys/ucred.h>
#include <sys/mount.h>
#define MAX_MODEL_LEN 256
#define MAX_SERIAL_LEN 64
#define MAX_TYPE_LEN 16
#define MAX_FW_LEN 16
#define MAX_WWN_LEN 32
#define MAX_MOUNTS 128
#define MAX_DISKS 64
#define MAX_MODEL_LEN 256 /* Buffer size for device model string */
#define MAX_SERIAL_LEN 64 /* Buffer size for drive serial number string */
#define MAX_TYPE_LEN 16 /* Buffer size for determined drive architecture type string */
#define MAX_FW_LEN 16 /* Buffer size for device firmware version revision string */
#define MAX_WWN_LEN 32 /* Buffer size for LU WWN Device Id hex string format */
#define MAX_FF_LEN 32 /* Buffer size for physical form factor string */
#define MAX_ATA_LEN 64 /* Buffer size for major/minor ATA standard specification */
#define MAX_SATA_LEN 64 /* Buffer size for SATA signaling capabilities and link speed */
#define MAX_SEC_LEN 32 /* Buffer size for internal ATA Security feature state */
#define MAX_MOUNTS 128 /* Maximum tracked active filesystem mount topologies */
#define MAX_DISKS 64 /* Maximum concurrent physical storage units allowed */
/*
* Base hardware identity properties (similar to smartctl -i output).
@ -61,6 +65,12 @@ struct disk_properties {
int smart_supported; /* S.M.A.R.T. capability support: 1 = Yes, 0 = No, -1 = N/A */
int smart_enabled; /* S.M.A.R.T. active enablement: 1 = Yes, 0 = No, -1 = N/A */
/* Advanced ATA hardware descriptors */
char form_factor[MAX_FF_LEN]; /* Nominal physical drive form factor dimensions */
char ata_version[MAX_ATA_LEN]; /* Major/Minor ATA/ATAPI specification standard tracking */
char sata_version[MAX_SATA_LEN]; /* Active SATA signaling speed capabilities and status */
char security_status[MAX_SEC_LEN];/* Internal firmware ATA Security Feature Set execution state */
};
/*

4
modules/perl/bsdiskinfo.xs

@ -51,8 +51,8 @@ get_properties(base_disk)
}
if (props.smart_supported >= 0) {
hv_store(hv, "smart_supported", 15, newSVbool(props.smart_supported == 1), 0);
hv_store(hv, "smart_enabled", 13, newSVbool(props.smart_enabled == 1), 0);
hv_store(hv, "smart_supported", 15, boolSV(props.smart_supported == 1), 0);
hv_store(hv, "smart_enabled", 13, boolSV(props.smart_enabled == 1), 0);
}
/* Return as a reference to the hash */

47
src/cli.c

@ -83,15 +83,34 @@ int main(int argc, char *argv[]) {
static void print_identity(const char *disk_name, struct disk_properties *props) {
printf("=== START OF INFORMATION SECTION (%s) ===\n", disk_name);
printf("Device Model: %s\n", strlen(props->model) > 0 ? props->model : "N/A");
printf("Serial Number: %s\n", strlen(props->serial) > 0 ? props->serial : "N/A");
printf("Firmware Version: %s\n", strlen(props->fw_version) > 0 ? props->fw_version : "N/A");
printf("LU WWN Device Id: %s\n", strlen(props->wwn) > 0 ? props->wwn : "N/A");
printf("Determined Type: %s\n", strlen(props->type_str) > 0 ? props->type_str : "unknown");
printf("User Capacity: %llu bytes\n", (unsigned long long)props->mediasize);
printf("Sector Sizes: %u bytes logical, %u bytes physical\n",
printf("Device Model: %s\n", props->model);
printf("Serial Number: %s\n", props->serial);
printf("Firmware Version: %s\n", props->fw_version);
printf("LU WWN Device Id: %s\n", props->wwn);
/* Display advanced ATA hardware descriptors unpacked by the core library */
printf("Form Factor: %s\n", props->form_factor); /* Nominal physical unit size dimensions */
printf("ATA Version: %s\n", props->ata_version); /* Major compliance standard tracking */
printf("SATA Version: %s\n", props->sata_version); /* Signalling standard interface capabilities */
printf("ATA Security: %s\n", props->security_status); /* Operational hardware security locks state */
printf("Determined Type: %s\n", props->type_str);
/* Render storage payload storage capacity with explicit units computing */
double gib = (double)props->mediasize / (1024.0 * 1024.0 * 1024.0);
if (gib >= 1024.0) {
printf("User Capacity: %llu bytes (%.2f TiB)\n",
(unsigned long long)props->mediasize, gib / 1024.0);
} else {
printf("User Capacity: %llu bytes (%.2f GiB)\n",
(unsigned long long)props->mediasize, gib);
}
/* Print drive layout formatting boundaries details */
printf("Sector Sizes: %u bytes logical, %u bytes physical\n",
props->logical_sector_size, props->physical_sector_size);
/* Parse nominal motor spin rotation specs layout */
if (props->rotation_rate == 0) {
printf("Rotation Rate: 0 rpm (SSD)\n");
} else if (props->rotation_rate > 0) {
@ -100,9 +119,15 @@ static void print_identity(const char *disk_name, struct disk_properties *props)
printf("Rotation Rate: N/A\n");
}
printf("SMART Support: %s\n", props->smart_supported == 1 ? "Available" : "Unavailable");
printf("SMART Status: %s\n", props->smart_supported != 1 ? "N/A" :
(props->smart_enabled == 1 ? "Enabled" : "Disabled"));
/* Process core hardware diagnostic state indicators */
if (props->smart_supported == 1) {
printf("SMART Support: Available\n");
printf("SMART Status: %s\n", props->smart_enabled == 1 ? "Enabled" : "Disabled");
} else if (props->smart_supported == 0) {
printf("SMART Support: Not available\n");
} else {
printf("SMART Support: N/A\n");
}
printf("=====================================================\n\n");
}

320
src/libbsdiskinfo.c

@ -30,6 +30,7 @@
#define _GNU_SOURCE /* For strcasestr support */
#include <errno.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
@ -225,6 +226,125 @@ int diskinfo_get_mounts(const char *base_disk, struct disk_mount *mounts_out, in
return match_count; /* Return absolute count of populated records */
}
/*
* Extracts advanced ATA/SATA hardware descriptor properties from
* the provided native IDENTIFY parameter blocks using native libcam functions.
*/
static void parse_advanced_ata_fields(struct ata_params *buf, struct disk_properties *props, float current_speed) {
/* 1. Form Factor dimensions handling (Word 168) */
uint16_t ff = buf->form_factor;
if (ff == 2) strlcpy(props->form_factor, "3.5 inches", sizeof(props->form_factor));
else if (ff == 3) strlcpy(props->form_factor, "2.5 inches", sizeof(props->form_factor));
else if (ff == 4) strlcpy(props->form_factor, "1.8 inches", sizeof(props->form_factor));
else if (ff == 5) strlcpy(props->form_factor, "less than 1.8", sizeof(props->form_factor));
else strlcpy(props->form_factor, "N/A", sizeof(props->form_factor));
/* 2. Major ATA Version standard handling using native ata_version() from libcam.so */
int version = ata_version(buf->version_major);
if (buf->version_major == 0xFFFF || buf->version_major == 0x0000 || version < 0) {
strlcpy(props->ata_version, "Unknown", sizeof(props->ata_version));
} else {
switch (version) {
case 0:
strlcpy(props->ata_version, "ATA", sizeof(props->ata_version));
break;
case 8:
strlcpy(props->ata_version, "ATA8-ACS", sizeof(props->ata_version));
break;
default:
if (version <= 7) {
snprintf(props->ata_version, sizeof(props->ata_version), "ATA-%d", version);
} else {
snprintf(props->ata_version, sizeof(props->ata_version), "ACS-%d", version - 7);
}
break;
}
}
/* 3. Serial ATA Capabilities layout tracing limited by mapped ATA standard boundaries (Word 76 & Word 222) */
uint16_t sata_cap = buf->satacapabilities;
uint16_t transport = buf->transport_major;
if (sata_cap == 0xFFFF || sata_cap == 0x0000) {
strlcpy(props->sata_version, "Not a SATA device", sizeof(props->sata_version));
} else {
const char *ver = NULL;
float cap_speed = 1.5;
/* Extract logical transport revision from Word 222 using explicit camcontrol bit boundaries */
if (transport != 0xFFFF && transport != 0x0000 && (transport & 0x1000)) {
if (transport & 0x0400) ver = "SATA 3.5";
else if (transport & 0x0200) ver = "SATA 3.4";
else if (transport & 0x0100) ver = "SATA 3.3";
else if (transport & 0x0080) ver = "SATA 3.2";
else if (transport & 0x0040) ver = "SATA 3.1";
else if (transport & 0x0020) ver = "SATA 3.0";
else if (transport & 0x0010) ver = "SATA 2.6";
else if (transport & 0x0008) ver = "SATA 2.5";
}
/* Fallback to physical link generation signaling limits from Word 76 if transport is unmapped */
if (ver == NULL) {
if (strcmp(props->ata_version, "ATA-7") == 0 || strncmp(props->ata_version, "ATA-", 4) == 0) {
ver = "SATA 1.x";
cap_speed = 1.5;
} else {
if (sata_cap & 0x0006) {
ver = "SATA 3.x";
cap_speed = 6.0;
} else if (sata_cap & 0x0004) {
ver = "SATA 2.x";
cap_speed = 3.0;
} else {
ver = "SATA 1.x";
cap_speed = 1.5;
}
}
} else {
/* Determine maximum capable speed rate from Word 76 capabilities for modern units */
if (sata_cap & 0x0006) cap_speed = 6.0;
else if (sata_cap & 0x0004) cap_speed = 3.0;
else cap_speed = 1.5;
/*
* Architectural ceiling check for backwards compatibility (FreeBSD 11.4 legacy layers).
* If the drive is reporting legacy SATA 2.5/2.6 or if we are tracking older firmware lines,
* override fake controller-injected Gen3 speed limits using drive's genuine Word 76 bits.
*/
if (strncmp(ver, "SATA 2.", 7) == 0 && cap_speed > 3.0) {
if (sata_cap & 0x0004) cap_speed = 3.0;
else cap_speed = 1.5;
}
/* If running on older FreeBSD 11.4 where Word 222 output fluctuates, sanitize down to 1.x */
if (cap_speed == 1.5 && strncmp(ver, "SATA 3.", 7) != 0) {
ver = "SATA 1.x";
}
}
/* If CAM transport layout parsing returned empty/restricted, sync with capable speed */
if (current_speed <= 0.0) {
current_speed = cap_speed;
}
/* Generate final elegant string matches smartctl/camcontrol style */
snprintf(props->sata_version, sizeof(props->sata_version),
"%s, %.1f Gb/s (current: %.1f Gb/s)", ver, cap_speed, current_speed);
}
/* 4. ATA Security Feature Set state validation (Word 128) */
uint16_t sec = buf->security_status;
if (!(sec & 0x0001)) {
strlcpy(props->security_status, "Disabled", sizeof(props->security_status));
} else if (sec & 0x0004) {
strlcpy(props->security_status, "LOCKED", sizeof(props->security_status));
} else if (sec & 0x0008) {
strlcpy(props->security_status, "FROZEN", sizeof(props->security_status));
} else {
strlcpy(props->security_status, "NOT FROZEN", sizeof(props->security_status));
}
}
/*
* Internal helper to query the device via FreeBSD libcam.
* Handles SCSI SAT Pass-Through (16) for 'da' devices and native ATA for 'ada' devices.
@ -244,12 +364,90 @@ static void query_cam_properties(
*pt_failed = 0;
/* CRITICAL: Must use O_RDWR for transport-layer commands execution path to unlock */
/* Pre-initialize advanced hardware descriptors with default values */
strlcpy(props->form_factor, "N/A", sizeof(props->form_factor));
strlcpy(props->ata_version, "N/A", sizeof(props->ata_version));
strlcpy(props->sata_version, "N/A", sizeof(props->sata_version));
strlcpy(props->security_status, "N/A", sizeof(props->security_status));
/* CRITICAL Check: Permissions routing validation */
cam_dev = cam_open_device(base_disk, O_RDWR);
if (cam_dev == NULL) return;
if (cam_dev == NULL) {
if (errno == EACCES || errno == EPERM) {
fprintf(stderr, "[ERROR] %s: Permission denied. Accessing libcam transport layers requires root privileges (run with sudo).\n", base_disk);
} else {
fprintf(stderr, "[ERROR] %s: Failed to open CAM device node (errno: %d)\n", base_disk, errno);
}
return;
}
/* Extract current active signaling rate mirroring camcontrol.c camxferrate() flow exactly */
float current_speed = 0.0;
uint32_t speed = 0;
union ccb *ccb_cts = NULL;
struct ccb_pathinq cpi;
memset(&cpi, 0, sizeof(struct ccb_pathinq));
/* Step A: Fetch base speed path inquiry matching camcontrol get_cpi() */
union ccb *ccb_cpi = cam_getccb(cam_dev);
if (ccb_cpi != NULL) {
ccb_cpi->ccb_h.func_code = XPT_PATH_INQ;
ccb_cpi->ccb_h.flags = CAM_DIR_NONE;
ccb_cpi->ccb_h.retry_count = 1;
ccb_cpi->ccb_h.timeout = 1000;
if (cam_send_ccb(cam_dev, ccb_cpi) == 0 && (ccb_cpi->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
bcopy(&ccb_cpi->cpi, &cpi, sizeof(struct ccb_pathinq));
}
cam_freeccb(ccb_cpi);
}
/* Initialize baseline transfer speed value from Path Inquiry */
speed = cpi.base_transfer_speed;
/* Step B: Query exact active transfer settings from the CAM layer */
ccb_cts = cam_getccb(cam_dev);
if (ccb_cts != NULL) {
ccb_cts->ccb_h.func_code = XPT_GET_TRAN_SETTINGS;
ccb_cts->ccb_h.flags = CAM_DIR_NONE;
ccb_cts->ccb_h.retry_count = 1;
ccb_cts->ccb_h.timeout = 1000;
ccb_cts->ccb_h.target_id = cam_dev->target_id;
ccb_cts->ccb_h.target_lun = cam_dev->target_lun;
ccb_cts->cts.type = CTS_TYPE_CURRENT_SETTINGS; /* Force runtime active link preference layout fetch */
if (cam_send_ccb(cam_dev, ccb_cts) == 0 && (ccb_cts->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
/* Execute exact transport specific branching copied from camcontrol.c source */
if (ccb_cts->cts.transport == XPORT_ATA) {
struct ccb_trans_settings_pata *pata = &ccb_cts->cts.xport_specific.ata;
if (pata->valid & CTS_ATA_VALID_MODE) {
speed = ata_mode2speed(pata->mode);
}
} else if (ccb_cts->cts.transport == XPORT_SATA) {
struct ccb_trans_settings_sata *sata = &ccb_cts->cts.xport_specific.sata;
if (sata->valid & CTS_SATA_VALID_REVISION) {
speed = ata_revision2speed(sata->revision);
}
} else if (ccb_cts->cts.transport == XPORT_SAS) {
struct ccb_trans_settings_sas *sas = &ccb_cts->cts.xport_specific.sas;
if (sas->valid & 0x01) { /* CTS_SAS_VALID_SPEED */
speed = sas->bitrate;
}
}
}
cam_freeccb(ccb_cts);
}
/* Map finalized speed KB/s calculation to human-readable Gb/s standard format */
uint32_t mb = speed / 1000;
if (mb >= 550) current_speed = 6.0;
else if (mb >= 280) current_speed = 3.0;
else if (mb >= 140) current_speed = 1.5;
/* Allocate CCB structure for standard IDENTIFY command execution path below */
ccb = cam_getccb(cam_dev);
if (ccb == NULL) {
fprintf(stderr, "[ERROR] %s: Failed to allocate CAM CCB structure\n", base_disk);
cam_close_device(cam_dev);
return;
}
@ -320,6 +518,11 @@ static void query_cam_properties(
success = 1;
}
}
/* Filter out fake success on SAT(12) loop as well */
if (success && ident_buf->model[0] == 0) {
success = 0;
}
}
if (success) {
@ -362,9 +565,14 @@ static void query_cam_properties(
*is_ssd = 0;
}
/* Parse Sector Sizes */
if ((ident_buf->pss & 0xC000) == 0x4000 && (ident_buf->pss & 0x1000)) {
/* Parse advanced hardware descriptors from the verified buffer */
parse_advanced_ata_fields(ident_buf, props, current_speed);
/* Parse Sector Sizes (Word 106: Physical sector size / Advanced Format) */
if ((ident_buf->pss & 0x4000) && (ident_buf->pss & 0x2000)) {
props->physical_sector_size = props->logical_sector_size * (1 << (ident_buf->pss & 0x000F));
} else {
props->physical_sector_size = props->logical_sector_size;
}
/* Parse S.M.A.R.T. enablement states */
@ -375,6 +583,7 @@ static void query_cam_properties(
props->smart_supported = 0; props->smart_enabled = 0;
}
} else {
fprintf(stderr, "[ERROR] %s: ATA Pass-Through rejected or returned empty payload (hardware timeout/fault)\n", base_disk);
*pt_failed = 1;
}
}
@ -390,56 +599,67 @@ static void query_cam_properties(
ccb->ataio.cmd.command = 0xEC;
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
char real_model[41];
memcpy(real_model, ident_buf->model, sizeof(ident_buf->model));
real_model[sizeof(ident_buf->model)] = '\0';
ata_str_fix(real_model, sizeof(real_model));
if (strlen(real_model) > 0) strlcpy(props->model, real_model, sizeof(props->model));
char real_serial[21];
memcpy(real_serial, ident_buf->serial, sizeof(ident_buf->serial));
real_serial[sizeof(ident_buf->serial)] = '\0';
ata_str_fix(real_serial, sizeof(real_serial));
if (strlen(real_serial) > 0) strlcpy(props->serial, real_serial, sizeof(props->serial));
char real_fw[9];
memcpy(real_fw, ident_buf->revision, sizeof(ident_buf->revision));
real_fw[sizeof(ident_buf->revision)] = '\0';
ata_str_fix(real_fw, sizeof(real_fw));
if (strlen(real_fw) > 0) strlcpy(props->fw_version, real_fw, sizeof(props->fw_version));
/* Parse LU WWN Device ID */
uint64_t wwn_val = 0;
for (int w = 0; w < 4; w++) {
wwn_val |= ((uint64_t)ident_buf->wwn[w]) << ((3 - w) * 16);
}
if (wwn_val != 0) snprintf(props->wwn, sizeof(props->wwn), "%016llx", (unsigned long long)wwn_val);
else strlcpy(props->wwn, "N/A", sizeof(props->wwn));
/* Parse Rotation Rate and SSD state flags */
if (ident_buf->media_rotation_rate == 1) {
props->rotation_rate = 0;
*is_ssd = 1;
} else if (ident_buf->media_rotation_rate > 1 && ident_buf->media_rotation_rate < 0xFFFF) {
props->rotation_rate = ident_buf->media_rotation_rate;
*is_ssd = 0;
if (ident_buf->model[0] == 0) {
fprintf(stderr, "[ERROR] %s: IDENTIFY DEVICE returned empty payload (hardware fault)\n", base_disk);
} else {
props->rotation_rate = -1;
*is_ssd = 0;
}
char real_model[41];
memcpy(real_model, ident_buf->model, sizeof(ident_buf->model));
real_model[sizeof(ident_buf->model)] = '\0';
ata_str_fix(real_model, sizeof(real_model));
if (strlen(real_model) > 0) strlcpy(props->model, real_model, sizeof(props->model));
/* Parse Sector Sizes */
if ((ident_buf->pss & 0xC000) == 0x4000 && (ident_buf->pss & 0x1000)) {
props->physical_sector_size = props->logical_sector_size * (1 << (ident_buf->pss & 0x000F));
}
char real_serial[21];
memcpy(real_serial, ident_buf->serial, sizeof(ident_buf->serial));
real_serial[sizeof(ident_buf->serial)] = '\0';
ata_str_fix(real_serial, sizeof(real_serial));
if (strlen(real_serial) > 0) strlcpy(props->serial, real_serial, sizeof(props->serial));
/* Parse S.M.A.R.T. enablement states */
if (ident_buf->support.command1 & 0x0001) {
props->smart_supported = 1;
props->smart_enabled = (ident_buf->enabled.command1 & 0x0001) ? 1 : 0;
} else {
props->smart_supported = 0; props->smart_enabled = 0;
char real_fw[9];
memcpy(real_fw, ident_buf->revision, sizeof(ident_buf->revision));
real_fw[sizeof(ident_buf->revision)] = '\0';
ata_str_fix(real_fw, sizeof(real_fw));
if (strlen(real_fw) > 0) strlcpy(props->fw_version, real_fw, sizeof(props->fw_version));
/* Parse LU WWN Device ID */
uint64_t wwn_val = 0;
for (int w = 0; w < 4; w++) {
wwn_val |= ((uint64_t)ident_buf->wwn[w]) << ((3 - w) * 16);
}
if (wwn_val != 0) snprintf(props->wwn, sizeof(props->wwn), "%016llx", (unsigned long long)wwn_val);
else strlcpy(props->wwn, "N/A", sizeof(props->wwn));
/* Parse Rotation Rate and SSD state flags */
if (ident_buf->media_rotation_rate == 1) {
props->rotation_rate = 0;
*is_ssd = 1;
} else if (ident_buf->media_rotation_rate > 1 && ident_buf->media_rotation_rate < 0xFFFF) {
props->rotation_rate = ident_buf->media_rotation_rate;
*is_ssd = 0;
} else {
props->rotation_rate = -1;
*is_ssd = 0;
}
/* Parse advanced hardware descriptors from the direct AHCI buffer */
parse_advanced_ata_fields(ident_buf, props, current_speed);
/* Parse Sector Sizes (Word 106: Physical sector size / Advanced Format) */
if ((ident_buf->pss & 0x4000) && (ident_buf->pss & 0x2000)) {
props->physical_sector_size = props->logical_sector_size * (1 << (ident_buf->pss & 0x000F));
} else {
props->physical_sector_size = props->logical_sector_size;
}
/* Parse S.M.A.R.T. enablement states */
if (ident_buf->support.command1 & 0x0001) {
props->smart_supported = 1;
props->smart_enabled = (ident_buf->enabled.command1 & 0x0001) ? 1 : 0;
} else {
props->smart_supported = 0; props->smart_enabled = 0;
}
}
} else {
fprintf(stderr, "[ERROR] %s: Native ATA IDENTIFY command rejected by the controller\n", base_disk);
}
}

Loading…
Cancel
Save