diff options
| author | Chuck Tuffli <chuck@FreeBSD.org> | 2026-04-28 16:55:50 +0000 |
|---|---|---|
| committer | Chuck Tuffli <chuck@FreeBSD.org> | 2026-04-28 17:08:27 +0000 |
| commit | 68e5b71517e947b4e3f349c970af362b47b45f27 (patch) | |
| tree | 8881f6a0a7e40eec89c1a0f8bdd90d34a0015527 | |
| parent | 5ed26c21e4ff1d478d4611abbf3dc14cc1b77244 (diff) | |
Vendor import of smart at 1.0.2
smart/diskhealth is a command line application to monitor disk health
from a storage device via SMART.
Reviewed by: fuz, jrm
Relnotes: yes
Differential Revision: https://reviews.freebsd.org/D56638
| -rw-r--r-- | contrib/smart/Changelog | 37 | ||||
| -rw-r--r-- | contrib/smart/LICENSE | 13 | ||||
| -rw-r--r-- | contrib/smart/Makefile | 26 | ||||
| -rw-r--r-- | contrib/smart/freebsd_dev.c | 828 | ||||
| -rw-r--r-- | contrib/smart/libsmart.c | 1359 | ||||
| -rw-r--r-- | contrib/smart/libsmart.h | 174 | ||||
| -rw-r--r-- | contrib/smart/libsmart_desc.c | 158 | ||||
| -rw-r--r-- | contrib/smart/libsmart_dev.h | 60 | ||||
| -rw-r--r-- | contrib/smart/libsmart_priv.h | 83 | ||||
| -rw-r--r-- | contrib/smart/smart.8 | 245 | ||||
| -rw-r--r-- | contrib/smart/smart.c | 334 | ||||
| -rw-r--r-- | packages/Makefile | 1 | ||||
| -rw-r--r-- | packages/smart/Makefile | 4 | ||||
| -rw-r--r-- | packages/smart/smart.ucl | 30 | ||||
| -rw-r--r-- | usr.sbin/Makefile | 1 | ||||
| -rw-r--r-- | usr.sbin/smart/Makefile | 8 |
16 files changed, 3361 insertions, 0 deletions
diff --git a/contrib/smart/Changelog b/contrib/smart/Changelog new file mode 100644 index 000000000000..42b79bc34070 --- /dev/null +++ b/contrib/smart/Changelog @@ -0,0 +1,37 @@ +This file documents changes for smart releases + +version 1.0.2 + - Bring man page up to snuff + - Fix various complier warnings + +version 1.0.1 + - Fix don't print attribute ID with description + +version 1.0.0 + - Fix ATA threshold output (gh-10). This is a breaking change as it + reduces the output from 4 fields to 3 (drops the "reserved" byte + from threshold). + - Fix the ATA raw output. This is a breaking change as it increase the + output from 6 bytes to 7 (i.e., includes the "reserved" byte). Note + that while some attributes use this byte, most do not. + - Fix direct debug output (--debug) to standard error + - Use POSIX memcpy and memset instead of older bXXX equivalents + +version 0.4.2 + - Update README contents + +version 0.4.1 + - Allow a comma-separated list of attributes + - Code refactor + update code comments + +version 0.3.0 + + - Reclaim the -d option from debug + - Change field separator from spaces to tab + - Add textual descriptions of attribute IDs for ATA, NVMe, and SCSI + - Add a manual page + - Fixes + * libxo structure for attribute and attributes + * simplify LIBXO ifdef sprawl + * display of threshold values + * display of long values diff --git a/contrib/smart/LICENSE b/contrib/smart/LICENSE new file mode 100644 index 000000000000..8b0a0bf6a4a6 --- /dev/null +++ b/contrib/smart/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2016-2026 Chuck Tuffli <chuck@tuffli.net> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/contrib/smart/Makefile b/contrib/smart/Makefile new file mode 100644 index 000000000000..64cab720e08f --- /dev/null +++ b/contrib/smart/Makefile @@ -0,0 +1,26 @@ +# +# Copyright (c) 2016-2021 Chuck Tuffli <chuck@tuffli.net> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +PROG= smart +SRCS= smart.c libsmart.c libsmart_desc.c +SRCS+= freebsd_dev.c +LIBADD= cam xo +MAN=smart.8 +MLINKS= smart.8 diskhealth.8 +#CFLAGS+= -ggdb -O0 +CFLAGS+= -DLIBXO +LINKS= ${BINDIR}/smart ${BINDIR}/diskhealth + +.include <bsd.prog.mk> diff --git a/contrib/smart/freebsd_dev.c b/contrib/smart/freebsd_dev.c new file mode 100644 index 000000000000..d1dda2289742 --- /dev/null +++ b/contrib/smart/freebsd_dev.c @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2016-2021 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <err.h> +#include <errno.h> +#include <camlib.h> +#include <cam/scsi/scsi_message.h> + +#include "libsmart.h" +#include "libsmart_priv.h" +#include "libsmart_dev.h" + +/* Provide compatibility for FreeBSD 11.0 */ +#if (__FreeBSD_version < 1101000) + +struct scsi_log_informational_exceptions { + struct scsi_log_param_header hdr; +#define SLP_IE_GEN 0x0000 + uint8_t ie_asc; + uint8_t ie_ascq; + uint8_t temperature; +}; + +#endif + +struct fbsd_smart { + smart_t common; + struct cam_device *camdev; +}; + +static smart_protocol_e __device_get_proto(struct fbsd_smart *); +static bool __device_proto_tunneled(struct fbsd_smart *); +static int32_t __device_get_info(struct fbsd_smart *); + +smart_h +device_open(smart_protocol_e protocol, char *devname) +{ + struct fbsd_smart *h = NULL; + + h = malloc(sizeof(struct fbsd_smart)); + if (h == NULL) + return NULL; + + memset(h, 0, sizeof(struct fbsd_smart)); + + h->common.protocol = SMART_PROTO_MAX; + h->camdev = cam_open_device(devname, O_RDWR); + if (h->camdev == NULL) { + printf("%s: error opening %s - %s\n", + __func__, devname, + cam_errbuf); + free(h); + h = NULL; + } else { + smart_protocol_e proto = __device_get_proto(h); + + if ((protocol == SMART_PROTO_AUTO) || + (protocol == proto)) { + h->common.protocol = proto; + } else { + printf("%s: protocol mismatch %d vs %d\n", + __func__, protocol, proto); + } + + if (proto == SMART_PROTO_SCSI) { + if (__device_proto_tunneled(h)) { + h->common.protocol = SMART_PROTO_ATA; + h->common.info.tunneled = 1; + } + } + + __device_get_info(h); + } + + return h; +} + +void +device_close(smart_h h) +{ + struct fbsd_smart *fsmart = h; + + if (fsmart != NULL) { + if (fsmart->camdev != NULL) { + cam_close_device(fsmart->camdev); + } + + free(fsmart); + } +} + +static const uint8_t smart_read_data[] = { + 0xb0, 0xd0, 0x00, 0x4f, 0xc2, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const uint8_t smart_return_status[] = { + 0xb0, 0xda, 0x00, 0x4f, 0xc2, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int32_t +__device_read_ata(smart_h h, uint32_t page, void *buf, size_t bsize, union ccb *ccb) +{ + struct fbsd_smart *fsmart = h; + const uint8_t *smart_fis; + uint32_t smart_fis_size = 0; + uint32_t cam_flags = 0; + uint16_t sector_count = 0; + uint8_t protocol = 0; + + switch (page) { + case PAGE_ID_ATA_SMART_READ_DATA: /* Support SMART READ DATA */ + smart_fis = smart_read_data; + smart_fis_size = sizeof(smart_read_data); + cam_flags = CAM_DIR_IN; + sector_count = 1; + protocol = AP_PROTO_PIO_IN; + break; + case PAGE_ID_ATA_SMART_RET_STATUS: /* Support SMART RETURN STATUS */ + smart_fis = smart_return_status; + smart_fis_size = sizeof(smart_return_status); + /* Command has no data but uses the return status */ + cam_flags = CAM_DIR_NONE; + protocol = AP_PROTO_NON_DATA; + bsize = 0; + break; + default: + return EINVAL; + } + + if (fsmart->common.info.tunneled) { + struct ata_pass_16 *cdb; + uint8_t cdb_flags; + + if (bsize > 0) { + cdb_flags = AP_FLAG_TDIR_FROM_DEV | + AP_FLAG_BYT_BLOK_BLOCKS | + AP_FLAG_TLEN_SECT_CNT; + } else { + cdb_flags = AP_FLAG_CHK_COND | + AP_FLAG_TDIR_FROM_DEV | + AP_FLAG_BYT_BLOK_BLOCKS; + } + + cdb = (struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes; + memset(cdb, 0, sizeof(*cdb)); + + scsi_ata_pass_16(&ccb->csio, + /*retries*/ 1, + /*cbfcnp*/ NULL, + /*flags*/ cam_flags, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*protocol*/ protocol, + /*ata_flags*/ cdb_flags, + /*features*/ page, + /*sector_count*/sector_count, + /*lba*/ 0, + /*command*/ ATA_SMART_CMD, + /*control*/ 0, + /*data_ptr*/ buf, + /*dxfer_len*/ bsize, + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ 5000 + ); + cdb->lba_mid = 0x4f; + cdb->lba_high = 0xc2; + cdb->device = 0; /* scsi_ata_pass_16() sets this */ + } else { + memcpy(&ccb->ataio.cmd.command, smart_fis, smart_fis_size); + + cam_fill_ataio(&ccb->ataio, + /* retries */1, + /* cbfcnp */NULL, + /* flags */cam_flags, + /* tag_action */0, + /* data_ptr */buf, + /* dxfer_len */bsize, + /* timeout */5000); + ccb->ataio.cmd.flags |= CAM_ATAIO_NEEDRESULT; + ccb->ataio.cmd.control = 0; + } + + return 0; +} + +static int32_t +__device_read_scsi(__attribute__((unused)) smart_h h, uint32_t page, void *buf, size_t bsize, union ccb *ccb) +{ + + scsi_log_sense(&ccb->csio, + /* retries */1, + /* cbfcnp */NULL, + /* tag_action */0, + /* page_code */SLS_PAGE_CTRL_CUMULATIVE, + /* page */page, + /* save_pages */0, + /* ppc */0, + /* paramptr */0, + /* param_buf */buf, + /* param_len */bsize, + /* sense_len */0, + /* timeout */5000); + + return 0; +} + +static int32_t +__device_read_nvme(__attribute__((unused)) smart_h h, uint32_t page, void *buf, size_t bsize, union ccb *ccb) +{ + struct ccb_nvmeio *nvmeio = &ccb->nvmeio; + uint32_t numd = 0; /* number of dwords */ + + /* + * NVME CAM passthru + * 1200000 > version > 1101510 uses nvmeio->cmd.opc + * 1200059 > version > 1200038 uses nvmeio->cmd.opc + * 1200081 > version > 1200058 uses nvmeio->cmd.opc_fuse + * > 1200080 uses nvmeio->cmd.opc + * This code doesn't support the brief 'opc_fuse' period. + */ +#if ((__FreeBSD_version > 1200038) || ((__FreeBSD_version > 1101510) && (__FreeBSD_version < 1200000))) + switch (page) { + case NVME_LOG_HEALTH_INFORMATION: + numd = (sizeof(struct nvme_health_information_page) / sizeof(uint32_t)); + break; + default: + /* Unsupported log page */ + return EINVAL; + } + + /* Subtract 1 because NUMD is a zero based value */ + numd--; + + nvmeio->cmd.opc = NVME_OPC_GET_LOG_PAGE; + nvmeio->cmd.nsid = NVME_GLOBAL_NAMESPACE_TAG; + nvmeio->cmd.cdw10 = page | (numd << 16); + + cam_fill_nvmeadmin(&ccb->nvmeio, + /* retries */1, + /* cbfcnp */NULL, + /* flags */CAM_DIR_IN, + /* data_ptr */buf, + /* dxfer_len */bsize, + /* timeout */5000); +#endif + return 0; +} + +/* + * Retrieve the SMART RETURN STATUS + * + * SMART RETURN STATUS provides the reliability status of the + * device and can be used as a high-level indication of health. + */ +static int32_t +__device_status_ata(smart_h h, union ccb *ccb) +{ + struct fbsd_smart *fsmart = h; + uint8_t *buf = NULL; + uint32_t page = 0; + uint8_t lba_high = 0, lba_mid = 0, device = 0, status = 0; + + if (fsmart->common.info.tunneled) { + struct ata_res_pass16 { + u_int16_t reserved[5]; + u_int8_t flags; + u_int8_t error; + u_int8_t sector_count_exp; + u_int8_t sector_count; + u_int8_t lba_low_exp; + u_int8_t lba_low; + u_int8_t lba_mid_exp; + u_int8_t lba_mid; + u_int8_t lba_high_exp; + u_int8_t lba_high; + u_int8_t device; + u_int8_t status; + } *res_pass16 = (struct ata_res_pass16 *)(uintptr_t) + &ccb->csio.sense_data; + + buf = ccb->csio.data_ptr; + page = ((struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes)->features; + lba_high = res_pass16->lba_high; + lba_mid = res_pass16->lba_mid; + device = res_pass16->device; + status = res_pass16->status; + + /* + * Note that this generates an expected CHECK CONDITION. + * Mask it so the outer function doesn't print an error + * message. + */ + ccb->ccb_h.status &= ~CAM_STATUS_MASK; + ccb->ccb_h.status |= CAM_REQ_CMP; + } else { + struct ccb_ataio *ataio = (struct ccb_ataio *)&ccb->ataio; + + buf = ataio->data_ptr; + page = ataio->cmd.features; + lba_high = ataio->res.lba_high; + lba_mid = ataio->res.lba_mid; + device = ataio->res.device; + status = ataio->res.status; + } + + switch (page) { + case PAGE_ID_ATA_SMART_RET_STATUS: + /* + * Typically, SMART related log pages return data, but this + * command is different in that the data is encoded in the + * result registers. + * + * Handle this in a UNIX-like way by writing a 0 (no errors) + * or 1 (threshold exceeded condition) to the output buffer. + */ + dprintf("SMART_RET_STATUS: lba mid=%#x high=%#x device=%#x status=%#x\n", + lba_mid, + lba_high, + device, + status); + if ((lba_high == 0x2c) && (lba_mid == 0xf4)) { + buf[0] = 1; + } else if ((lba_high == 0xc2) && (lba_mid == 0x4f)) { + buf[0] = 0; + } else { + /* Ruh-roh ... */ + buf[0] = 255; + } + break; + default: + ; + } + + return 0; +} + +int32_t +device_read_log(smart_h h, uint32_t page, void *buf, size_t bsize) +{ + struct fbsd_smart *fsmart = h; + union ccb *ccb = NULL; + int rc = 0; + + if (fsmart == NULL) + return EINVAL; + + dprintf("read log page %#x\n", page); + + ccb = cam_getccb(fsmart->camdev); + if (ccb == NULL) + return ENOMEM; + + CCB_CLEAR_ALL_EXCEPT_HDR(ccb); + + switch (fsmart->common.protocol) { + case SMART_PROTO_ATA: + rc = __device_read_ata(h, page, buf, bsize, ccb); + break; + case SMART_PROTO_SCSI: + rc = __device_read_scsi(h, page, buf, bsize, ccb); + break; + case SMART_PROTO_NVME: + rc = __device_read_nvme(h, page, buf, bsize, ccb); + break; + default: + warnx("unsupported protocol %d", fsmart->common.protocol); + cam_freeccb(ccb); + return ENODEV; + } + + if (rc) { + if (rc == EINVAL) + warnx("unsupported page %#x", page); + + return rc; + } + + if (((rc = cam_send_ccb(fsmart->camdev, ccb)) < 0) + || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { + if (rc < 0) + warn("error sending command"); + } + + /* + * Most commands don't need any post-processing. But then there's + * ATA. It's why we can't have nice things :( + */ + switch (fsmart->common.protocol) { + case SMART_PROTO_ATA: + __device_status_ata(h, ccb); + break; + default: + ; + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + cam_error_print(fsmart->camdev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + } + + cam_freeccb(ccb); + + return 0; +} + +/* + * The SCSI / ATA Translation (SAT) requires devices to support the ATA + * Information VPD Page (T10/2126-D Revision 04). Use the existence of + * this page to identify tunneled devices. + */ +static bool +__device_proto_tunneled(struct fbsd_smart *fsmart) +{ + union ccb *ccb = NULL; + struct scsi_vpd_supported_page_list supportedp; + uint32_t i; + bool is_tunneled = false; + + if (fsmart->common.protocol != SMART_PROTO_SCSI) { + return false; + } + + ccb = cam_getccb(fsmart->camdev); + if (!ccb) { + warn("Allocation failure ccb=%p", ccb); + goto __device_proto_tunneled_out; + } + + scsi_inquiry(&ccb->csio, + 3, // retries + NULL, // callback function + MSG_SIMPLE_Q_TAG, // tag action + (uint8_t *)&supportedp, + sizeof(struct scsi_vpd_supported_page_list), + 1, // EVPD + SVPD_SUPPORTED_PAGE_LIST, // page code + SSD_FULL_SIZE, // sense length + 5000); // timeout + + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if ((cam_send_ccb(fsmart->camdev, ccb) >= 0) && + ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) { + dprintf("Looking for page %#x (total = %u):\n", SVPD_ATA_INFORMATION, + supportedp.length); + for (i = 0; i < supportedp.length; i++) { + dprintf("\t[%u] = %#x\n", i, supportedp.list[i]); + if (supportedp.list[i] == SVPD_ATA_INFORMATION) { + is_tunneled = true; + break; + } + } + } + + cam_freeccb(ccb); + +__device_proto_tunneled_out: + return is_tunneled; +} + +/** + * Retrieve the device protocol type via the transport settings + * + * @return protocol type or SMART_PROTO_MAX on error + */ +static smart_protocol_e +__device_get_proto(struct fbsd_smart *fsmart) +{ + smart_protocol_e proto = SMART_PROTO_MAX; + union ccb *ccb; + + if (!fsmart || !fsmart->camdev) { + warn("Bad handle %p", fsmart); + return proto; + } + + ccb = cam_getccb(fsmart->camdev); + if (ccb != NULL) { + CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->cts); + + ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS; + ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS; + + if (cam_send_ccb(fsmart->camdev, ccb) >= 0) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + struct ccb_trans_settings *cts = &ccb->cts; + + switch (cts->protocol) { + case PROTO_ATA: + proto = SMART_PROTO_ATA; + break; + case PROTO_SCSI: + proto = SMART_PROTO_SCSI; + break; + case PROTO_NVME: + proto = SMART_PROTO_NVME; + break; + default: + printf("%s: unknown protocol %d\n", + __func__, + cts->protocol); + } + } + } + + cam_freeccb(ccb); + } + + return proto; +} + +static int32_t +__device_info_ata(struct fbsd_smart *fsmart, struct ccb_getdev *cgd) +{ + smart_info_t *sinfo = NULL; + + if (!fsmart || !cgd) { + return -1; + } + + sinfo = &fsmart->common.info; + + sinfo->supported = cgd->ident_data.support.command1 & + ATA_SUPPORT_SMART; + + dprintf("ATA command1 = %#x\n", cgd->ident_data.support.command1); + + cam_strvis((uint8_t *)sinfo->device, cgd->ident_data.model, + sizeof(cgd->ident_data.model), + sizeof(sinfo->device)); + cam_strvis((uint8_t *)sinfo->rev, cgd->ident_data.revision, + sizeof(cgd->ident_data.revision), + sizeof(sinfo->rev)); + cam_strvis((uint8_t *)sinfo->serial, cgd->ident_data.serial, + sizeof(cgd->ident_data.serial), + sizeof(sinfo->serial)); + + return 0; +} + +static int32_t +__device_info_scsi(struct fbsd_smart *fsmart, struct ccb_getdev *cgd) +{ + smart_info_t *sinfo = NULL; + union ccb *ccb = NULL; + struct scsi_vpd_unit_serial_number *snum = NULL; + struct scsi_log_informational_exceptions ie = {0}; + + if (!fsmart || !cgd) { + return -1; + } + + sinfo = &fsmart->common.info; + + cam_strvis((uint8_t *)sinfo->vendor, (uint8_t *)cgd->inq_data.vendor, + sizeof(cgd->inq_data.vendor), + sizeof(sinfo->vendor)); + cam_strvis((uint8_t *)sinfo->device, (uint8_t *)cgd->inq_data.product, + sizeof(cgd->inq_data.product), + sizeof(sinfo->device)); + cam_strvis((uint8_t *)sinfo->rev, (uint8_t *)cgd->inq_data.revision, + sizeof(cgd->inq_data.revision), + sizeof(sinfo->rev)); + + ccb = cam_getccb(fsmart->camdev); + snum = malloc(sizeof(struct scsi_vpd_unit_serial_number)); + if (!ccb || !snum) { + warn("Allocation failure ccb=%p snum=%p", ccb, snum); + goto __device_info_scsi_out; + } + + /* Get the serial number */ + CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio); + + scsi_inquiry(&ccb->csio, + 3, // retries + NULL, // callback function + MSG_SIMPLE_Q_TAG, // tag action + (uint8_t *)snum, + sizeof(struct scsi_vpd_unit_serial_number), + 1, // EVPD + SVPD_UNIT_SERIAL_NUMBER, // page code + SSD_FULL_SIZE, // sense length + 5000); // timeout + + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if ((cam_send_ccb(fsmart->camdev, ccb) >= 0) && + ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) { + cam_strvis((uint8_t *)sinfo->serial, snum->serial_num, + snum->length, + sizeof(sinfo->serial)); + sinfo->serial[sizeof(sinfo->serial) - 1] = '\0'; + } + + memset(ccb, 0, sizeof(*ccb)); + + scsi_log_sense(&ccb->csio, + /* retries */1, + /* cbfcnp */NULL, + /* tag_action */0, + /* page_code */SLS_PAGE_CTRL_CUMULATIVE, + /* page */SLS_IE_PAGE, + /* save_pages */0, + /* ppc */0, + /* paramptr */0, + /* param_buf */(uint8_t *)&ie, + /* param_len */sizeof(ie), + /* sense_len */0, + /* timeout */5000); + + /* + * Note: The existance of the Informational Exceptions (IE) log page + * appears to be the litmus test for SMART support in SCSI + * devices. Confusingly, smartctl will report SMART health + * status as 'OK' if the device doesn't support the IE page. + * For now, just report the facts. + */ + if ((cam_send_ccb(fsmart->camdev, ccb) >= 0) && + ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) { + if ((ie.hdr.param_len < 4) || ie.ie_asc || ie.ie_ascq) { + printf("Log Sense, Informational Exceptions failed " + "(length=%u asc=%#x ascq=%#x)\n", + ie.hdr.param_len, ie.ie_asc, ie.ie_ascq); + } else { + sinfo->supported = true; + } + } + +__device_info_scsi_out: + free(snum); + if (ccb) + cam_freeccb(ccb); + + return 0; +} + +static int32_t +__device_info_nvme(struct fbsd_smart *fsmart, struct ccb_getdev *cgd) +{ + union ccb *ccb; + smart_info_t *sinfo = NULL; + struct nvme_controller_data cd; + + if (!fsmart || !cgd) { + return -1; + } + + sinfo = &fsmart->common.info; + + sinfo->supported = true; + + ccb = cam_getccb(fsmart->camdev); + if (ccb != NULL) { + struct ccb_dev_advinfo *cdai = &ccb->cdai; + + CCB_CLEAR_ALL_EXCEPT_HDR(cdai); + + cdai->ccb_h.func_code = XPT_DEV_ADVINFO; + cdai->ccb_h.flags = CAM_DIR_IN; + cdai->flags = CDAI_FLAG_NONE; +#ifdef CDAI_TYPE_NVME_CNTRL + cdai->buftype = CDAI_TYPE_NVME_CNTRL; +#else + cdai->buftype = 6; +#endif + cdai->bufsiz = sizeof(struct nvme_controller_data); + cdai->buf = (uint8_t *)&cd; + + if (cam_send_ccb(fsmart->camdev, ccb) >= 0) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + cam_strvis((uint8_t *)sinfo->device, cd.mn, + sizeof(cd.mn), + sizeof(sinfo->device)); + cam_strvis((uint8_t *)sinfo->rev, cd.fr, + sizeof(cd.fr), + sizeof(sinfo->rev)); + cam_strvis((uint8_t *)sinfo->serial, cd.sn, + sizeof(cd.sn), + sizeof(sinfo->serial)); + } + } + + cam_freeccb(ccb); + } + + return 0; +} + +static int32_t +__device_info_tunneled_ata(struct fbsd_smart *fsmart) +{ + struct ata_params ident_data; + union ccb *ccb = NULL; + struct ata_pass_16 *ata_pass_16; + struct ata_cmd ata_cmd; + int32_t rc = -1; + + ccb = cam_getccb(fsmart->camdev); + if (ccb == NULL) { + goto __device_info_tunneled_ata_out; + } + + memset(&ident_data, 0, sizeof(struct ata_params)); + + CCB_CLEAR_ALL_EXCEPT_HDR(ccb); + + scsi_ata_pass_16(&ccb->csio, + /*retries*/ 1, + /*cbfcnp*/ NULL, + /*flags*/ CAM_DIR_IN, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*protocol*/ AP_PROTO_PIO_IN, + /*ata_flags*/ AP_FLAG_TLEN_SECT_CNT | + AP_FLAG_BYT_BLOK_BLOCKS | + AP_FLAG_TDIR_FROM_DEV, + /*features*/ 0, + /*sector_count*/sizeof(struct ata_params), + /*lba*/ 0, + /*command*/ ATA_ATA_IDENTIFY, + /*control*/ 0, + /*data_ptr*/ (uint8_t *)&ident_data, + /*dxfer_len*/ sizeof(struct ata_params), + /*sense_len*/ SSD_FULL_SIZE, + /*timeout*/ 5000 + ); + + ata_pass_16 = (struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes; + ata_cmd.command = ata_pass_16->command; + ata_cmd.control = ata_pass_16->control; + ata_cmd.features = ata_pass_16->features; + + rc = cam_send_ccb(fsmart->camdev, ccb); + if (rc != 0) { + warnx("%s: scsi_ata_pass_16() failed (programmer error?)", + __func__); + goto __device_info_tunneled_ata_out; + } + + fsmart->common.info.supported = ident_data.support.command1 & + ATA_SUPPORT_SMART; + + dprintf("ATA command1 = %#x\n", ident_data.support.command1); + +__device_info_tunneled_ata_out: + if (ccb) { + cam_freeccb(ccb); + } + + return rc; +} + +/** + * Retrieve the device information and use to populate the info structure + */ +static int32_t +__device_get_info(struct fbsd_smart *fsmart) +{ + union ccb *ccb; + int32_t rc = -1; + + if (!fsmart || !fsmart->camdev) { + warn("Bad handle %p", fsmart); + return -1; + } + + ccb = cam_getccb(fsmart->camdev); + if (ccb != NULL) { + struct ccb_getdev *cgd = &ccb->cgd; + + CCB_CLEAR_ALL_EXCEPT_HDR(cgd); + + /* + * GDEV_TYPE doesn't support NVMe. What we do get is: + * - device (ata/model, scsi/product) + * - revision (ata, scsi) + * - serial (ata) + * - vendor (scsi) + * - supported (ata) + * + * Serial # for all proto via ccb_dev_advinfo (buftype CDAI_TYPE_SERIAL_NUM) + */ + ccb->ccb_h.func_code = XPT_GDEV_TYPE; + + if (cam_send_ccb(fsmart->camdev, ccb) >= 0) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + switch (cgd->protocol) { + case PROTO_ATA: + rc = __device_info_ata(fsmart, cgd); + break; + case PROTO_SCSI: + rc = __device_info_scsi(fsmart, cgd); + if (!rc && fsmart->common.protocol == SMART_PROTO_ATA) { + rc = __device_info_tunneled_ata(fsmart); + } + break; + case PROTO_NVME: + rc = __device_info_nvme(fsmart, cgd); + break; + default: + printf("%s: unsupported protocol %d\n", + __func__, cgd->protocol); + } + } + } + + cam_freeccb(ccb); + } + + return rc; +} diff --git a/contrib/smart/libsmart.c b/contrib/smart/libsmart.c new file mode 100644 index 000000000000..a1732de09ed9 --- /dev/null +++ b/contrib/smart/libsmart.c @@ -0,0 +1,1359 @@ +/* + * Copyright (c) 2016-2026 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <assert.h> +#include <err.h> +#include <string.h> +#include <sys/endian.h> + +#ifdef LIBXO +#include <libxo/xo.h> +#endif + +#include "libsmart.h" +#include "libsmart_priv.h" +#include "libsmart_dev.h" + +/* Default page lists */ + +static smart_page_list_t pg_list_ata = { + .pg_count = 2, + .pages = { + { .id = PAGE_ID_ATA_SMART_READ_DATA, .bytes = 512 }, + { .id = PAGE_ID_ATA_SMART_RET_STATUS, .bytes = 4 } + } +}; + +#define PAGE_ID_NVME_SMART_HEALTH 0x02 + +static smart_page_list_t pg_list_nvme = { + .pg_count = 1, + .pages = { + { .id = PAGE_ID_NVME_SMART_HEALTH, .bytes = 512 } + } +}; + +static smart_page_list_t pg_list_scsi = { + .pg_count = 8, + .pages = { + { .id = PAGE_ID_SCSI_WRITE_ERR, .bytes = 128 }, + { .id = PAGE_ID_SCSI_READ_ERR, .bytes = 128 }, + { .id = PAGE_ID_SCSI_VERIFY_ERR, .bytes = 128 }, + { .id = PAGE_ID_SCSI_NON_MEDIUM_ERR, .bytes = 128 }, + { .id = PAGE_ID_SCSI_LAST_N_ERR, .bytes = 128 }, + { .id = PAGE_ID_SCSI_TEMPERATURE, .bytes = 64 }, + { .id = PAGE_ID_SCSI_START_STOP_CYCLE, .bytes = 128 }, + { .id = PAGE_ID_SCSI_INFO_EXCEPTION, .bytes = 64 }, + } +}; + +static uint32_t __smart_attribute_max(smart_buf_t *sb); +static uint32_t __smart_buffer_size(smart_h h); +static smart_map_t *__smart_map(smart_h h, smart_buf_t *sb); +static smart_page_list_t *__smart_page_list(smart_h h); +static int32_t __smart_read_pages(smart_h h, smart_buf_t *sb); + +static const char * +smart_proto_str(smart_protocol_e p) +{ + + switch (p) { + case SMART_PROTO_AUTO: + return "auto"; + case SMART_PROTO_ATA: + return "ATA"; + case SMART_PROTO_SCSI: + return "SCSI"; + case SMART_PROTO_NVME: + return "NVME"; + default: + return "Unknown"; + } +} + +smart_h +smart_open(smart_protocol_e protocol, char *devname) +{ + smart_t *s; + + s = device_open(protocol, devname); + + if (s) { + dprintf("protocol %s (specified %s%s)\n", + smart_proto_str(s->protocol), + smart_proto_str(protocol), + s->info.tunneled ? ", tunneled ATA" : ""); + } + + return s; +} + +void +smart_close(smart_h h) +{ + + device_close(h); +} + +bool +smart_supported(smart_h h) +{ + smart_t *s = h; + bool supported = false; + + if (s) { + supported = s->info.supported; + dprintf("SMART is %ssupported\n", supported ? "" : "not "); + } + + return supported; +} + +smart_map_t * +smart_read(smart_h h) +{ + smart_t *s = h; + smart_buf_t *sb = NULL; + smart_map_t *sm = NULL; + + sb = calloc(1, sizeof(smart_buf_t)); + if (sb) { + sb->protocol = s->protocol; + + /* + * Need the page list to calculate the buffer size. If one + * isn't specified, get the default based on the protocol. + */ + if (s->pg_list == NULL) { + s->pg_list = __smart_page_list(s); + if (!s->pg_list) { + goto smart_read_out; + } + } + + sb->b = NULL; + sb->bsize = __smart_buffer_size(s); + + if (sb->bsize != 0) { + sb->b = malloc(sb->bsize); + } + + if (sb->b == NULL) { + goto smart_read_out; + } + + if (__smart_read_pages(s, sb) < 0) { + goto smart_read_out; + } + + sb->attr_count = __smart_attribute_max(sb); + + sm = __smart_map(h, sb); + if (!sm) { + free(sb->b); + free(sb); + sb = NULL; + } + } + +smart_read_out: + if (!sm) { + if (sb) { + if (sb->b) { + free(sb->b); + } + + free(sb); + } + } + + return sm; +} + +void +smart_free(smart_map_t *sm) +{ + smart_buf_t *sb = NULL; + uint32_t i; + + if (sm == NULL) + return; + + sb = sm->sb; + + if (sb) { + if (sb->b) { + free(sb->b); + sb->b = NULL; + } + + free(sb); + } + + for (i = 0; i < sm->count; i++) { + smart_map_t *tm = sm->attr[i].thresh; + + if (tm) { + free(tm); + } + + if (sm->attr[i].flags & SMART_ATTR_F_ALLOC) { + free((void *)(uintptr_t)sm->attr[i].description); + } + } + + free(sm); +} + +/* + * Format specifier for the various output types + * Provides versions to use with libxo and without + * TODO some of this is ATA specific + */ +#ifndef LIBXO +# define __smart_print_val(fmt, ...) printf(fmt, ##__VA_ARGS__) +# define VEND_STR "Vendor\t%s\n" +# define DEV_STR "Device\t%s\n" +# define REV_STR "Revision\t%s\n" +# define SERIAL_STR "Serial\t%s\n" +# define PAGE_HEX "%#01.1x\t" +# define PAGE_DEC "%d\t" +# define ID_HEX "%#01.1x\t" +# define ID_DEC "%d\t" +# define RAW_STR "%s" +# define RAW_HEX "%#01.1x" +# define RAW_DEC "%d" +/* Long integer version of the format macro */ +# define RAW_LHEX "%#01.1" PRIx64 +# define RAW_LDEC "%" PRId64 +# define THRESH_HEX "\t%#02.2x\t%#01.1x\t%#01.1x" +# define THRESH_DEC "\t%d\t%d\t%d" +# define DESC_STR "%s" +#else +# define __smart_print_val(fmt, ...) xo_emit(fmt, ##__VA_ARGS__) +# define VEND_STR "{L:Vendor}{P:\t}{:vendor/%s}\n" +# define DEV_STR "{L:Device}{P:\t}{:device/%s}\n" +# define REV_STR "{L:Revision}{P:\t}{:rev/%s}\n" +# define SERIAL_STR "{L:Serial}{P:\t}{:serial/%s}\n" +# define PAGE_HEX "{k:page/%#01.1x}{P:\t}" +# define PAGE_DEC "{k:page/%d}{P:\t}" +# define ID_HEX "{k:id/%#01.1x}{P:\t}" +# define ID_DEC "{k:id/%d}{P:\t}" +# define RAW_STR "{k:raw/%s}" +# define RAW_HEX "{k:raw/%#01.1x}" +# define RAW_DEC "{k:raw/%d}" +/* Long integer version of the format macro */ +# define RAW_LHEX "{k:raw/%#01.1" PRIx64 "}" +# define RAW_LDEC "{k:raw/%" PRId64 "}" +# define THRESH_HEX "{P:\t}{k:flags/%#02.2x}{P:\t}{k:nominal/%#01.1x}{P:\t}{k:worst/%#01.1x}" +# define THRESH_DEC "{P:\t}{k:flags/%d}{P:\t}{k:nominal/%d}{P:\t}{k:worst/%d}" +# define DESC_STR "{:description}{P:\t}" +#endif + +#define THRESH_COUNT 3 + + +/* Convert an 128-bit unsigned integer to a string */ +static char * +__smart_u128_str(smart_attr_t *sa) +{ + /* Max size is log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322 */ +#define MAX_LEN (128 / 3 + 1 + 1) + static char s[MAX_LEN]; + char *p = s + MAX_LEN - 1; + uint32_t *a = (uint32_t *)sa->raw; + uint64_t r, d; + + *p-- = '\0'; + + do { + r = a[3]; + + d = r / 10; + r = ((r - d * 10) << 32) + a[2]; + a[3] = d; + + d = r / 10; + r = ((r - d * 10) << 32) + a[1]; + a[2] = d; + + d = r / 10; + r = ((r - d * 10) << 32) + a[0]; + a[1] = d; + + d = r / 10; + r = r - d * 10; + a[0] = d; + + *p-- = '0' + r; + } while (a[0] || a[1] || a[2] || a[3]); + + p++; + + while ((*p == '0') && (p < &s[sizeof(s) - 2])) + p++; + + return p; +} + +static void +__smart_print_thresh(smart_map_t *tm, uint32_t flags) +{ + bool do_hex = false; + + if (!tm) { + return; + } + + if (flags & SMART_OPEN_F_HEX) + do_hex = true; + + __smart_print_val(do_hex ? THRESH_HEX : THRESH_DEC, + *((uint16_t *)tm->attr[0].raw), + *((uint8_t *)tm->attr[1].raw), + *((uint8_t *)tm->attr[2].raw)); +} + +/* Does the attribute match one requested by the caller? */ +static bool +__smart_attr_match(smart_matches_t *match, smart_attr_t *attr) +{ + uint32_t i; + + assert((match != NULL) && (attr != NULL)); + + for (i = 0; i < match->count; i++) { + if ((match->m[i].page != -1) && ((uint32_t)match->m[i].page != attr->page)) + continue; + + if ((uint32_t)match->m[i].id == attr->id) + return true; + } + + return false; +} + +void +smart_print(__attribute__((unused)) smart_h h, smart_map_t *sm, smart_matches_t *which, uint32_t flags) +{ + uint32_t i; + bool do_hex = false, do_descr = false; + uint32_t bytes = 0; + + if (!sm) { + return; + } + + if (flags & SMART_OPEN_F_HEX) + do_hex = true; + if (flags & SMART_OPEN_F_DESCR) + do_descr = true; + +#ifdef LIBXO + xo_open_container("attributes"); + xo_open_list("attribute"); +#endif + for (i = 0; i < sm->count; i++) { + /* If we're printing a specific attribute, is this it? */ + if ((which != NULL) && !__smart_attr_match(which, &sm->attr[i])) { + continue; + } + +#ifdef LIBXO + xo_open_instance("attribute"); +#endif + /* Print the page / attribute ID if selecting all attributes */ + if (which == NULL) { + if (do_descr && (sm->attr[i].description != NULL)) + __smart_print_val(DESC_STR, sm->attr[i].description); + else { + __smart_print_val(do_hex ? PAGE_HEX : PAGE_DEC, sm->attr[i].page); + __smart_print_val(do_hex ? ID_HEX : ID_DEC, sm->attr[i].id); + } + } + + bytes = sm->attr[i].bytes; + + /* Print the attribute based on its size */ + if (sm->attr[i].flags & SMART_ATTR_F_STR) { + __smart_print_val(RAW_STR, (char *)sm->attr[i].raw); + } else if (bytes > 8) { + if (do_hex) + ; + else + __smart_print_val(RAW_STR, + __smart_u128_str(&sm->attr[i])); + + } else if (bytes > 4) { + uint64_t v64 = 0; + uint64_t mask = UINT64_MAX; + + memcpy(&v64, sm->attr[i].raw, bytes); + + if (sm->attr[i].flags & SMART_ATTR_F_BE) { + v64 = be64toh(v64); + } else { + v64 = le64toh(v64); + } + + mask >>= 8 * (sizeof(uint64_t) - bytes); + + v64 &= mask; + + __smart_print_val(do_hex ? RAW_LHEX : RAW_LDEC, v64); + + } else if (bytes > 2) { + uint32_t v32 = 0; + uint32_t mask = UINT32_MAX; + + memcpy(&v32, sm->attr[i].raw, bytes); + + if (sm->attr[i].flags & SMART_ATTR_F_BE) { + v32 = be32toh(v32); + } else { + v32 = le32toh(v32); + } + + mask >>= 8 * (sizeof(uint32_t) - bytes); + + v32 &= mask; + + __smart_print_val(do_hex ? RAW_HEX : RAW_DEC, v32); + + } else if (bytes > 1) { + uint16_t v16 = 0; + uint16_t mask = UINT16_MAX; + + memcpy(&v16, sm->attr[i].raw, bytes); + + if (sm->attr[i].flags & SMART_ATTR_F_BE) { + v16 = be16toh(v16); + } else { + v16 = le16toh(v16); + } + + mask >>= 8 * (sizeof(uint16_t) - bytes); + + v16 &= mask; + + __smart_print_val(do_hex ? RAW_HEX : RAW_DEC, v16); + + } else if (bytes > 0) { + uint8_t v8 = *((uint8_t *)sm->attr[i].raw); + + __smart_print_val(do_hex ? RAW_HEX : RAW_DEC, v8); + } + + if ((flags & SMART_OPEN_F_THRESH) && sm->attr[i].thresh) { + xo_open_container("threshold"); + __smart_print_thresh(sm->attr[i].thresh, flags); + xo_close_container("threshold"); + } + + __smart_print_val("\n"); + +#ifdef LIBXO + xo_close_instance("attribute"); +#endif + } +#ifdef LIBXO + xo_close_list("attribute"); + xo_close_container("attributes"); +#endif +} + +void +smart_print_device_info(smart_h h) +{ + smart_t *s = h; + + if (!s) { + return; + } + + if (*s->info.vendor != '\0') + __smart_print_val(VEND_STR, s->info.vendor); + if (*s->info.device != '\0') + __smart_print_val(DEV_STR, s->info.device); + if (*s->info.rev != '\0') + __smart_print_val(REV_STR, s->info.device); + if (*s->info.serial != '\0') + __smart_print_val(SERIAL_STR, s->info.serial); +} + +static uint32_t +__smart_attr_max_ata(smart_buf_t *sb) +{ + uint32_t max = 0; + + if (sb) { + max = 30; + } + + return max; +} + +static uint32_t +__smart_attr_max_nvme(smart_buf_t *sb) +{ + uint32_t max = 0; + + if (sb) { + max = 512; + } + + return max; +} + +static uint32_t +__smart_attr_max_scsi(smart_buf_t *sb) +{ + uint32_t max = 0; + + if (sb) { + max = 512; + } + + return max; +} + +static uint32_t +__smart_attribute_max(smart_buf_t *sb) +{ + uint32_t count = 0; + + if (sb != NULL) { + switch (sb->protocol) { + case SMART_PROTO_ATA: + count = __smart_attr_max_ata(sb); + break; + case SMART_PROTO_NVME: + count = __smart_attr_max_nvme(sb); + break; + case SMART_PROTO_SCSI: + count = __smart_attr_max_scsi(sb); + break; + default: + ; + } + } + + return count; +} + +/** + * Return the total buffer size needed by the protocol's page list + */ +static uint32_t +__smart_buffer_size(smart_h h) +{ + smart_t *s = h; + uint32_t size = 0; + + if ((s != NULL) && (s->pg_list != NULL)) { + smart_page_list_t *plist = s->pg_list; + uint32_t p = 0; + + for (p = 0; p < plist->pg_count; p++) { + size += plist->pages[p].bytes; + } + } + + return size; +} + +/* + * Map SMART READ DATA threshold attributes + * + * Read the 3 consecutive values (flags, nominal, and worst) + */ +static smart_map_t * +__smart_map_ata_thresh(uint8_t *b) +{ + smart_map_t *sm = NULL; + + sm = malloc(sizeof(smart_map_t) + (THRESH_COUNT * sizeof(smart_attr_t))); + if (sm) { + uint32_t i; + + sm->count = THRESH_COUNT; + + sm->attr[0].page = 0; + sm->attr[0].id = 0; + sm->attr[0].bytes = 2; + sm->attr[0].flags = 0; + sm->attr[0].raw = b; + sm->attr[0].thresh = NULL; + + b +=2; + + for (i = 1; i < sm->count; i++) { + sm->attr[i].page = 0; + sm->attr[i].id = i; + sm->attr[i].bytes = 1; + sm->attr[i].flags = 0; + sm->attr[i].raw = b; + sm->attr[i].thresh = NULL; + + b ++; + } + } + + return sm; +} + +/* + * Map SMART READ DATA attributes + * + * The format for the READ DATA buffer is: + * 2 bytes Revision + * 360 bytes Attributes (12 bytes each) + * + * Each attribute consists of: + * 1 byte ID + * 2 byte Status Flags + * 1 byte Nominal value + * 1 byte Worst value + * 7 byte Raw value + * Note that many attributes do not use the entire 7 bytes of the raw value. + */ +static void +__smart_map_ata_read_data(smart_map_t *sm, void *buf, size_t bsize) +{ + uint8_t *b = NULL; + uint8_t *b_end = NULL; + uint32_t max_attr = 0; + uint32_t a; + + max_attr = __smart_attr_max_ata(sm->sb); + a = sm->count; + + b = buf; + + /* skip revision */ + b += 2; + + b_end = b + (max_attr * 12); + if (b_end > (b + bsize)) { + sm->count = 0; + return; + } + + while (b < b_end) { + if (*b != 0) { + if ((a - sm->count) >= max_attr) { + warnx("More attributes (%d) than fit in map", + a - sm->count); + break; + } + + sm->attr[a].page = PAGE_ID_ATA_SMART_READ_DATA; + sm->attr[a].id = b[0]; + sm->attr[a].description = __smart_ata_desc( + PAGE_ID_ATA_SMART_READ_DATA, sm->attr[a].id); + sm->attr[a].bytes = 7; + sm->attr[a].flags = 0; + sm->attr[a].raw = b + 5; + sm->attr[a].thresh = __smart_map_ata_thresh(b + 1); + + a++; + } + + b += 12; + } + + sm->count = a; +} + +static void +__smart_map_ata_return_status(smart_map_t *sm, void *buf) +{ + uint8_t *b = NULL; + uint32_t a; + + a = sm->count; + + b = buf; + + sm->attr[a].page = PAGE_ID_ATA_SMART_RET_STATUS; + sm->attr[a].id = 0; + sm->attr[a].description = __smart_ata_desc(PAGE_ID_ATA_SMART_RET_STATUS, + sm->attr[a].id); + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = b; + sm->attr[a].thresh = NULL; + + a++; + + sm->count = a; +} + +static void +__smart_map_ata(smart_h h, smart_buf_t *sb, smart_map_t *sm) +{ + smart_t *s = h; + smart_page_list_t *pg_list = NULL; + uint8_t *b = NULL; + uint32_t p; + + pg_list = s->pg_list; + b = sb->b; + + sm->count = 0; + + for (p = 0; p < pg_list->pg_count; p++) { + switch (pg_list->pages[p].id) { + case PAGE_ID_ATA_SMART_READ_DATA: + __smart_map_ata_read_data(sm, b, pg_list->pages[p].bytes); + break; + case PAGE_ID_ATA_SMART_RET_STATUS: + __smart_map_ata_return_status(sm, b); + break; + } + + b += pg_list->pages[p].bytes; + } +} + +#ifndef ARRAYLEN +#define ARRAYLEN(p) sizeof(p)/sizeof(p[0]) +#endif + +#define NVME_VS(mjr,mnr,ter) (((mjr) << 16) | ((mnr) << 8) | (ter)) +#define NVME_VS_1_0 NVME_VS(1,0,0) +#define NVME_VS_1_1 NVME_VS(1,1,0) +#define NVME_VS_1_2 NVME_VS(1,2,0) +#define NVME_VS_1_2_1 NVME_VS(1,2,1) +#define NVME_VS_1_3 NVME_VS(1,3,0) +#define NVME_VS_1_4 NVME_VS(1,4,0) +static struct { + uint32_t off; /* buffer offset */ + uint32_t bytes; /* size in bytes */ + uint32_t ver; /* first version available */ + const char *description; +} __smart_nvme_values[] = { + { 0, 1, NVME_VS_1_0, "Critical Warning" }, + { 1, 2, NVME_VS_1_0, "Composite Temperature" }, + { 3, 1, NVME_VS_1_0, "Available Spare" }, + { 4, 1, NVME_VS_1_0, "Available Spare Threshold" }, + { 5, 1, NVME_VS_1_0, "Percentage Used" }, + { 6, 1, NVME_VS_1_4, "Endurance Group Critical Warning Summary" }, + { 32, 16, NVME_VS_1_0, "Data Units Read" }, + { 48, 16, NVME_VS_1_0, "Data Units Written" }, + { 64, 16, NVME_VS_1_0, "Host Read Commands" }, + { 80, 16, NVME_VS_1_0, "Host Write Commands" }, + { 96, 16, NVME_VS_1_0, "Controller Busy Time" }, + { 112, 16, NVME_VS_1_0, "Power Cycles" }, + { 128, 16, NVME_VS_1_0, "Power On Hours" }, + { 144, 16, NVME_VS_1_0, "Unsafe Shutdowns" }, + { 160, 16, NVME_VS_1_0, "Media and Data Integrity Errors" }, + { 176, 16, NVME_VS_1_0, "Number of Error Information Log Entries" }, + { 192, 4, NVME_VS_1_2, "Warning Composite Temperature Time" }, + { 196, 4, NVME_VS_1_2, "Critical Composite Temperature Time" }, + { 200, 2, NVME_VS_1_2, "Temperature Sensor 1" }, + { 202, 2, NVME_VS_1_2, "Temperature Sensor 2" }, + { 204, 2, NVME_VS_1_2, "Temperature Sensor 3" }, + { 206, 2, NVME_VS_1_2, "Temperature Sensor 4" }, + { 208, 2, NVME_VS_1_2, "Temperature Sensor 5" }, + { 210, 2, NVME_VS_1_2, "Temperature Sensor 6" }, + { 212, 2, NVME_VS_1_2, "Temperature Sensor 7" }, + { 214, 2, NVME_VS_1_2, "Temperature Sensor 8" }, + { 216, 4, NVME_VS_1_3, "Thermal Management Temperature 1 Transition Count" }, + { 220, 4, NVME_VS_1_3, "Thermal Management Temperature 2 Transition Count" }, + { 224, 4, NVME_VS_1_3, "Total Time For Thermal Management Temperature 1" }, + { 228, 4, NVME_VS_1_3, "Total Time For Thermal Management Temperature 2" }, +}; + +/** + * NVMe doesn't define attribute IDs like ATA does, but we can + * approximate this behavior by treating the byte offset as the + * attribute ID. + */ +static void +__smart_map_nvme(smart_buf_t *sb, smart_map_t *sm) +{ + uint8_t *b = NULL; + uint32_t vs = NVME_VS_1_0; // XXX assume device is 1.0 + uint32_t i, a; + + sm->count = 0; + b = sb->b; + + for (i = 0, a = 0; i < ARRAYLEN(__smart_nvme_values); i++) { + if (vs >= __smart_nvme_values[i].ver) { + sm->attr[a].page = 0x2; + sm->attr[a].id = __smart_nvme_values[i].off; + sm->attr[a].description = __smart_nvme_values[i].description; + sm->attr[a].bytes = __smart_nvme_values[i].bytes; + sm->attr[a].flags = 0; + sm->attr[a].raw = b + __smart_nvme_values[i].off; + sm->attr[a].thresh = NULL; + + a++; + } + } + + sm->count = a; +} + +/* + * Create a SMART map for SCSI error counter pages + * + * Several SCSI log pages have a similar format for the error counter log + * pages + */ +static void +__smart_map_scsi_err_page(smart_map_t *sm, void *b) +{ + struct scsi_err_page { + uint8_t page_code; + uint8_t subpage_code; + uint16_t page_length; + uint8_t param[]; + } __attribute__((packed)) *err = b; + struct scsi_err_counter_param { + uint16_t code; + uint8_t format:2, + tmc:2, + etc:1, + tsd:1, + :1, + du:1; + uint8_t length; + uint8_t counter[]; + } __attribute__((packed)) *param = NULL; + uint32_t a, p, page_length; + const char *cmd = NULL, *desc = NULL; + + switch (err->page_code) { + case PAGE_ID_SCSI_WRITE_ERR: + cmd = "Write"; + break; + case PAGE_ID_SCSI_READ_ERR: + cmd = "Read"; + break; + case PAGE_ID_SCSI_VERIFY_ERR: + cmd = "Verify"; + break; + case PAGE_ID_SCSI_NON_MEDIUM_ERR: + cmd = "Non-Medium"; + break; + default: + fprintf(stderr, "Unknown command %#x\n", err->page_code); + cmd = "Unknown"; + break; + } + + a = sm->count; + + p = 0; + page_length = be16toh(err->page_length); + + while (p < page_length) { + param = (struct scsi_err_counter_param *) (err->param + p); + + sm->attr[a].page = err->page_code; + sm->attr[a].id = be16toh(param->code); + desc = __smart_scsi_err_desc(sm->attr[a].id); + if (desc != NULL) { + size_t bytes; + char *str; + + bytes = snprintf(NULL, 0, "%s %s", cmd, desc); + str = malloc(bytes + 1); + if (str != NULL) { + snprintf(str, bytes + 1, "%s %s", cmd, desc); + sm->attr[a].description = str; + sm->attr[a].flags |= SMART_ATTR_F_ALLOC; + } + } + sm->attr[a].bytes = param->length; + sm->attr[a].flags = SMART_ATTR_F_BE; + sm->attr[a].raw = param->counter; + sm->attr[a].thresh = NULL; + + p += 4 + param->length; + + a++; + } + + sm->count = a; +} + +static void +__smart_map_scsi_last_err(smart_map_t *sm, void *b) +{ + struct scsi_last_n_error_event_page { + uint8_t page_code:6, + spf:1, + ds:1; + uint8_t subpage_code; + uint16_t page_length; + uint8_t event[]; + } __attribute__((packed)) *lastn = b; + struct scsi_last_n_error_event { + uint16_t code; + uint8_t format:2, + tmc:2, + etc:1, + tsd:1, + :1, + du:1; + uint8_t length; + uint8_t data[]; + } __attribute__((packed)) *event = NULL; + uint32_t a, p, page_length; + + a = sm->count; + + p = 0; + page_length = be16toh(lastn->page_length); + + while (p < page_length) { + event = (struct scsi_last_n_error_event *) (lastn->event + p); + + sm->attr[a].page = lastn->page_code; + sm->attr[a].id = be16toh(event->code); + sm->attr[a].bytes = event->length; + sm->attr[a].flags = SMART_ATTR_F_BE; + sm->attr[a].raw = event->data; + sm->attr[a].thresh = NULL; + + p += 4 + event->length; + + a++; + } + + sm->count = a; +} + +static void +__smart_map_scsi_temp(smart_map_t *sm, void *b) +{ + struct scsi_temperature_log_page { + uint8_t page_code; + uint8_t subpage_code; + uint16_t page_length; + struct scsi_temperature_log_entry { + uint16_t code; + uint8_t control; + uint8_t length; + uint8_t rsvd; + uint8_t temperature; + } param[]; + } __attribute__((packed)) *temp = b; + uint32_t a, p, count; + + count = be16toh(temp->page_length) / sizeof(struct scsi_temperature_log_entry); + + a = sm->count; + + for (p = 0; p < count; p++) { + uint16_t code = be16toh(temp->param[p].code); + switch (code) { + case 0: + case 1: + sm->attr[a].page = temp->page_code; + sm->attr[a].id = be16toh(temp->param[p].code); + sm->attr[a].description = code == 0 ? "Temperature" : "Reference Temperature"; + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = &(temp->param[p].temperature); + sm->attr[a].thresh = NULL; + a++; + break; + default: + break; + } + } + + sm->count = a; +} + +static void +__smart_map_scsi_start_stop(smart_map_t *sm, void *b) +{ + struct scsi_start_stop_page { + uint8_t page_code; +#define START_STOP_CODE_DATE_MFG 0x0001 +#define START_STOP_CODE_DATE_ACCTN 0x0002 +#define START_STOP_CODE_CYCLES_LIFE 0x0003 +#define START_STOP_CODE_CYCLES_ACCUM 0x0004 +#define START_STOP_CODE_LOAD_LIFE 0x0005 +#define START_STOP_CODE_LOAD_ACCUM 0x0006 + uint8_t subpage_code; + uint16_t page_length; + uint8_t param[]; + } __attribute__((packed)) *sstop = b; + struct scsi_start_stop_param { + uint16_t code; + uint8_t format:2, + tmc:2, + etc:1, + tsd:1, + :1, + du:1; + uint8_t length; + uint8_t data[]; + } __attribute__((packed)) *param; + uint32_t a, p, page_length; + + a = sm->count; + + p = 0; + page_length = be16toh(sstop->page_length); + + while (p < page_length) { + param = (struct scsi_start_stop_param *) (sstop->param + p); + + sm->attr[a].page = sstop->page_code; + sm->attr[a].id = be16toh(param->code); + sm->attr[a].bytes = param->length; + + switch (sm->attr[a].id) { + case START_STOP_CODE_DATE_MFG: + sm->attr[a].description = "Date of Manufacture"; + sm->attr[a].flags = SMART_ATTR_F_STR; + break; + case START_STOP_CODE_DATE_ACCTN: + sm->attr[a].description = "Accounting Date"; + sm->attr[a].flags = SMART_ATTR_F_STR; + break; + case START_STOP_CODE_CYCLES_LIFE: + sm->attr[a].description = "Specified Cycle Count Over Device Lifetime"; + sm->attr[a].flags = SMART_ATTR_F_BE; + break; + case START_STOP_CODE_CYCLES_ACCUM: + sm->attr[a].description = "Accumulated Start-Stop Cycles"; + sm->attr[a].flags = SMART_ATTR_F_BE; + break; + case START_STOP_CODE_LOAD_LIFE: + sm->attr[a].description = "Specified Load-Unload Count Over Device Lifetime"; + sm->attr[a].flags = SMART_ATTR_F_BE; + break; + case START_STOP_CODE_LOAD_ACCUM: + sm->attr[a].description = "Accumulated Load-Unload Cycles"; + sm->attr[a].flags = SMART_ATTR_F_BE; + break; + } + + sm->attr[a].raw = param->data; + sm->attr[a].thresh = NULL; + + p += 4 + param->length; + + a++; + } + + sm->count = a; +} + +static void +__smart_map_scsi_info_exception(smart_map_t *sm, void *b) +{ + struct scsi_info_exception_log_page { + uint8_t page_code; + uint8_t subpage_code; + uint16_t page_length; + uint8_t param[]; + } __attribute__((packed)) *ie = b; + struct scsi_ie_param { + uint16_t code; + uint8_t control; + uint8_t length; + uint8_t asc; /* IE Additional Sense Code */ + uint8_t ascq; /* IE Additional Sense Code Qualifier */ + uint8_t temp_recent; + uint8_t temp_trip_point; + uint8_t temp_max; + } __attribute__((packed)) *param; + uint32_t a, p, page_length; + + a = sm->count; + + p = 0; + page_length = be16toh(ie->page_length); + + while (p < page_length) { + param = (struct scsi_ie_param *)(ie->param + p); + + p += 4 + param->length; + + sm->attr[a].page = ie->page_code; + sm->attr[a].id = offsetof(struct scsi_ie_param, asc); + sm->attr[a].description = "Informational Exception ASC"; + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = ¶m->asc; + sm->attr[a].thresh = NULL; + a++; + + sm->attr[a].page = ie->page_code; + sm->attr[a].id = offsetof(struct scsi_ie_param, ascq); + sm->attr[a].description = "Informational Exception ASCQ"; + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = ¶m->ascq; + sm->attr[a].thresh = NULL; + a++; + + sm->attr[a].page = ie->page_code; + sm->attr[a].id = offsetof(struct scsi_ie_param, temp_recent); + sm->attr[a].description = "Informational Exception Most recent temperature"; + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = ¶m->temp_recent; + sm->attr[a].thresh = NULL; + a++; + + sm->attr[a].page = ie->page_code; + sm->attr[a].id = offsetof(struct scsi_ie_param, temp_trip_point); + sm->attr[a].description = "Informational Exception Vendor HDA temperature trip point"; + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = ¶m->temp_trip_point; + sm->attr[a].thresh = NULL; + a++; + + sm->attr[a].page = ie->page_code; + sm->attr[a].id = offsetof(struct scsi_ie_param, temp_max); + sm->attr[a].description = "Informational Exception Maximum temperature"; + sm->attr[a].bytes = 1; + sm->attr[a].flags = 0; + sm->attr[a].raw = ¶m->temp_max; + sm->attr[a].thresh = NULL; + a++; + } + + sm->count = a; +} + +/* + * Create a map based on the page list + */ +static void +__smart_map_scsi(smart_h h, smart_buf_t *sb, smart_map_t *sm) +{ + smart_t *s = h; + smart_page_list_t *pg_list = NULL; + uint8_t *b = NULL; + uint32_t p; + + pg_list = s->pg_list; + b = sb->b; + + sm->count = 0; + + for (p = 0; p < pg_list->pg_count; p++) { + switch (pg_list->pages[p].id) { + case PAGE_ID_SCSI_WRITE_ERR: + case PAGE_ID_SCSI_READ_ERR: + case PAGE_ID_SCSI_VERIFY_ERR: + case PAGE_ID_SCSI_NON_MEDIUM_ERR: + __smart_map_scsi_err_page(sm, b); + break; + case PAGE_ID_SCSI_LAST_N_ERR: + __smart_map_scsi_last_err(sm, b); + break; + case PAGE_ID_SCSI_TEMPERATURE: + __smart_map_scsi_temp(sm, b); + break; + case PAGE_ID_SCSI_START_STOP_CYCLE: + __smart_map_scsi_start_stop(sm, b); + break; + case PAGE_ID_SCSI_INFO_EXCEPTION: + __smart_map_scsi_info_exception(sm, b); + break; + } + + b += pg_list->pages[p].bytes; + } +} + +/** + * Create a map of SMART values + */ +static void +__smart_attribute_map(smart_h h, smart_buf_t *sb, smart_map_t *sm) +{ + + if (!sb || !sm) { + return; + } + + switch (sb->protocol) { + case SMART_PROTO_ATA: + __smart_map_ata(h, sb, sm); + break; + case SMART_PROTO_NVME: + __smart_map_nvme(sb, sm); + break; + case SMART_PROTO_SCSI: + __smart_map_scsi(h, sb, sm); + break; + default: + sm->count = 0; + } +} + +static smart_map_t * +__smart_map(smart_h h, smart_buf_t *sb) +{ + smart_map_t *sm = NULL; + uint32_t max = 0; + + max = sb->attr_count; + if (max == 0) { + warnx("Attribute count is zero?!?"); + return NULL; + } + + sm = malloc(sizeof(smart_map_t) + (max * sizeof(smart_attr_t))); + if (sm) { + memset(sm, 0, sizeof(smart_map_t) + (max * sizeof(smart_attr_t))); + sm->sb = sb; + + /* count starts as the max but is adjusted to reflect the actual number */ + sm->count = max; + + __smart_attribute_map(h, sb, sm); + } + + return sm; +} + +typedef struct { + uint8_t page_code; + uint8_t subpage_code; + uint16_t page_length; + uint8_t supported_pages[]; +} __attribute__((packed)) scsi_supported_log_pages; + +static smart_page_list_t * +__smart_page_list_scsi(smart_t *s) +{ + smart_page_list_t *pg_list = NULL; + scsi_supported_log_pages *b = NULL; + uint32_t bsize = 68; /* 4 byte header + 63 entries + 1 just cuz */ + int32_t rc; + + b = malloc(bsize); + if (!b) { + return NULL; + } + + /* Supported Pages page ID is 0 */ + rc = device_read_log(s, PAGE_ID_SCSI_SUPPORTED_PAGES, (uint8_t *)b, + bsize); + if (rc < 0) { + fprintf(stderr, "Read Supported Log Pages failed\n"); + } else { + uint8_t *supported_page = b->supported_pages; + uint32_t n_supported = be16toh(b->page_length); + uint32_t pg, p, pmax = pg_list_scsi.pg_count; + + /* Build a page list using only pages the device supports */ + pg_list = malloc(sizeof(pg_list_scsi)); + if (pg_list == NULL) { + n_supported = 0; + } else { + pg_list->pg_count = 0; + } + + /* + * Loop through all supported pages looking for those related + * to SMART. The below assumes the supported page list from the + * device and in pg_lsit_scsi are sorted in increasing order. + */ + dprintf("Supported SCSI pages:\n"); + for (pg = 0, p = 0; (pg < n_supported) && (p < pmax); pg++) { + dprintf("\t[%u] = %#x\n", pg, supported_page[pg]); + while ((supported_page[pg] > pg_list_scsi.pages[p].id) && + (p < pmax)) { + p++; + } + + if (supported_page[pg] == pg_list_scsi.pages[p].id) { + pg_list->pages[pg_list->pg_count] = pg_list_scsi.pages[p]; + pg_list->pg_count++; + p++; + } + } + } + + free(b); + + return pg_list; +} + +static smart_page_list_t * +__smart_page_list(smart_h h) +{ + smart_t *s = h; + smart_page_list_t *pg_list = NULL; + + if (!s) { + return NULL; + } + + switch (s->protocol) { + case SMART_PROTO_ATA: + pg_list = &pg_list_ata; + break; + case SMART_PROTO_NVME: + pg_list = &pg_list_nvme; + break; + case SMART_PROTO_SCSI: + pg_list = __smart_page_list_scsi(s); + break; + default: + pg_list = NULL; + } + + return pg_list; +} + +static int32_t +__smart_read_pages(smart_h h, smart_buf_t *sb) +{ + smart_t *s = h; + smart_page_list_t *plist = NULL; + uint8_t *buf = NULL; + int32_t rc = 0; + uint32_t p = 0; + + plist = s->pg_list; + + buf = sb->b; + + for (p = 0; p < s->pg_list->pg_count; p++) { + memset(buf, 0, plist->pages[p].bytes); + rc = device_read_log(h, plist->pages[p].id, buf, plist->pages[p].bytes); + if (rc) { + dprintf("bad read (%d) from page %#x (bytes=%lu)\n", rc, + plist->pages[p].id, plist->pages[p].bytes); + break; + } + + buf += plist->pages[p].bytes; + } + + return rc; +} diff --git a/contrib/smart/libsmart.h b/contrib/smart/libsmart.h new file mode 100644 index 000000000000..60346f3b3a70 --- /dev/null +++ b/contrib/smart/libsmart.h @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016-2021 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _LIBSMART_H +#define _LIBSMART_H + +#include <inttypes.h> +#include <stdbool.h> + +/* + * libsmart uses a common model for SMART data (a.k.a. "attributes") across + * storage protocols. Each health value consists of: + * - The identifier of the log page containing this attribute + * - The attribute's identifier + * - A description of the attribute + * - A pointer to the raw data + * - The attribute's size in bytes + * + * This model most closely resembles SCSI's native representation, but it + * can represent ATA and NVMe with the following substitutions: + * - ATA : use the Command Feature field value for the log page ID + * - NVMe : use the field's starting byte offset for the attribute ID + * + * libsmart returns a "map" to the SMART/health data read from a device + * in the smart_map_t structure. The map consists of: + * - A variable-length array of attributes + * - The length of the array + * - The raw data read from the device + * + * Consumers of the map will typically iterate through the array of attributes + * to print or otherwise process the health data. + */ + +/* + * A smart handle is an opaque reference to the device + */ +typedef void * smart_h; + +typedef enum { + SMART_PROTO_AUTO, + SMART_PROTO_ATA, + SMART_PROTO_SCSI, + SMART_PROTO_NVME, + SMART_PROTO_MAX +} smart_protocol_e; + +/* + * A smart buffer contains the raw data returned from the protocol-specific + * health command. + */ +typedef struct { + smart_protocol_e protocol; + void *b; // buffer of raw data + size_t bsize; // buffer size + uint32_t attr_count; // number of SMART attributes +} smart_buf_t; + +struct smart_map_s; + +/* + * A smart attribute is an individual health data element + */ +typedef struct smart_attr_s { + uint32_t page; + uint32_t id; + const char *description; /* human readable description */ + uint32_t bytes; + uint32_t flags; +#define SMART_ATTR_F_BE 0x00000001 /* Attribute is big-endian */ +#define SMART_ATTR_F_STR 0x00000002 /* Attribute is a string */ +#define SMART_ATTR_F_ALLOC 0x00000004 /* Attribute description dynamically allocated */ + void *raw; + struct smart_map_s *thresh; /* Threshold values (if any) */ +} smart_attr_t; + +/* + * A smart map is the collection of health data elements from the device + */ +typedef struct smart_map_s { + smart_buf_t *sb; + uint32_t count; /* Number of attributes */ + smart_attr_t attr[]; /* Array of attributes */ +} smart_map_t; + +#define SMART_OPEN_F_HEX 0x1 /* Print values in hexadecimal */ +#define SMART_OPEN_F_THRESH 0x2 /* Print threshold values */ +#define SMART_OPEN_F_DESCR 0x4 /* Print textual description */ + +/* SMART attribute to match */ +typedef struct smart_match_s { + int32_t page; + int32_t id; +} smart_match_t; + +/* List of SMART attribute(s) to match */ +typedef struct smart_matches_s { + uint32_t count; + smart_match_t m[]; +} smart_matches_t; + +/** + * Connect to a device to read SMART data + * + * @param p The desired protocol or "auto" to automatically detect it + * @param devname The device name to open + * + * @return An opaque handle or NULL on failure + */ +smart_h smart_open(smart_protocol_e p, char *devname); +/** + * Close device connection + * + * @param handle The handle returned from smart_open() + * + * @return None + */ +void smart_close(smart_h); +/** + * Does the device support SMART/health data? + * + * @param handle The handle returned from smart_open() + * + * @return true / false + */ +bool smart_supported(smart_h); +/** + * Read SMART/health data from the device + * + * @param handle The handle returned from smart_open() + * + * @return a pointer to the SMART map or NULL on failure + */ +smart_map_t *smart_read(smart_h); +/** + * Free memory associated with the health data read from the device + * + * @param map Pointer returned from smart_read() + * + * @return None + */ +void smart_free(smart_map_t *); +/** + * Print health data matching the desired attributes + * + * @param handle The handle returned from smart_open() + * @param map Pointer returned from smart_read() + * @param which Pointer to attributes to match or NULL to match all + * @param flags Control display of attributes (hexadecimal, description, ... + * + * @return None + */ +void smart_print(smart_h, smart_map_t *, smart_matches_t *, uint32_t); +/** + * Print high-level device information + * + * @param handle The handle returned from smart_open() + * + * @return None + */ +void smart_print_device_info(smart_h); + +#endif diff --git a/contrib/smart/libsmart_desc.c b/contrib/smart/libsmart_desc.c new file mode 100644 index 000000000000..bcfb31d66922 --- /dev/null +++ b/contrib/smart/libsmart_desc.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2021 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <stddef.h> + +#include "libsmart.h" +#include "libsmart_priv.h" + +/* Strings from "SMART Attribute Descriptions" (SAD) */ +static const char * +desc_ata_data[] = { + [1] = "Read Error Rate", + [2] = "Throughput Performance", + [3] = "Spin-Up Time", + [4] = "Start/Stop Count", + [5] = "Reallocated Sectors Count", + [6] = "Read Channel Margin", + [7] = "Seek Error Rate", + [8] = "Seek Time Performance", + [9] = "Power-On Hours", + [10] = "Spin Retry Count", + [11] = "Calibration Retry Count", + [12] = "Power Cycle Count", + [13] = "Soft Read Error Rate", + [22] = "Current Helium Level", /* HGST */ + [170] = "Available Reserved Space", /* Intel */ + [171] = "SSD Program Fail", /* Kingston? */ + [172] = "SSD Erase Fail Count", /* Kingston? */ + [173] = "SSD Wear Leveling Count", /* HPE SSD Endurance Limit */ + [174] = "Unexpected Power Loss Count", /* Intel */ + [175] = "Power Loss Protection Failure", /* Intel */ + [176] = "Erase Fail Count (chip)", + [177] = "Wear Range Delta", + [179] = "Used Reserved Block Count Total", +/* [180] = HPE, Seagate, Intel differences */ + [181] = "Non-4K Aligned Access Count", /* Micron. Conflict Kingston */ + [182] = "Erase Fail Count", + [183] = "Runtime Bad Block", + [184] = "End-to-End Error", + [185] = "Head Stability", /* WD */ + [186] = "Induced Op-Vibration Detection", /* WD */ + [187] = "Reported Uncorrectable Errors", + [188] = "Command Timeout", + [189] = "High Fly Writes", + [190] = "Airflow Temperature", /* WDC, HPE conflict */ + [191] = "G-Sense Error Rate", + [192] = "Power-Off Count", /* HPE, Seagate */ + [193] = "Load/Unload Cycle Count", + [194] = "Temperature Celsius", + [195] = "Hardware ECC Recovered", + [196] = "Reallocation Event Count", + [197] = "Current Pending Sector Count", + [198] = "Uncorrectable Sector Count", /* Fujitsu */ + [199] = "UltraDMA CRC Error Count", + [200] = "Write Error Rate", + [201] = "Soft Read Error Rate", + [202] = "Data Address Mark Errors", + [203] = "Run Out Cancel", + [204] = "Soft ECC Correction", + [205] = "Thermal Asperity Rate", + [206] = "Flying Height", + [207] = "Spin High Current", + [208] = "Spin Buzz", + [209] = "Offline Seek Performnce", + [210] = "Vibration, During Write", /* Maxtor */ + [211] = "Vibration During Write", /* Acronis */ + [212] = "Shock During Write", /* Acronis */ + [220] = "Disk Shift", + [221] = "G-Sense Error Rate", + [222] = "Loaded Hours", + [223] = "Load/Unload Retry Count", + [224] = "Load Friction", + [225] = "Load/Unload Cycle Count", + [226] = "Load-in Time", + [227] = "Torque Amplification Count", + [228] = "Power-off Retract Cycle", + [230] = "GMR Head Amplitude Drive Life Protection Status", + [231] = "Temperature SSD Life Left", /* Kingston */ + [232] = "Endurance Remaining", /* Multiple conflict */ + [233] = "Power-On Hours", /* Multiple conflict */ + [234] = "Average Erase Count", /* Multiple conflict */ + [235] = "Good Block Count", /* Multiple conflict */ + [240] = "Head Flying Hours", + [241] = "Total LBAs Written", + [242] = "Total LBAs Read", + [243] = "Total LBAs Written Expanded", /* Multiple conflict */ + [244] = "Total LBAs Read Expanded", /* Multiple conflict */ + [250] = "Read Error Rate", + [251] = "Minimum Spares Remaining", + [252] = "Newly Added Bad Flash Block", + [254] = "Free Fall Protection" +}; + +const char * +__smart_ata_desc(uint32_t page, uint32_t id) +{ + const char *desc = NULL; + + switch (page) { + case PAGE_ID_ATA_SMART_READ_DATA: + if (desc_ata_data[id] != NULL) + desc = desc_ata_data[id]; + break; + case PAGE_ID_ATA_SMART_RET_STATUS: + desc = "SMART Status"; + break; + default: + ; + } + + return (desc); +} + +const char * +__smart_scsi_err_desc(uint32_t id) +{ + const char *param = NULL; + + switch (id) { + case 0: + param = "Errors corrected without substantial delay"; + break; + case 1: + param = "Errors corrected with possible delays"; + break; + case 2: + param = "Total retries"; + break; + case 3: + param = "Total errors corrected"; + break; + case 4: + param = "Total times correction algorithm processed"; + break; + case 5: + param = "Total bytes processed"; + break; + case 6: + param = "Total uncorrected errors"; + break; + default: + return (NULL); + } + + return (param); +} diff --git a/contrib/smart/libsmart_dev.h b/contrib/smart/libsmart_dev.h new file mode 100644 index 000000000000..bbf028e73712 --- /dev/null +++ b/contrib/smart/libsmart_dev.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017-2021 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _LIBSMART_DEV_H +#define _LIBSMART_DEV_H + +/** + * Open a device to gather SMART information + * + * The call performs OS specific functions necessary to prepare the device + * to receive read log requests. + * + * Although opaque to the user, the handle must be a pointer to a structure + * with the first member being struct smart_s. The remaining members are OS + * specific and are not used by the library. + * + * @param protocol The desired protocol or "auto" to automatically detect it + * @param devname The device name to open + * + * @return An opaque handle to the device or NULL on failure + */ +extern smart_h device_open(smart_protocol_e, char *); + +/** + * Close a device and release the associated resources + * + * @param handle The handle returned from device_open() + * + * @return None + */ +extern void device_close(smart_h); + +/** + * Read the log page + * + * This call reads the specified log page in the protocol specific manner + * needed by the device. The results are placed in the provided buffer. + * + * @param h SMART handle returned from device_open() + * @param page The log page ID + * @param buf Pointer to buffer containing the results of the read + * @param bsize Size of the buffer in bytes + * + * @return Zero on success, errno on failure + */ +extern int32_t device_read_log(smart_h, uint32_t, void *, size_t); + +#endif /* !_LIBSMART_DEV_H */ diff --git a/contrib/smart/libsmart_priv.h b/contrib/smart/libsmart_priv.h new file mode 100644 index 000000000000..f29fc1e1e033 --- /dev/null +++ b/contrib/smart/libsmart_priv.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016-2021 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef _LIBSMART_PRIV_H +#define _LIBSMART_PRIV_H + +/* OS-independent structures and definitions internal to libsmart */ + +#define PAGE_ID_ATA_SMART_READ_DATA 0xd0 /* SMART Read Data */ +#define PAGE_ID_ATA_SMART_RET_STATUS 0xda /* SMART Return Status */ + +#define PAGE_ID_SCSI_SUPPORTED_PAGES 0x00 +#define PAGE_ID_SCSI_WRITE_ERR 0x02 /* Write Error counter */ +#define PAGE_ID_SCSI_READ_ERR 0x03 /* Read Error counter */ +#define PAGE_ID_SCSI_VERIFY_ERR 0x05 /* Verify Error counter */ +#define PAGE_ID_SCSI_NON_MEDIUM_ERR 0x06 /* Non-Medium Error */ +#define PAGE_ID_SCSI_LAST_N_ERR 0x07 /* Last n Error events */ +#define PAGE_ID_SCSI_TEMPERATURE 0x0d /* Temperature */ +#define PAGE_ID_SCSI_START_STOP_CYCLE 0x0e /* Start-Stop Cycle counter */ +#define PAGE_ID_SCSI_INFO_EXCEPTION 0x2f /* Informational Exceptions */ + +extern bool do_debug; + +#define dprintf(f, ...) if (do_debug) fprintf(stderr, "dbg: " f, ## __VA_ARGS__) + +/* General information about the device */ +typedef struct smart_info_s { + /* device supports SMART */ + uint32_t supported:1, + /* storage protocol is tunneled (e.g. ATA inside SCSI) */ + tunneled:1, + :30; + /* + * Device-provided information, including + * - vendor name + * - device / model + * - firmware revision + * - serial number + * Protocols may provide a subset of this information + */ + char vendor[16], device[48], rev[16], serial[32]; +} smart_info_t; + +/* List of pages providing SMART/health data */ +typedef struct smart_page_list_s { + uint32_t pg_count; + struct { + uint32_t id; + size_t bytes; + } pages[]; +} smart_page_list_t; + +/* + * The device handle (i.e. smart_h) is an opaque pointer to memory containing + * device/OS independent and dependent data. The library uses type punning to + * isolate the OS-independent portion (struct smart_s) from the OS-dependent + * details. Because of this, the device layer allocates and frees this memory. + */ +typedef struct smart_s { + smart_protocol_e protocol; + smart_info_t info; + smart_page_list_t *pg_list; + /* Device / OS specific follows this structure */ +} smart_t; + +/* Return a textual description of the ATA attribute */ +const char * __smart_ata_desc(uint32_t page, uint32_t id); +/* Return a textual description of the SCSI error attribute */ +const char * __smart_scsi_err_desc(uint32_t id); + +#endif diff --git a/contrib/smart/smart.8 b/contrib/smart/smart.8 new file mode 100644 index 000000000000..3215ff890001 --- /dev/null +++ b/contrib/smart/smart.8 @@ -0,0 +1,245 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.\" Copyright (c) 2021-2026 Chuck Tuffli +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" Note: The date here should be updated whenever a non-trivial +.\" change is made to the manual page. +.Dd February 14, 2026 +.Dt SMART 8 +.Os +.Sh NAME +.Nm smart , +.Nm diskhealth +.Nd monitor disk health from a storage device via SMART +.Sh SYNOPSIS +.Nm +.Op Fl dhitvx +.Oo Fl a Ar page:attribute Ns Oo , Ns Ar page:attribute Oc Ns ... Oc +.Op Fl Fl debug +.Ar device +.Nm diskhealth +.Op Fl Dhitvx +.Oo Fl a Ar page:attribute Ns Oo , Ns Ar page:attribute Oc Ns ... Oc +.Op Fl Fl debug +.Ar device +.Sh DESCRIPTION +The +.Nm +command allows the user to monitor the various information reported +by Self-Monitoring, Analysis and Reporting Technology (SMART) present +on most ATA, SCSI, and NVMe storage media. +Because the structure of this information varies across protocols, +.Nm +normalizes entries using the format: +.Bd -literal + <Page ID> <Attribute ID> <Value> <Thresholds> +.Ed +.Pp +Fields are tab-delimited by default, but the command can output +data in any format supported by +.Xr libxo 3 . +.Pp +Because ATA does not have log pages, +.Nm +uses the Command Feature field value in place of the log page ID. +For SMART READ DATA, this value is 208 / 0xd0. +Note that devices choose which attribute ID values they support, their +descriptions, and the format of the data. +The three values displayed with the +.Fl Fl threshold +option are: +.Pp +.Bl -dash -compact -offset indent +.It +Status flags (byte offset 1, 2 bytes) +.It +Nominal attribute value (byte offset 3, 1 byte) +.It +Worst Ever attribute value (byte offset 4, 1 byte) +.El +.Pp +Additionally, +.Nm +reports the value of the SMART STATUS command (Command Feature field +value 218 / 0xda). +As this command does not return any data, +the command represents this entry with a synthetic attribute +ID of 0, and it uses the command status (0 or 1) as the attribute +value. +.Pp +NVMe devices support the SMART/Health log page (Page ID 2 / 0x2). +The data returned in this log page is not structured as attribute IDs. +Instead, +.Nm +uses the byte offset of each field as the attribute ID. +For example, +byte 3 is the Available Spare. +Thus, for NVMe, attribute ID 3 is +Available Spare. +Note that NVMe health data does not include threshold +values, and as a result, the command will ignore the +.Fl Fl threshold +option. +.Pp +SCSI devices can support a number of log pages which report drive +health. +The command will report the following pages: +.Pp +.Bl -dash -compact -offset indent +.It +Write Errors (Page ID 2 / 0x2) +.It +Read Errors (Page ID 3 / 0x3) +.It +Verify Errors (Page ID 5 / 0x5) +.It +Non-medium Errors (Page ID 6 / 0x6) +.It +Last N Errors (Page ID 7 / 0x7) +.It +Temperature (Page ID 13 / 0xd) +.It +Start-stop Cycles (Page ID 14 / 0xe) +.It +Informational Exceptions (Page ID 47 / 0x2f) +.El +.Pp +Note that all log pages are optional, and a particular drive +may not support all these pages. +For SCSI devices, the Attribute ID +maps to the SCSI parameter code defined by the command. +Parameter +codes are integer values from 0 to N, and, by themselves, are ambiguous +outside the context of a particular log page. +Note that SCSI health data +does not include threshold values, and as a result, the command will +ignore the +.Fl Fl threshold +option. +.Pp +The following options are available: +.Bl -tag -width "-d argument" +.It Fl a Ar page:attribute , Fl Fl attribute= Ns Ar page:attribute +A comma-separated list of attributes to display. +If page is missing, display the matching attribute from any page. +.It Fl d , Fl Fl decode +Decode the attribute ID values. +This is the default when invoked as +.Nm diskhealth . +.It Fl D , Fl Fl no-decode +Do not decode the attribute ID values. +This is the default when invoked as +.Nm . +.It Fl h , Fl Fl help +Prints a usage message and exits. +.It Fl i , Fl Fl info +Print general device information. +.It Fl t , Fl Fl threshold +Also print the threshold values. +.It Fl v , Fl Fl version +Print the version and copyright. +.It Fl x , Fl Fl hex +Print the values in hexadecimal. +.It Ar device +An explicit device path +.Pq Pa /dev/ada0 +or GEOM provider +.Pq Pa ada0 +. +.El +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Print all SMART READ DATA and SMART STATUS including the +threshold values for ATA drive ada0. +.Pp +.Dl # smart -t ada0 +.Pp +Print only attribute ID 5 ("Reallocated Sectors Count") for +ATA drive ada0. +.Pp +.Dl # smart -a 5 ada0 +.Pp +Print attribute IDs 5 ("Reallocated Sectors Count") and 171 +("SSD Program Fail") for ATA drive ada0. +.Pp +.Dl # smart -a 5,171 ada0 +.Pp +Print all health pages supported by SCSI device da0 including: +.Bl -dash -compact -offset indent +.It +Write Errors +.It +Read Errors +.It +Verify Errors +.It +Non-medium Errors +.It +Last N Errors +.It +Temperature +.It +Start-stop Cycles +.It +Informational Exceptions +.El +.Pp +.Dl # smart da0 +.Pp +Print all Health log page entries in hexadecimal for NVMe +device nda0. +.Pp +.Dl # smart -x nda0 +.Sh DIAGNOSTICS +The command may fail for one of the following reasons: +.Bl -diag +.It "No output displayed" +The device does not support health data. +.It "CAMGETPASSTHRU ioctl failed" +.Nm +relies on +.Xr cam 4 +to retrieve data from devices and will display this message if the +device does not have a passthrough driver. +This can happen, for +example, if the system uses the +.Xr nvd 4 +NVMe driver instead of the +.Xr nda 4 +driver. +.El +.Sh SEE ALSO +.Xr libxo 3 , +.Xr cam 4 , +.Xr nda 4 , +.Xr nvd 4 +.Sh AUTHORS +This +utility was written by +.An Chuck Tuffli Aq Mt chuck@FreeBSD.org . +.Sh BUGS +Probably. diff --git a/contrib/smart/smart.c b/contrib/smart/smart.c new file mode 100644 index 000000000000..8b543fea1819 --- /dev/null +++ b/contrib/smart/smart.c @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2016-2026 Chuck Tuffli <chuck@tuffli.net> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <stdbool.h> +#include <string.h> +#ifdef LIBXO +#include <libxo/xo.h> +#endif + +#include "libsmart.h" + +#define SMART_NAME "smart" +#define SMART_VERSION "1.0.2" + +extern bool do_debug; + +static const char *pn; +bool do_debug = false; +static int debugset = 0; + +static struct option opts[] = { + { "help", no_argument, NULL, 'h' }, + { "threshold", no_argument, NULL, 't' }, + { "hex", no_argument, NULL, 'x' }, + { "attribute", required_argument, NULL, 'a' }, + { "info", no_argument, NULL, 'i' }, + { "version", no_argument, NULL, 'v' }, + { "decode", no_argument, NULL, 'd' }, + { "no-decode", no_argument, NULL, 'D' }, + { "debug", no_argument, &debugset, 1 }, + { NULL, 0, NULL, 0 } +}; + +static void +usage(const char *name) +{ + printf("Usage: %s [-htxi] [-a attribute[,attribute]...] <device name>\n", name); + printf("\t-h, --help\n"); + printf("\t-t, --threshold : also print out the threshold values\n"); + printf("\t-x, --hex : print the values out in hexadecimal\n"); + printf("\t-a, --attribute : print specified attribute(s)\n"); + printf("\t-i, --info : print general device information\n"); + printf("\t-d, --decode: decode the attribute IDs\n"); + printf("\t-D, --no-decode: don't decode the attribute IDs\n"); + printf("\t-v, --version : print the version and copyright\n"); + printf("\t --debug : output diagnostic information\n"); +} + +/* + * Convert string to an integer + * + * Returns -1 on error, converted value otherwise + */ +static int32_t +get_val(char *attr, char **next) +{ + char *sep = NULL; + long val; + + *next = NULL; + + val = strtol(attr, &sep, 0); + if ((val == 0) && (errno != 0)) { + printf("Error parsing attribute %s", attr); + switch (errno) { + case EINVAL: + printf(" (not a number?)\n"); + break; + case ERANGE: + printf(" (value out of range)\n"); + break; + default: + printf("\n"); + } + return -1; + } + + if (val > INT32_MAX) { + printf("Attribute value %ld too big\n", val); + return -1; + } + + *next = sep; + return ((int32_t)val); +} + +/* + * Create a match specification from the given attribute + * + * Attribute format is + * <Page ID>:<Attribute ID> + * where page and attribute IDs are integers. If the page ID is missing, + * match the specified attribute ID on any page (i.e. -1). Valid forms are + * <int>:<int> + * :<int> + * <int> + * + * Returns 0 on success + */ +static int +add_match(smart_matches_t **matches, char *attr) +{ + char *next; + int32_t page = -1, id; + uint32_t count = 0; + + id = get_val(attr, &next); + if (id < 0) + return id; + + if (*next == ':') { + page = id; + id = get_val(next + 1, &next); + if (id < 0) + return id; + } + + if (*matches == NULL) { + *matches = calloc(1, sizeof(smart_matches_t) + sizeof(smart_match_t)); + if (*matches == NULL) + return ENOMEM; + } else { + void *tmp; + + count = (*matches)->count; + tmp = realloc(*matches, sizeof(smart_matches_t) + ((count + 1) * sizeof(smart_match_t))); + if (tmp == NULL) + return ENOMEM; + *matches = tmp; + } + + (*matches)->m[count].page = page; + (*matches)->m[count].id = id; + (*matches)->count++; + return 0; +} + +/* + * Parse the comma separated list of attributes to match + * + * Caller frees memory allocated for the smart_matches_t pointer. + * + * Returns 0 on success + */ +static int +parse_matches(smart_matches_t **matches, char *attr) +{ + int res; + + if (attr[0] == '\0') + return -1; + + while (*attr != '\0') { + char *next; + size_t len; + + if ((next = strchr(attr, ',')) == NULL) { + len = strlen(attr); + next = attr + len; + } else { + len = next - attr; + next++; + } + + if (len == 0) { + printf("Malformed attribute %s\n", attr); + return -1; + } + + res = add_match(matches, attr); + if (res) + return res; + + attr = next; + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + smart_h h; + smart_map_t *sm = NULL; + char *devname = NULL; + int ch; + bool do_thresh = false, do_hex = false, do_info = false, do_version = false, + do_descr; + smart_matches_t *matches = NULL; + int rc = EXIT_SUCCESS; + + /* + * By default, keep the original behavior (output numbers only) if + * invoked as smart. Otherwise, default to printing the human-friendly + * text descriptions. + */ + pn = getprogname(); + if (strcmp(pn, SMART_NAME) == 0) + do_descr = false; + else + do_descr = true; + +#ifdef LIBXO + argc = xo_parse_args(argc, argv); +#endif + + while ((ch = getopt_long(argc, argv, "htxa:idDv", opts, NULL)) != -1) { + switch (ch) { + case 'h': + usage(pn); +#ifdef LIBXO + xo_finish(); +#endif + return EXIT_SUCCESS; + break; + case 't': + do_thresh = true; + break; + case 'x': + do_hex = true; + break; + case 'a': + if (parse_matches(&matches, optarg)) { + usage(pn); + return EXIT_FAILURE; + } + break; + case 'i': + do_info = true; + break; + case 'd': + do_descr = true; + break; + case 'D': + do_descr = false; + break; + case 'v': + do_version = true; + break; + case 0: + if (debugset) + do_debug = true; + break; + default: + usage(pn); +#ifdef LIBXO + xo_finish(); +#endif + return EXIT_FAILURE; + } + } + + if (do_version) { + printf("%s, version %s\n", pn, SMART_VERSION); + printf("Copyright (c) 2016-2026 Chuck Tuffli\n" + "This is free software; see the source for copying conditions.\n"); + return EXIT_SUCCESS; + } + + argc -= optind; + argv += optind; + + devname = argv[0]; + + if (!devname) { + printf("no device specified\n"); + usage(pn); +#ifdef LIBXO + xo_finish(); +#endif + return EXIT_FAILURE; + } + + h = smart_open(SMART_PROTO_AUTO, argv[0]); + + if (h == NULL) { + printf("device open failed %s\n", argv[0]); +#ifdef LIBXO + xo_finish(); +#endif + return EXIT_FAILURE; + } + +#ifdef LIBXO + xo_open_container("drive"); +#endif + + if (do_info) { + smart_print_device_info(h); + } + + if (smart_supported(h)) { + sm = smart_read(h); + + if (sm) { + uint32_t flags = 0; + + if (do_hex) + flags |= SMART_OPEN_F_HEX; + if (do_thresh) + flags |= SMART_OPEN_F_THRESH; + if (do_descr) + flags |= SMART_OPEN_F_DESCR; + + smart_print(h, sm, matches, flags); + + smart_free(sm); + } + } else { + rc = EXIT_FAILURE; + } +#ifdef LIBXO + xo_finish(); +#endif + smart_close(h); + + return rc; +} diff --git a/packages/Makefile b/packages/Makefile index 79bdab9f1abd..71a49d6d06ea 100644 --- a/packages/Makefile +++ b/packages/Makefile @@ -66,6 +66,7 @@ SUBDIR= blocklist \ resolvconf \ rip \ runtime \ + smart \ smbutils \ syslogd \ tcpd \ diff --git a/packages/smart/Makefile b/packages/smart/Makefile new file mode 100644 index 000000000000..2cc04493da57 --- /dev/null +++ b/packages/smart/Makefile @@ -0,0 +1,4 @@ +WORLDPACKAGE= smart + +.include <bsd.pkg.mk> + diff --git a/packages/smart/smart.ucl b/packages/smart/smart.ucl new file mode 100644 index 000000000000..e861f61a08ab --- /dev/null +++ b/packages/smart/smart.ucl @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2026 Chuck Tuffli <chuck@FreeBSD.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +comment = "SMART monitoring" + +desc = <<EOD +smart(8) allows the user to monitor the various information reported +by Self-Monitoring, Analysis and Reporting Technology (SMART) present +on most ATA, SCSI, and NVMe storage media. +EOD + +annotations { + set = "optional,optional-jail" +} + diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 3d3a8443a7cf..9e885d5d45c5 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -81,6 +81,7 @@ SUBDIR= adduser \ setfib \ setfmac \ setpmac \ + smart \ smbmsg \ snapinfo \ spi \ diff --git a/usr.sbin/smart/Makefile b/usr.sbin/smart/Makefile new file mode 100644 index 000000000000..924b06d3ebcb --- /dev/null +++ b/usr.sbin/smart/Makefile @@ -0,0 +1,8 @@ +.include <src.opts.mk> + +SMARTDIR=${SRCTOP}/contrib/smart +.PATH: ${SMARTDIR} + +PACKAGE= smart + +.include "${SMARTDIR}/Makefile" |
