Vendor import of smart at 1.0.2
smart/diskhealth is a command line application to monitor disk health from a storage device via SMART. Reviewed by: fuz, jrm Relnotes: yes Differential Revision: https://reviews.freebsd.org/D56638
This commit is contained in:
@@ -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
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -0,0 +1,245 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: BSD-2-Clause
|
||||
.\"
|
||||
.\" Copyright (c) 2021-2026 Chuck Tuffli
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
.\" modification, are permitted provided that the following conditions
|
||||
.\" are met:
|
||||
.\" 1. Redistributions of source code must retain the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer.
|
||||
.\" 2. Redistributions in binary form must reproduce the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer in the
|
||||
.\" documentation and/or other materials provided with the distribution.
|
||||
.\"
|
||||
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.\" Note: The date here should be updated whenever a non-trivial
|
||||
.\" change is made to the manual page.
|
||||
.Dd February 14, 2026
|
||||
.Dt SMART 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm smart ,
|
||||
.Nm diskhealth
|
||||
.Nd monitor disk health from a storage device via SMART
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dhitvx
|
||||
.Oo Fl a Ar page:attribute Ns Oo , Ns Ar page:attribute Oc Ns ... Oc
|
||||
.Op Fl Fl debug
|
||||
.Ar device
|
||||
.Nm diskhealth
|
||||
.Op Fl Dhitvx
|
||||
.Oo Fl a Ar page:attribute Ns Oo , Ns Ar page:attribute Oc Ns ... Oc
|
||||
.Op Fl Fl debug
|
||||
.Ar device
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
command allows the user to monitor the various information reported
|
||||
by Self-Monitoring, Analysis and Reporting Technology (SMART) present
|
||||
on most ATA, SCSI, and NVMe storage media.
|
||||
Because the structure of this information varies across protocols,
|
||||
.Nm
|
||||
normalizes entries using the format:
|
||||
.Bd -literal
|
||||
<Page ID> <Attribute ID> <Value> <Thresholds>
|
||||
.Ed
|
||||
.Pp
|
||||
Fields are tab-delimited by default, but the command can output
|
||||
data in any format supported by
|
||||
.Xr libxo 3 .
|
||||
.Pp
|
||||
Because ATA does not have log pages,
|
||||
.Nm
|
||||
uses the Command Feature field value in place of the log page ID.
|
||||
For SMART READ DATA, this value is 208 / 0xd0.
|
||||
Note that devices choose which attribute ID values they support, their
|
||||
descriptions, and the format of the data.
|
||||
The three values displayed with the
|
||||
.Fl Fl threshold
|
||||
option are:
|
||||
.Pp
|
||||
.Bl -dash -compact -offset indent
|
||||
.It
|
||||
Status flags (byte offset 1, 2 bytes)
|
||||
.It
|
||||
Nominal attribute value (byte offset 3, 1 byte)
|
||||
.It
|
||||
Worst Ever attribute value (byte offset 4, 1 byte)
|
||||
.El
|
||||
.Pp
|
||||
Additionally,
|
||||
.Nm
|
||||
reports the value of the SMART STATUS command (Command Feature field
|
||||
value 218 / 0xda).
|
||||
As this command does not return any data,
|
||||
the command represents this entry with a synthetic attribute
|
||||
ID of 0, and it uses the command status (0 or 1) as the attribute
|
||||
value.
|
||||
.Pp
|
||||
NVMe devices support the SMART/Health log page (Page ID 2 / 0x2).
|
||||
The data returned in this log page is not structured as attribute IDs.
|
||||
Instead,
|
||||
.Nm
|
||||
uses the byte offset of each field as the attribute ID.
|
||||
For example,
|
||||
byte 3 is the Available Spare.
|
||||
Thus, for NVMe, attribute ID 3 is
|
||||
Available Spare.
|
||||
Note that NVMe health data does not include threshold
|
||||
values, and as a result, the command will ignore the
|
||||
.Fl Fl threshold
|
||||
option.
|
||||
.Pp
|
||||
SCSI devices can support a number of log pages which report drive
|
||||
health.
|
||||
The command will report the following pages:
|
||||
.Pp
|
||||
.Bl -dash -compact -offset indent
|
||||
.It
|
||||
Write Errors (Page ID 2 / 0x2)
|
||||
.It
|
||||
Read Errors (Page ID 3 / 0x3)
|
||||
.It
|
||||
Verify Errors (Page ID 5 / 0x5)
|
||||
.It
|
||||
Non-medium Errors (Page ID 6 / 0x6)
|
||||
.It
|
||||
Last N Errors (Page ID 7 / 0x7)
|
||||
.It
|
||||
Temperature (Page ID 13 / 0xd)
|
||||
.It
|
||||
Start-stop Cycles (Page ID 14 / 0xe)
|
||||
.It
|
||||
Informational Exceptions (Page ID 47 / 0x2f)
|
||||
.El
|
||||
.Pp
|
||||
Note that all log pages are optional, and a particular drive
|
||||
may not support all these pages.
|
||||
For SCSI devices, the Attribute ID
|
||||
maps to the SCSI parameter code defined by the command.
|
||||
Parameter
|
||||
codes are integer values from 0 to N, and, by themselves, are ambiguous
|
||||
outside the context of a particular log page.
|
||||
Note that SCSI health data
|
||||
does not include threshold values, and as a result, the command will
|
||||
ignore the
|
||||
.Fl Fl threshold
|
||||
option.
|
||||
.Pp
|
||||
The following options are available:
|
||||
.Bl -tag -width "-d argument"
|
||||
.It Fl a Ar page:attribute , Fl Fl attribute= Ns Ar page:attribute
|
||||
A comma-separated list of attributes to display.
|
||||
If page is missing, display the matching attribute from any page.
|
||||
.It Fl d , Fl Fl decode
|
||||
Decode the attribute ID values.
|
||||
This is the default when invoked as
|
||||
.Nm diskhealth .
|
||||
.It Fl D , Fl Fl no-decode
|
||||
Do not decode the attribute ID values.
|
||||
This is the default when invoked as
|
||||
.Nm .
|
||||
.It Fl h , Fl Fl help
|
||||
Prints a usage message and exits.
|
||||
.It Fl i , Fl Fl info
|
||||
Print general device information.
|
||||
.It Fl t , Fl Fl threshold
|
||||
Also print the threshold values.
|
||||
.It Fl v , Fl Fl version
|
||||
Print the version and copyright.
|
||||
.It Fl x , Fl Fl hex
|
||||
Print the values in hexadecimal.
|
||||
.It Ar device
|
||||
An explicit device path
|
||||
.Pq Pa /dev/ada0
|
||||
or GEOM provider
|
||||
.Pq Pa ada0
|
||||
.
|
||||
.El
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Ex -std
|
||||
.Sh EXAMPLES
|
||||
Print all SMART READ DATA and SMART STATUS including the
|
||||
threshold values for ATA drive ada0.
|
||||
.Pp
|
||||
.Dl # smart -t ada0
|
||||
.Pp
|
||||
Print only attribute ID 5 ("Reallocated Sectors Count") for
|
||||
ATA drive ada0.
|
||||
.Pp
|
||||
.Dl # smart -a 5 ada0
|
||||
.Pp
|
||||
Print attribute IDs 5 ("Reallocated Sectors Count") and 171
|
||||
("SSD Program Fail") for ATA drive ada0.
|
||||
.Pp
|
||||
.Dl # smart -a 5,171 ada0
|
||||
.Pp
|
||||
Print all health pages supported by SCSI device da0 including:
|
||||
.Bl -dash -compact -offset indent
|
||||
.It
|
||||
Write Errors
|
||||
.It
|
||||
Read Errors
|
||||
.It
|
||||
Verify Errors
|
||||
.It
|
||||
Non-medium Errors
|
||||
.It
|
||||
Last N Errors
|
||||
.It
|
||||
Temperature
|
||||
.It
|
||||
Start-stop Cycles
|
||||
.It
|
||||
Informational Exceptions
|
||||
.El
|
||||
.Pp
|
||||
.Dl # smart da0
|
||||
.Pp
|
||||
Print all Health log page entries in hexadecimal for NVMe
|
||||
device nda0.
|
||||
.Pp
|
||||
.Dl # smart -x nda0
|
||||
.Sh DIAGNOSTICS
|
||||
The command may fail for one of the following reasons:
|
||||
.Bl -diag
|
||||
.It "No output displayed"
|
||||
The device does not support health data.
|
||||
.It "CAMGETPASSTHRU ioctl failed"
|
||||
.Nm
|
||||
relies on
|
||||
.Xr cam 4
|
||||
to retrieve data from devices and will display this message if the
|
||||
device does not have a passthrough driver.
|
||||
This can happen, for
|
||||
example, if the system uses the
|
||||
.Xr nvd 4
|
||||
NVMe driver instead of the
|
||||
.Xr nda 4
|
||||
driver.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr libxo 3 ,
|
||||
.Xr cam 4 ,
|
||||
.Xr nda 4 ,
|
||||
.Xr nvd 4
|
||||
.Sh AUTHORS
|
||||
This
|
||||
utility was written by
|
||||
.An Chuck Tuffli Aq Mt chuck@FreeBSD.org .
|
||||
.Sh BUGS
|
||||
Probably.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -66,6 +66,7 @@ SUBDIR= blocklist \
|
||||
resolvconf \
|
||||
rip \
|
||||
runtime \
|
||||
smart \
|
||||
smbutils \
|
||||
syslogd \
|
||||
tcpd \
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
WORLDPACKAGE= smart
|
||||
|
||||
.include <bsd.pkg.mk>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ SUBDIR= adduser \
|
||||
setfib \
|
||||
setfmac \
|
||||
setpmac \
|
||||
smart \
|
||||
smbmsg \
|
||||
snapinfo \
|
||||
spi \
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.include <src.opts.mk>
|
||||
|
||||
SMARTDIR=${SRCTOP}/contrib/smart
|
||||
.PATH: ${SMARTDIR}
|
||||
|
||||
PACKAGE= smart
|
||||
|
||||
.include "${SMARTDIR}/Makefile"
|
||||
Reference in New Issue
Block a user