6 changed files with 910 additions and 0 deletions
@ -0,0 +1,38 @@ |
|||
-- test.lua |
|||
local diskinfo = require("diskinfo") |
|||
local disks, err = diskinfo.get_info() |
|||
|
|||
if not disks then |
|||
print("Error fetching disk info: " .. tostring(err)) |
|||
os.exit(1) |
|||
end |
|||
|
|||
print("==================================================================") |
|||
print("FREEBSD SYSTEM DISK TREE (With S.M.A.R.T. Temperature)") |
|||
print("==================================================================") |
|||
|
|||
for _, disk_name in ipairs(disks._order) do |
|||
local disk = disks[disk_name] |
|||
|
|||
-- Проверяем, вернул ли модуль температуру SMART для этого накопителя |
|||
local smart_str = "" |
|||
if disk.smart_temp then |
|||
smart_str = string.format(" | Temp: %d°C", disk.smart_temp) |
|||
end |
|||
|
|||
print(string.format("Drive: %s [%s] | Type: %s%s", disk_name, disk.size, disk.type, smart_str)) |
|||
print(string.format(" |- Hardware Model : %s", disk.model)) |
|||
print(" |- Partition Tree :") |
|||
|
|||
if #disk.mounts._order == 0 then |
|||
print(" [No active mounts discovered on this device]") |
|||
else |
|||
for _, part_name in ipairs(disk.mounts._order) do |
|||
local partition = disk.mounts[part_name] |
|||
print(string.format(" -> %-10s | Path: %-20s | FS Type: %s", |
|||
part_name, partition.path, partition.type)) |
|||
end |
|||
end |
|||
print(string.rep("-", 66)) |
|||
end |
|||
|
|||
@ -0,0 +1,40 @@ |
|||
LIB_NAME= diskinfo |
|||
SRC= diskinfo.c |
|||
|
|||
PREFIX?= /usr/local |
|||
|
|||
OS!= uname -s |
|||
.if ${OS} != "FreeBSD" |
|||
.error "This Makefile and module are designed strictly for FreeBSD." |
|||
.endif |
|||
|
|||
.if !empty(LUA_VER) |
|||
.if ${LUA_VER} == "jit" |
|||
LUA_PC= luajit |
|||
.else |
|||
LUA_PC= lua-${LUA_VER} |
|||
.endif |
|||
.else |
|||
LUA_PC!= pkg-config --list-all | grep -E '^(lua-[0-9]|luajit)' | awk '{print $$1}' | sort -V | tail -n 1 |
|||
.endif |
|||
|
|||
LUA_CFLAGS!= pkg-config --cflags ${LUA_PC} |
|||
LUA_MODDIR!= pkg-config --variable=INSTALL_CMOD ${LUA_PC} |
|||
|
|||
CFLAGS+= -O2 -fPIC -Wall ${LUA_CFLAGS} |
|||
LDFLAGS+= -shared -lcam |
|||
|
|||
all: ${LIB_NAME}.so |
|||
|
|||
${LIB_NAME}.so: ${SRC} |
|||
${CC} ${CFLAGS} ${LDFLAGS} -o ${LIB_NAME}.so ${SRC} |
|||
|
|||
clean: |
|||
rm -f ${LIB_NAME}.so |
|||
|
|||
install: all |
|||
mkdir -p ${DESTDIR}${LUA_MODDIR} |
|||
install -m 755 ${LIB_NAME}.so ${DESTDIR}${LUA_MODDIR}/${LIB_NAME}.so |
|||
|
|||
.PHONY: all clean install |
|||
|
|||
@ -0,0 +1,407 @@ |
|||
/*
|
|||
* diskinfo.c - FreeBSD-specific Lua module for gathering disk hardware |
|||
* models, capacities, precise drive types, and active filesystem mount points. |
|||
* |
|||
* This version uses the native FreeBSD libcam library via O_RDWR access. |
|||
* Accurately branches protocols between SCSI SAT for USB bridges (da) and |
|||
* native ATA commands for direct SATA controllers (ada). |
|||
*/ |
|||
|
|||
#ifndef __FreeBSD__ |
|||
#error "This module is FreeBSD-specific and cannot be compiled on this platform." |
|||
#endif |
|||
|
|||
#include <sys/param.h> |
|||
#include <sys/ucred.h> |
|||
#include <sys/mount.h> |
|||
#include <sys/sysctl.h> |
|||
#include <sys/disk.h> |
|||
#include <sys/ioctl.h> |
|||
#include <sys/ata.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <fcntl.h> |
|||
#include <unistd.h> |
|||
#include <stdio.h> |
|||
#include <stdint.h> |
|||
#include <ctype.h> |
|||
|
|||
/* FreeBSD CAM (Common Access Method) userland library interfaces */ |
|||
#include <cam/cam.h> |
|||
#include <cam/cam_ccb.h> |
|||
#include <cam/scsi/scsi_all.h> |
|||
#include <camlib.h> |
|||
|
|||
/* Core Lua API headers */ |
|||
#include <lua.h> |
|||
#include <lauxlib.h> |
|||
|
|||
/* Internal project definitions */ |
|||
#include "smart.h" |
|||
|
|||
/* Backward compatibility layer macro to support Lua 5.1 and LuaJIT environments */ |
|||
#if LUA_VERSION_NUM == 501 |
|||
#define luaL_newlib(L, l) (lua_newtable(L), luaL_register(L, NULL, l)) |
|||
#endif |
|||
|
|||
#define MAX_DISKS 64 |
|||
#define MAX_MOUNTS 128 |
|||
|
|||
/* Simple structure used to sort array components alphabetically via qsort API */ |
|||
typedef struct { |
|||
char name[MNAMELEN]; |
|||
} StringKey; |
|||
|
|||
/* Comparison callback function for alphabetical qsort */ |
|||
static int compare_strings(const void *a, const void *b) { |
|||
return strcmp(((StringKey *)a)->name, ((StringKey *)b)->name); |
|||
} |
|||
|
|||
/* Cleans up trailing spaces and non-printable junk from SCSI inquiry strings */ |
|||
static void clean_scsi_str(char *str) { |
|||
int i = strlen(str) - 1; |
|||
while (i >= 0 && (isspace((unsigned char)str[i]) || str[i] == '\0')) { |
|||
str[i] = '\0'; |
|||
i--; |
|||
} |
|||
} |
|||
|
|||
/* Fixes word-byte-swapping in raw ATA firmware identity blocks */ |
|||
static void ata_str_fix(char *str, size_t len) { |
|||
for (size_t i = 0; i < len - 1; i += 2) { |
|||
char tmp = str[i]; |
|||
str[i] = str[i + 1]; |
|||
str[i + 1] = tmp; |
|||
} |
|||
int i = len - 1; |
|||
while (i >= 0 && (isspace((unsigned char)str[i]) || str[i] == '\0' || str[i] == '\n' || str[i] == '\r')) { |
|||
str[i] = '\0'; |
|||
i--; |
|||
} |
|||
} |
|||
|
|||
/* Extracts base drive token name from slice/partition maps (e.g. "ada0p1" -> "ada0") */ |
|||
static void get_base_disk(const char *dev_name, char *base_buf, size_t buf_len) { |
|||
strlcpy(base_buf, dev_name, buf_len); |
|||
char *p = base_buf; |
|||
while (*p && !(*p >= '0' && *p <= '9')) p++; /* Skip leading letters */ |
|||
while (*p && (*p >= '0' && *p <= '9')) p++; /* Skip index digits */ |
|||
if (*p) *p = '\0'; /* Chop partition suffixes */ |
|||
} |
|||
|
|||
/*
|
|||
* Queries the device using FreeBSD libcam via appropriate protocol. |
|||
* For 'da' (USB) devices: Sends SCSI INQUIRY and SAT ATA PASS-THROUGH (16). |
|||
* For 'ada' (SATA) devices: Sends native ATA IDENTIFY command. |
|||
* Successfully bypasses all controller bugs to read real struct ata_params. |
|||
*/ |
|||
static void query_cam_properties(const char *base_disk, char *model_buf, size_t buf_len, int *is_removable, int *is_ssd, int *pt_failed, int *temp) { |
|||
struct cam_device *cam_dev = NULL; |
|||
union ccb *ccb = NULL; |
|||
struct ata_params *ident_buf = NULL; |
|||
struct smart_values *smart_buf = NULL; |
|||
uint8_t *inq_buf = NULL; |
|||
|
|||
*is_removable = 0; |
|||
*is_ssd = 0; |
|||
*pt_failed = 0; |
|||
*temp = -1; |
|||
|
|||
/* CRITICAL: Must use O_RDWR for transport-layer commands execution path to unlock */ |
|||
cam_dev = cam_open_device(base_disk, O_RDWR); |
|||
if (cam_dev == NULL) return; |
|||
|
|||
ccb = cam_getccb(cam_dev); |
|||
if (ccb == NULL) { |
|||
cam_close_device(cam_dev); |
|||
return; |
|||
} |
|||
|
|||
ident_buf = calloc(1, sizeof(struct ata_params)); |
|||
smart_buf = calloc(1, sizeof(struct smart_values)); |
|||
if (ident_buf == NULL || smart_buf == NULL) { |
|||
free(ident_buf); free(smart_buf); |
|||
cam_freeccb(ccb); cam_close_device(cam_dev); |
|||
return; |
|||
} |
|||
|
|||
/* --- PROTOCOL BRANCH 1: USB / SCSI Direct Access Devices (da) --- */ |
|||
if (strncmp(base_disk, "da", 2) == 0) { |
|||
inq_buf = calloc(1, sizeof(struct scsi_inquiry_data)); |
|||
if (inq_buf == NULL) { |
|||
free(ident_buf); free(smart_buf); cam_freeccb(ccb); cam_close_device(cam_dev); |
|||
return; |
|||
} |
|||
|
|||
/* 1a: SCSI INQUIRY */ |
|||
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio)); |
|||
struct scsi_inquiry_data *inq_data = (struct scsi_inquiry_data *)inq_buf; |
|||
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE, |
|||
(uint8_t *)inq_data, SHORT_INQUIRY_LENGTH, SSD_FULL_SIZE, 6, 5000); |
|||
ccb->csio.cdb_io.cdb_bytes[0] = INQUIRY; |
|||
ccb->csio.cdb_io.cdb_bytes[4] = SHORT_INQUIRY_LENGTH; |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { |
|||
if ((inq_data->device & SID_RMB) != 0) *is_removable = 1; |
|||
char vendor[9], product[17]; |
|||
snprintf(vendor, sizeof(vendor), "%.8s", inq_data->vendor); |
|||
snprintf(product, sizeof(product), "%.16s", inq_data->product); |
|||
clean_scsi_str(vendor); clean_scsi_str(product); |
|||
snprintf(model_buf, buf_len, "%s %s", vendor, product); |
|||
} |
|||
|
|||
/* 1b: SCSI SAT ATA PASS-THROUGH (16) -> IDENTIFY */ |
|||
cam_freeccb(ccb); ccb = cam_getccb(cam_dev); |
|||
if (ccb != NULL) { |
|||
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio)); |
|||
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE, |
|||
(uint8_t *)ident_buf, sizeof(struct ata_params), SSD_FULL_SIZE, 16, 5000); |
|||
|
|||
uint8_t *cdb = ccb->csio.cdb_io.cdb_bytes; |
|||
cdb[0] = 0x85; cdb[1] = 4 << 1; cdb[2] = 0x08 | 0x02; cdb[4] = 1; cdb[14] = 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(model_buf, real_model, buf_len); |
|||
if (ident_buf->media_rotation_rate == 1) *is_ssd = 1; |
|||
} else { |
|||
*pt_failed = 1; |
|||
} |
|||
} |
|||
|
|||
/* 1c: SCSI SAT ATA PASS-THROUGH (16) -> SMART VALUES */ |
|||
if (!(*pt_failed)) { |
|||
cam_freeccb(ccb); ccb = cam_getccb(cam_dev); |
|||
if (ccb != NULL) { |
|||
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio)); |
|||
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE, |
|||
(uint8_t *)smart_buf, sizeof(struct smart_values), SSD_FULL_SIZE, 16, 5000); |
|||
|
|||
uint8_t *cdb = ccb->csio.cdb_io.cdb_bytes; |
|||
cdb[0] = 0x85; cdb[1] = 4 << 1; cdb[2] = 0x08 | 0x02; cdb[4] = 0xD0; cdb[6] = 1; |
|||
cdb[8] = 0x4F; cdb[10] = 0xC2; cdb[14] = 0xB0; |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { |
|||
for (int i = 0; i < 30; i++) { |
|||
struct smart_attribute *a = &smart_buf->attr[i]; |
|||
if (a->id == 194 || a->id == 190) { |
|||
*temp = parse_smart_temperature(a); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
free(inq_buf); |
|||
} |
|||
/* --- PROTOCOL BRANCH 2: Native SATA/ATA Devices (ada) --- */ |
|||
else if (strncmp(base_disk, "ada", 3) == 0) { |
|||
/* 2a: Pure ATA IDENTIFY */ |
|||
memset(&ccb->ataio, 0, sizeof(struct ccb_ataio)); |
|||
ccb->ccb_h.func_code = XPT_ATA_IO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->ccb_h.timeout = 5000; |
|||
ccb->ataio.data_ptr = (uint8_t *)ident_buf; ccb->ataio.dxfer_len = sizeof(struct ata_params); |
|||
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(model_buf, real_model, buf_len); |
|||
if (ident_buf->media_rotation_rate == 1) *is_ssd = 1; |
|||
} |
|||
|
|||
/* 2b: Pure ATA SMART VALUES */ |
|||
cam_freeccb(ccb); ccb = cam_getccb(cam_dev); |
|||
if (ccb != NULL) { |
|||
memset(&ccb->ataio, 0, sizeof(struct ccb_ataio)); |
|||
ccb->ccb_h.func_code = XPT_ATA_IO; ccb->ccb_h.flags = CAM_DIR_IN; ccb->ccb_h.timeout = 5000; |
|||
ccb->ataio.data_ptr = (uint8_t *)smart_buf; ccb->ataio.dxfer_len = sizeof(struct smart_values); |
|||
|
|||
ccb->ataio.cmd.command = 0xB0; ccb->ataio.cmd.features = 0xD0; |
|||
ccb->ataio.cmd.lba_mid = 0x4F; ccb->ataio.cmd.lba_high = 0xC2; |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { |
|||
for (int i = 0; i < 30; i++) { |
|||
struct smart_attribute *a = &smart_buf->attr[i]; |
|||
if (a->id == 194 || a->id == 190) { |
|||
*temp = parse_smart_temperature(a); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
free(ident_buf); free(smart_buf); |
|||
if (ccb != NULL) cam_freeccb(ccb); |
|||
cam_close_device(cam_dev); |
|||
} |
|||
|
|||
/* Fetches the hardware description/model, raw storage capacity and precise drive type */ |
|||
static void get_disk_properties(lua_State *L, const char *base_disk) { |
|||
char path[MAXPATHLEN]; |
|||
char model_buf[256]; |
|||
char type_buf[16]; |
|||
off_t mediasize = 0; |
|||
|
|||
snprintf(path, sizeof(path), "/dev/%s", base_disk); |
|||
strlcpy(model_buf, "Generic Drive", sizeof(model_buf)); |
|||
strlcpy(type_buf, "unknown", sizeof(type_buf)); |
|||
|
|||
if (strncmp(base_disk, "nvme", 4) == 0 || strncmp(base_disk, "nvd", 3) == 0) { |
|||
strlcpy(type_buf, "nvme", sizeof(type_buf)); |
|||
} else if (strncmp(base_disk, "mmcsd", 5) == 0) { |
|||
strlcpy(type_buf, "sd", sizeof(type_buf)); |
|||
} |
|||
|
|||
int fd = open(path, O_RDONLY); |
|||
if (fd >= 0) { |
|||
struct diocgattr_arg arg; |
|||
memset(&arg, 0, sizeof(arg)); |
|||
strlcpy(arg.name, "GEOM::descr", sizeof(arg.name)); |
|||
arg.len = sizeof(arg.value.str); |
|||
if (ioctl(fd, DIOCGATTR, &arg) == 0 && strlen(arg.value.str) > 0) { |
|||
strlcpy(model_buf, arg.value.str, sizeof(model_buf)); |
|||
} |
|||
ioctl(fd, DIOCGMEDIASIZE, &mediasize); |
|||
close(fd); |
|||
} |
|||
|
|||
int is_ssd = 0, is_removable = 0, pt_failed = 0; |
|||
int smart_temp = -1; |
|||
|
|||
if (strncmp(base_disk, "ada", 3) == 0 || strncmp(base_disk, "da", 2) == 0) { |
|||
query_cam_properties(base_disk, model_buf, sizeof(model_buf), &is_removable, &is_ssd, &pt_failed, &smart_temp); |
|||
} |
|||
|
|||
if (strcmp(type_buf, "unknown") == 0) { |
|||
if (strncmp(base_disk, "da", 2) == 0) { |
|||
if (is_removable || pt_failed) strlcpy(type_buf, "usb", sizeof(type_buf)); |
|||
else if (is_ssd) strlcpy(type_buf, "usdssd", sizeof(type_buf)); |
|||
else strlcpy(type_buf, "usdhdd", sizeof(type_buf)); |
|||
} else if (strncmp(base_disk, "ada", 3) == 0) { |
|||
if (is_ssd) strlcpy(type_buf, "ssd", sizeof(type_buf)); |
|||
else strlcpy(type_buf, "hdd", sizeof(type_buf)); |
|||
} |
|||
} |
|||
|
|||
/* Push properties into the Lua table current on top of stack */ |
|||
lua_pushstring(L, model_buf); lua_setfield(L, -2, "model"); |
|||
lua_pushstring(L, type_buf); lua_setfield(L, -2, "type"); |
|||
|
|||
/* Format storage size with binary standards */ |
|||
char size_buf[32]; |
|||
double size_gib = (double)mediasize / (1024.0 * 1024.0 * 1024.0); |
|||
double final_size = size_gib; |
|||
const char *unit = "GiB"; |
|||
if (size_gib >= 1024.0) { final_size = size_gib / 1024.0; unit = "TiB"; } |
|||
if (final_size == (long long)final_size) snprintf(size_buf, sizeof(size_buf), "%lld %s", (long long)final_size, unit); |
|||
else snprintf(size_buf, sizeof(size_buf), "%.1f %s", final_size, unit); |
|||
lua_pushstring(L, size_buf); lua_setfield(L, -2, "size"); |
|||
|
|||
/* Inject smart_temp into Lua fields strictly if it was discovered successfully */ |
|||
if (smart_temp >= 0) { |
|||
lua_pushinteger(L, smart_temp); |
|||
lua_setfield(L, -2, "smart_temp"); |
|||
} |
|||
} |
|||
|
|||
/* Main Lua binding function that builds the nested tree structure */ |
|||
static int lua_get_nested_disks(lua_State *L) { |
|||
StringKey disk_names[MAX_DISKS]; |
|||
int disk_count = 0; |
|||
memset(disk_names, 0, sizeof(disk_names)); |
|||
|
|||
/* Read the list of available physical disks from sysctl kernel tree */ |
|||
char *disks_str = NULL; size_t len = 0; |
|||
if (sysctlbyname("kern.disks", NULL, &len, NULL, 0) == 0) { |
|||
disks_str = malloc(len); |
|||
if (disks_str && sysctlbyname("kern.disks", disks_str, &len, NULL, 0) == 0) { |
|||
char *token = strtok(disks_str, " "); |
|||
while (token != NULL && disk_count < MAX_DISKS) { |
|||
strlcpy(disk_names[disk_count].name, token, sizeof(disk_names[disk_count].name)); |
|||
disk_count++; token = strtok(NULL, " "); |
|||
} |
|||
} |
|||
free(disks_str); |
|||
} |
|||
qsort(disk_names, disk_count, sizeof(StringKey), compare_strings); |
|||
|
|||
/* Fetch the active kernel mount points table using getfsstat */ |
|||
struct statfs *mntbuf = NULL; |
|||
int num_mounts = getfsstat(NULL, 0, MNT_NOWAIT); |
|||
if (num_mounts > 0) { |
|||
size_t bufsize = (num_mounts + 4) * sizeof(struct statfs); |
|||
mntbuf = malloc(bufsize); |
|||
if (mntbuf) num_mounts = getfsstat(mntbuf, bufsize, MNT_NOWAIT); |
|||
} |
|||
|
|||
/* Create the main root Lua table and its accompanying order tracker */ |
|||
lua_newtable(L); lua_newtable(L); |
|||
for (int i = 0; i < disk_count; i++) { |
|||
lua_pushstring(L, disk_names[i].name); lua_rawseti(L, -2, i + 1); |
|||
} |
|||
lua_setfield(L, -2, "_order"); |
|||
|
|||
/* Populate metrics, sub-tables and partitions for each physical disk discovered */ |
|||
for (int i = 0; i < disk_count; i++) { |
|||
char *current_disk = disk_names[i].name; |
|||
|
|||
lua_newtable(L); |
|||
get_disk_properties(L, current_disk); |
|||
|
|||
lua_newtable(L); |
|||
StringKey part_names[MAX_MOUNTS]; int part_count = 0; |
|||
if (mntbuf && num_mounts > 0) { |
|||
for (int m = 0; m < num_mounts; m++) { |
|||
char *dev_name = mntbuf[m].f_mntfromname; |
|||
if (strncmp(dev_name, "/dev/", 5) == 0) dev_name += 5; |
|||
char base_disk[MNAMELEN]; get_base_disk(dev_name, base_disk, sizeof(base_disk)); |
|||
if (strcmp(base_disk, current_disk) == 0 && part_count < MAX_MOUNTS) { |
|||
strlcpy(part_names[part_count].name, dev_name, sizeof(part_names[part_count].name)); |
|||
part_count++; |
|||
} |
|||
} |
|||
} |
|||
qsort(part_names, part_count, sizeof(StringKey), compare_strings); |
|||
|
|||
/* Create inner partition index arrays */ |
|||
lua_newtable(L); |
|||
for (int p = 0; p < part_count; p++) { |
|||
lua_pushstring(L, part_names[p].name); lua_rawseti(L, -2, p + 1); |
|||
} |
|||
lua_setfield(L, -2, "_order"); |
|||
|
|||
/* Intersect active mount parameters into final sub-objects */ |
|||
for (int p = 0; p < part_count; p++) { |
|||
for (int m = 0; m < num_mounts; m++) { |
|||
char *dev_name = mntbuf[m].f_mntfromname; |
|||
if (strncmp(dev_name, "/dev/", 5) == 0) dev_name += 5; |
|||
if (strcmp(dev_name, part_names[p].name) == 0) { |
|||
lua_newtable(L); |
|||
lua_pushstring(L, mntbuf[m].f_mntonname); lua_setfield(L, -2, "path"); |
|||
lua_pushstring(L, mntbuf[m].f_fstypename); lua_setfield(L, -2, "type"); |
|||
lua_setfield(L, -2, part_names[p].name); break; |
|||
} |
|||
} |
|||
} |
|||
lua_setfield(L, -2, "mounts"); lua_setfield(L, -2, current_disk); |
|||
} |
|||
if (mntbuf) free(mntbuf); |
|||
return 1; |
|||
} |
|||
|
|||
/* Official Lua Shared Library registration hooks definition */ |
|||
int luaopen_diskinfo(lua_State *L) { |
|||
static const struct luaL_Reg mylib[] = { |
|||
{ "get_info", lua_get_nested_disks }, |
|||
{ NULL, NULL } |
|||
}; |
|||
luaL_newlib(L, mylib); |
|||
return 1; |
|||
} |
|||
|
|||
@ -0,0 +1,112 @@ |
|||
/*
|
|||
* smart.h - Shared ATA S.M.A.R.T. structures and attribute definitions |
|||
* for diskinfo module and smartdump utility. |
|||
* |
|||
* Supports cross-vendor parsing for HDD (including Helium drives), |
|||
* SATA SSD, and Enterprise storage controllers. |
|||
*/ |
|||
|
|||
#ifndef _DISKINFO_SMART_H_ |
|||
#define _DISKINFO_SMART_H_ |
|||
|
|||
#include <stdint.h> |
|||
|
|||
#pragma pack(push, 1) |
|||
/* 12-byte layout of an individual ATA S.M.A.R.T. attribute */ |
|||
struct smart_attribute { |
|||
uint8_t id; /* Attribute ID */ |
|||
uint16_t status; /* Status flags */ |
|||
uint8_t current; /* Normalized Value */ |
|||
uint8_t worst; /* Worst Value */ |
|||
uint8_t raw[6]; /* 48-bit Raw Value register matrix */ |
|||
uint8_t reserv; |
|||
}; |
|||
|
|||
/* 512-byte payload sector returned by SMART READ DATA (0xD0) */ |
|||
struct smart_values { |
|||
uint16_t rev; /* Structure revision */ |
|||
struct smart_attribute attr[30]; /* 30 sequential attributes */ |
|||
uint8_t reserved[149];/* Vendor specific padding bytes */ |
|||
uint8_t chksum; /* Data integrity checksum */ |
|||
}; |
|||
#pragma pack(pop) |
|||
|
|||
/*
|
|||
* Universal and safe cross-vendor temperature parsing rule. |
|||
* Grabs the first raw byte for Seagate's noisy multi-byte registers |
|||
* and reads the full 48-bit value for Toshiba/WD/Samsung/Intel, |
|||
* safely filtering out vendor-specific debug telemetry. |
|||
*/ |
|||
static inline int parse_smart_temperature(struct smart_attribute *a) { |
|||
uint64_t raw_val = 0; |
|||
for (int b = 0; b < 6; b++) { |
|||
raw_val |= ((uint64_t)a->raw[b]) << (b * 8); |
|||
} |
|||
|
|||
/* If the full 48-bit value is reasonable, return it directly */ |
|||
if (raw_val > 0 && raw_val < 150) { |
|||
return (int)raw_val; |
|||
} |
|||
/* Fallback for Seagate: current real temperature is always in the first byte */ |
|||
return (int)a->raw[0]; |
|||
} |
|||
|
|||
/*
|
|||
* Universal ATA/ATAPI SMART Attribute dictionary compliant with T13 standard. |
|||
* Declared as static inline to safely include across multiple compilation units. |
|||
*/ |
|||
static inline const char *get_attr_name(uint8_t id) { |
|||
switch (id) { |
|||
case 1: return "Raw_Read_Error_Rate"; |
|||
case 2: return "Throughput_Performance"; |
|||
case 3: return "Spin_Up_Time"; |
|||
case 4: return "Start_Stop_Count"; |
|||
case 5: return "Reallocated_Sector_Ct"; |
|||
case 7: return "Seek_Error_Rate"; |
|||
case 8: return "Seek_Time_Performance"; |
|||
case 9: return "Power_On_Hours"; |
|||
case 10: return "Spin_Retry_Count"; |
|||
case 12: return "Power_Cycle_Count"; |
|||
case 16: return "Helium_Level_WD"; |
|||
case 22: return "Current_Helium_Level"; /* HGST / Hitachi Helium Drives */ |
|||
case 168: return "SATA_Phy_Error_Count"; |
|||
case 170: return "Bad_Block_Count_SSD"; |
|||
case 171: return "Program_Fail_Count_SSD"; |
|||
case 172: return "Erase_Fail_Count_SSD"; |
|||
case 173: return "Wear_Leveling_Count"; /* Universal SSD Wear Counter */ |
|||
case 174: return "Unexpected_Power_Loss"; |
|||
case 175: return "Power_Loss_Protect_Fail"; /* Intel SSD Capacitor Monitor */ |
|||
case 177: return "Wear_Range_Delta"; |
|||
case 179: return "Used_Rsvd_Blk_Cnt_Tot"; |
|||
case 181: return "Program_Fail_Count_SSD"; |
|||
case 182: return "Erase_Fail_Count_SSD"; |
|||
case 183: return "SATA_Downshift_Error_Ct"; |
|||
case 184: return "End-to-End_Error"; |
|||
case 187: return "Reported_Uncorrect"; |
|||
case 188: return "Command_Timeout"; |
|||
case 189: return "High_Fly_Writes"; |
|||
case 190: return "Airflow_Temperature_Cel"; |
|||
case 192: return "Power-Off_Retract_Count"; |
|||
case 193: return "Load_Cycle_Count"; |
|||
case 194: return "Temperature_Celsius"; |
|||
case 195: return "Hardware_ECC_Recovered"; |
|||
case 196: return "Reallocation_Event_Ct"; |
|||
case 197: return "Current_Pending_Sector"; /* Crucial HDD Health Marker */ |
|||
case 198: return "Offline_Uncorrectable"; |
|||
case 199: return "UDMA_CRC_Error_Count"; /* Interface/Cable Error Counter */ |
|||
case 202: return "Percentage_Used_Life"; /* SSD Remaining Life % */ |
|||
case 218: return "SATA_CRC_Error_Count"; |
|||
case 231: return "SSD_Life_Left"; |
|||
case 232: return "Available_Reserv_Space"; /* SSD Available Reserved Flash % */ |
|||
case 233: return "Media_Wearout_Indicator"; |
|||
case 235: return "POR_Recovery_Count"; /* Samsung SSD Panic Recovery Count */ |
|||
case 240: return "Head_Flying_Hours"; |
|||
case 241: return "Total_LBAs_Written"; /* NAND TBW Metric */ |
|||
case 242: return "Total_LBAs_Read"; /* Host Reads Metric */ |
|||
case 244: return "Thermal_Throttle_Count"; /* SSD Overheating Throttling Events */ |
|||
default: return "Vendor_Specific"; |
|||
} |
|||
} |
|||
|
|||
#endif /* _DISKINFO_SMART_H_ */ |
|||
|
|||
@ -0,0 +1,159 @@ |
|||
/*
|
|||
* cc -O2 -Wall camtest.c -lcam -o camtest |
|||
*/ |
|||
|
|||
#include <sys/param.h> |
|||
#include <sys/ioctl.h> |
|||
#include <sys/disk.h> |
|||
#include <sys/ata.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <fcntl.h> |
|||
#include <unistd.h> |
|||
#include <stdio.h> |
|||
#include <ctype.h> |
|||
|
|||
/* Official FreeBSD CAM Library Interface Headers */ |
|||
#include <camlib.h> |
|||
#include <cam/scsi/scsi_all.h> |
|||
|
|||
static void trim_str(char *str) { |
|||
int i = strlen(str) - 1; |
|||
while (i >= 0 && (isspace((unsigned char)str[i]) || str[i] == '\0')) { |
|||
str[i] = '\0'; |
|||
i--; |
|||
} |
|||
} |
|||
|
|||
static void ata_str_fix(char *str, size_t len) { |
|||
for (size_t i = 0; i < len - 1; i += 2) { |
|||
char tmp = str[i]; |
|||
str[i] = str[i + 1]; |
|||
str[i + 1] = tmp; |
|||
} |
|||
trim_str(str); |
|||
} |
|||
|
|||
int main(int argc, char **argv) { |
|||
if (argc < 2) { |
|||
fprintf(stderr, "Usage: %s <device_name> (e.g. ada0 or da0)\n", argv[0]); |
|||
return 1; |
|||
} |
|||
|
|||
const char *disk = argv[1]; |
|||
printf("=== Testing device via libcam (O_RDWR): %s ===\n\n", disk); |
|||
|
|||
/* -------------------------------------------------------------
|
|||
* TEST 1: Open device via libcam using O_RDWR |
|||
* ------------------------------------------------------------- */ |
|||
struct cam_device *cam_dev = cam_open_device(disk, O_RDWR); |
|||
if (cam_dev == NULL) { |
|||
fprintf(stderr, "Error: cam_open_device failed for '%s'.\n", disk); |
|||
fprintf(stderr, "Check permissions on /dev/xpt0 and /dev/pass* (group operator/devfs.rules).\n"); |
|||
return 1; |
|||
} |
|||
|
|||
union ccb *ccb = cam_getccb(cam_dev); |
|||
if (ccb == NULL) { |
|||
fprintf(stderr, "Error: Failed to allocate CCB block\n"); |
|||
cam_close_device(cam_dev); |
|||
return 1; |
|||
} |
|||
|
|||
/* Allocate 512 bytes alignment buffer for responses */ |
|||
uint8_t *data_buf = calloc(1, 512); |
|||
if (data_buf == NULL) { |
|||
fprintf(stderr, "Error: Memory allocation failed\n"); |
|||
cam_freeccb(ccb); |
|||
cam_close_device(cam_dev); |
|||
return 1; |
|||
} |
|||
|
|||
/* -------------------------------------------------------------
|
|||
* TEST 2: Pure SCSI INQUIRY |
|||
* ------------------------------------------------------------- */ |
|||
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio)); |
|||
struct scsi_inquiry_data *inq = (struct scsi_inquiry_data *)data_buf; |
|||
|
|||
cam_fill_csio(&ccb->csio, |
|||
/*retries*/ 2, |
|||
/*cbfcnp*/ NULL, |
|||
/*flags*/ CAM_DIR_IN, |
|||
/*tag_action*/ CAM_TAG_ACTION_NONE, |
|||
/*data_ptr*/ (uint8_t *)inq, |
|||
/*dxfer_len*/ SHORT_INQUIRY_LENGTH, |
|||
/*sense_len*/ SSD_FULL_SIZE, |
|||
/*cdb_len*/ 6, |
|||
/*timeout*/ 5000); |
|||
|
|||
/* Safely fill raw SCSI INQUIRY command bytes */ |
|||
ccb->csio.cdb_io.cdb_bytes[0] = INQUIRY; |
|||
ccb->csio.cdb_io.cdb_bytes[4] = SHORT_INQUIRY_LENGTH; |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { |
|||
char vendor[9], product[17]; |
|||
snprintf(vendor, sizeof(vendor), "%.8s", inq->vendor); |
|||
snprintf(product, sizeof(product), "%.16s", inq->product); |
|||
trim_str(vendor); trim_str(product); |
|||
|
|||
printf("[SCSI INQUIRY]:\n"); |
|||
printf(" Vendor : %s\n", vendor); |
|||
printf(" Product : %s\n", product); |
|||
printf(" Removable : %s (RMB Bit)\n", (inq->device & SID_RMB) ? "Yes (Flash/SD)" : "No (Fixed/HDD/SSD)"); |
|||
} else { |
|||
printf("[SCSI INQUIRY]: Failed (CAM Status: 0x%X)\n", ccb->ccb_h.status); |
|||
} |
|||
|
|||
/* -------------------------------------------------------------
|
|||
* TEST 3: SCSI SAT ATA PASS-THROUGH (16) -> ATA IDENTIFY |
|||
* ------------------------------------------------------------- */ |
|||
cam_freeccb(ccb); |
|||
ccb = cam_getccb(cam_dev); |
|||
if (ccb == NULL) { |
|||
free(data_buf); |
|||
cam_close_device(cam_dev); |
|||
return 1; |
|||
} |
|||
|
|||
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio)); |
|||
memset(data_buf, 0, 512); |
|||
struct ata_params *ident_buf = (struct ata_params *)data_buf; |
|||
|
|||
cam_fill_csio(&ccb->csio, |
|||
/*retries*/ 2, |
|||
/*cbfcnp*/ NULL, |
|||
/*flags*/ CAM_DIR_IN, |
|||
/*tag_action*/ CAM_TAG_ACTION_NONE, |
|||
/*data_ptr*/ (uint8_t *)ident_buf, |
|||
/*dxfer_len*/ sizeof(struct ata_params), |
|||
/*sense_len*/ SSD_FULL_SIZE, |
|||
/*cdb_len*/ 16, |
|||
/*timeout*/ 5000); |
|||
|
|||
/* Construct manual SAT ATA PASS-THROUGH (16) command block array */ |
|||
uint8_t *cdb = ccb->csio.cdb_io.cdb_bytes; |
|||
cdb[0] = 0x85; /* ATA PASS-THROUGH (16) Opcode */ |
|||
cdb[1] = 4 << 1; /* Protocol: PIO Data-In */ |
|||
cdb[2] = 0x08 | 0x02; /* T_DIR = 1 (Data In), ByteBlock = 1 (Block mode) */ |
|||
cdb[4] = 1; /* Sector Count = 1 sector (512 bytes) */ |
|||
cdb[14] = 0xEC; /* ATA Command: IDENTIFY DEVICE */ |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) == 0 && (ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { |
|||
char ata_model[41]; |
|||
memcpy(ata_model, ident_buf->model, sizeof(ident_buf->model)); |
|||
ata_model[40] = '\0'; |
|||
ata_str_fix(ata_model, sizeof(ata_model)); |
|||
|
|||
printf("\n[SCSI ATA PASS-THROUGH (16)]:\n"); |
|||
printf(" ATA Model : %s\n", ata_model); |
|||
printf(" Media RPM : %d (0=SSD, 1=Unknown)\n", ident_buf->media_rotation_rate); |
|||
} else { |
|||
printf("\n[SCSI ATA PASS-THROUGH (16)]: Failed / rejected by bridge (CAM Status: 0x%X)\n", ccb->ccb_h.status); |
|||
} |
|||
|
|||
free(data_buf); |
|||
cam_freeccb(ccb); |
|||
cam_close_device(cam_dev); |
|||
return 0; |
|||
} |
|||
|
|||
@ -0,0 +1,154 @@ |
|||
/*
|
|||
* smartdump.c - FreeBSD CLI utility for querying and decoding |
|||
* disk hardware S.M.A.R.T. records directly via the CAM subsystem. |
|||
* |
|||
* Can output raw binary stream for piping or human-readable decoded matrix. |
|||
* |
|||
* cc -O2 -Wall smartdump.c -lcam -o smartdump |
|||
*/ |
|||
|
|||
#include <sys/param.h> |
|||
#include <sys/ioctl.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <fcntl.h> |
|||
#include <unistd.h> |
|||
#include <stdio.h> |
|||
#include <ctype.h> |
|||
#include <stdint.h> |
|||
|
|||
/* FreeBSD CAM Library Interface Headers */ |
|||
#include <camlib.h> |
|||
#include <cam/scsi/scsi_all.h> |
|||
|
|||
/* Import local shared SMART layout matrix definitions */ |
|||
#include "../src/smart.h" |
|||
|
|||
/* Cross-vendor SMART buffer decoder and normalizer */ |
|||
static void decode_smart(struct smart_values *smart_buf) { |
|||
printf("ID# ATTRIBUTE_NAME VALUE WORST RAW_VALUE\n"); |
|||
printf("==================================================================\n"); |
|||
|
|||
for (int i = 0; i < 30; i++) { |
|||
struct smart_attribute *a = &smart_buf->attr[i]; |
|||
if (a->id == 0) continue; |
|||
|
|||
uint64_t raw_val = 0; |
|||
|
|||
/* Enforce custom cross-vendor temperature extraction rule */ |
|||
if (a->id == 194 || a->id == 190) { |
|||
raw_val = parse_smart_temperature(a); |
|||
} else { |
|||
/* Assemble clean 48-bit metric integer for standard counters */ |
|||
for (int b = 0; b < 6; b++) { |
|||
raw_val |= ((uint64_t)a->raw[b]) << (b * 8); |
|||
} |
|||
} |
|||
|
|||
printf("%3d %-23s %3d %3d %llu\n", |
|||
a->id, get_attr_name(a->id), a->current, a->worst, (unsigned long long)raw_val); |
|||
} |
|||
} |
|||
|
|||
int main(int argc, char **argv) { |
|||
int ch; |
|||
int mode_decode = 0; |
|||
|
|||
/* Parse configuration operational runtime flag args */ |
|||
while ((ch = getopt(argc, argv, "dr")) != -1) { |
|||
switch (ch) { |
|||
case 'd': mode_decode = 1; break; |
|||
case 'r': mode_decode = 0; break; |
|||
default: |
|||
fprintf(stderr, "Usage: %s [-d|-r] <device_name>\n", argv[0]); |
|||
return 1; |
|||
} |
|||
} |
|||
argc -= optind; |
|||
argv += optind; |
|||
|
|||
if (argc < 1) { |
|||
fprintf(stderr, "Usage: %s [-d|-r] <device_name> (e.g. ada1 or da0)\n", argv[-optind]); |
|||
return 1; |
|||
} |
|||
|
|||
const char *disk = argv[0]; |
|||
struct cam_device *cam_dev = NULL; |
|||
union ccb *ccb = NULL; |
|||
struct smart_values *smart_buf = NULL; |
|||
|
|||
/* Open device via O_RDWR to unlock transport-layer CCB execution paths */ |
|||
cam_dev = cam_open_device(disk, O_RDWR); |
|||
if (cam_dev == NULL) { |
|||
fprintf(stderr, "Error: cam_open_device failed for '%s'. Check permissions.\n", disk); |
|||
return 1; |
|||
} |
|||
|
|||
ccb = cam_getccb(cam_dev); |
|||
if (ccb == NULL) { |
|||
fprintf(stderr, "Error: Failed to allocate CCB block\n"); |
|||
cam_close_device(cam_dev); |
|||
return 1; |
|||
} |
|||
|
|||
smart_buf = calloc(1, sizeof(struct smart_values)); |
|||
if (smart_buf == NULL) { |
|||
fprintf(stderr, "Error: Memory allocation failed\n"); |
|||
cam_freeccb(ccb); |
|||
cam_close_device(cam_dev); |
|||
return 1; |
|||
} |
|||
|
|||
/* --- PROTOCOL BRANCH 1: Native SATA/ATA Devices (ada) --- */ |
|||
if (strncmp(disk, "ada", 3) == 0) { |
|||
cam_fill_ataio(&ccb->ataio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE, |
|||
(uint8_t *)smart_buf, sizeof(struct smart_values), 5000); |
|||
|
|||
ccb->ataio.cmd.command = 0xB0; /* ATA SMART Command Opcode */ |
|||
ccb->ataio.cmd.features = 0xD0; /* SMART READ DATA Command Feature Code */ |
|||
ccb->ataio.cmd.lba_mid = 0x4F; /* Subsystem Spec Signature Marker */ |
|||
ccb->ataio.cmd.lba_high = 0xC2; /* Subsystem Spec Signature Marker */ |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) != 0 || (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { |
|||
fprintf(stderr, "Error: ATA SMART command failed (CAM Status: 0x%X)\n", ccb->ccb_h.status); |
|||
free(smart_buf); cam_freeccb(ccb); cam_close_device(cam_dev); return 1; |
|||
} |
|||
} |
|||
/* --- PROTOCOL BRANCH 2: USB / SCSI Bridges (da) --- */ |
|||
else if (strncmp(disk, "da", 2) == 0) { |
|||
memset(&ccb->csio, 0, sizeof(struct ccb_scsiio)); |
|||
cam_fill_csio(&ccb->csio, 2, NULL, CAM_DIR_IN, CAM_TAG_ACTION_NONE, |
|||
(uint8_t *)smart_buf, sizeof(struct smart_values), SSD_FULL_SIZE, 16, 5000); |
|||
|
|||
uint8_t *cdb = ccb->csio.cdb_io.cdb_bytes; |
|||
cdb[0] = 0x85; /* SCSI Opcode: ATA PASS-THROUGH (16) */ |
|||
cdb[1] = 4 << 1; /* Protocol: PIO Data-In */ |
|||
cdb[2] = 0x08 | 0x02; /* T_DIR = 1 (Data-In), ByteBlock = 1 (Sectors mode) */ |
|||
cdb[4] = 0xD0; /* Features (lower) -> SMART READ DATA */ |
|||
cdb[6] = 1; /* Sector Count = 1 sector (512 bytes) */ |
|||
cdb[8] = 0x4F; /* LBA Mid -> SMART Signature Mid */ |
|||
cdb[10] = 0xC2; /* LBA High -> SMART Signature High */ |
|||
cdb[14] = 0xB0; /* ATA Command -> SMART Command Opcode */ |
|||
|
|||
if (cam_send_ccb(cam_dev, ccb) != 0 || (ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { |
|||
fprintf(stderr, "Error: SCSI SAT SMART command failed (CAM Status: 0x%X)\n", ccb->ccb_h.status); |
|||
free(smart_buf); cam_freeccb(ccb); cam_close_device(cam_dev); return 1; |
|||
} |
|||
} else { |
|||
fprintf(stderr, "Error: Unsupported device driver type target routing path.\n"); |
|||
free(smart_buf); cam_freeccb(ccb); cam_close_device(cam_dev); return 1; |
|||
} |
|||
|
|||
/* Route output generation maps according to active selection flag switches */ |
|||
if (mode_decode) { |
|||
decode_smart(smart_buf); |
|||
} else { |
|||
fwrite(smart_buf, 1, 512, stdout); |
|||
} |
|||
|
|||
free(smart_buf); |
|||
cam_freeccb(ccb); |
|||
cam_close_device(cam_dev); |
|||
return 0; |
|||
} |
|||
|
|||
Loading…
Reference in new issue