Page MenuHomeFreeBSD

D56638.id176661.diff
No OneTemporary

D56638.id176661.diff

diff --git a/contrib/smart/Changelog b/contrib/smart/Changelog
new file mode 100644
--- /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
--- /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
--- /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
--- /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.h b/contrib/smart/libsmart.h
new file mode 100644
--- /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.c b/contrib/smart/libsmart.c
new file mode 100644
--- /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 = &param->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 = &param->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 = &param->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 = &param->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 = &param->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_desc.c b/contrib/smart/libsmart_desc.c
new file mode 100644
--- /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
--- /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
--- /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
--- /dev/null
+++ b/contrib/smart/smart.8
@@ -0,0 +1,250 @@
+.\"
+.\" 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
+.Pps
+.Pp
+Print only attribute ID 5 ("Reallocated Sectors Count") for
+ATA drive ada0.
+.Pp
+.Dl # smart -a 5 ada0
+.Pps
+.Pp
+Print attribute IDs 5 ("Reallocated Sectors Count") and 171
+("SSD Program Fail") for ATA drive ada0.
+.Pp
+.Dl # smart -a 5,171 ada0
+.Pps
+.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
+.Pps
+.Pp
+Print all Health log page entries in hexadecimal for NVMe
+device nda0.
+.Pp
+.Dl # smart -x nda0
+.Pps
+.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
--- /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
--- a/packages/Makefile
+++ b/packages/Makefile
@@ -67,6 +67,7 @@
resolvconf \
rip \
runtime \
+ smart \
smbutils \
syslogd \
tcpd \
diff --git a/packages/smart/Makefile b/packages/smart/Makefile
new file mode 100644
--- /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
--- /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
--- a/usr.sbin/Makefile
+++ b/usr.sbin/Makefile
@@ -81,6 +81,7 @@
setfib \
setfmac \
setpmac \
+ smart \
smbmsg \
snapinfo \
spi \
diff --git a/usr.sbin/smart/Makefile b/usr.sbin/smart/Makefile
new file mode 100644
--- /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"

File Metadata

Mime Type
text/plain
Expires
Sat, May 2, 8:51 AM (15 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
32612125
Default Alt Text
D56638.id176661.diff (87 KB)

Event Timeline