Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F155095933
D56638.id176661.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
87 KB
Referenced Files
None
Subscribers
None
D56638.id176661.diff
View Options
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 = ¶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_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
Details
Attached
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)
Attached To
Mode
D56638: Vendor import of smart at 1.0.2
Attached
Detach File
Event Timeline
Log In to Comment