From 87e52cf180921d1fb778298c1ee699e652639b79 Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 5 Mar 2021 17:26:45 +0100 Subject: [PATCH] libatasmart: initial checkin This library is required by the smart plugin of the collectd. Signed-off-by: Florian Eckert --- libs/libatasmart/Makefile | 98 + .../patches/001-fix-cross-compile.patch | 43 + libs/libatasmart/src/atasmart.strpool.c | 3235 +++++++++++++++++ 3 files changed, 3376 insertions(+) create mode 100644 libs/libatasmart/Makefile create mode 100644 libs/libatasmart/patches/001-fix-cross-compile.patch create mode 100644 libs/libatasmart/src/atasmart.strpool.c diff --git a/libs/libatasmart/Makefile b/libs/libatasmart/Makefile new file mode 100644 index 000000000..097bd4938 --- /dev/null +++ b/libs/libatasmart/Makefile @@ -0,0 +1,98 @@ +# +# Copyright (C) 2021 TDT AG +# +# This is free software, licensed under the GNU General Public License v2. +# See https://www.gnu.org/licenses/gpl-2.0.txt for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=libatasmart +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://git.0pointer.net/libatasmart.git +PKG_SOURCE_DATE:=2012-05-21 +PKG_SOURCE_VERSION:=de6258940960443038b4c1651dfda3620075e870 +PKG_MIRROR_HASH:=6d2a8782d16e4c1b909e5e836c43c6d58d65b0e1698a53a463a8694a396eb0d7 + +PKG_MAINTAINER:=Florian Eckert +PKG_LICENSE:=LGPL-2.1 +PKG_LICENSE_FILES:=LGPL + +PKG_REMOVE_FILES:=autogen.sh +PKG_FIXUP:=autoreconf +PKG_INSTALL:=1 +PKG_BUILD_PARALLEL:=1 +PKG_BUILD_DEPENDS:=libatasmart/host + +# Do not do autoconf FIXUP for host. +# We only need Host Compiled strpool binary. +HOST_FIXUP:= + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/host-build.mk + +define Package/libatasmart + SECTION:=libs + CATEGORY:=Libraries + TITLE:=S.M.A.R.T. Reading and Parsing Library + URL:=https://git.0pointer.net/libatasmart.git + DEPENDS:= +libudev +endef + +define Package/libatasmart/description + This library is supposed to be lean and small and thus + supports only a subset of the S.M.A.R.T. functionality. + However, I claim that it implements the relevant part of it. + If you need full control over all S.M.A.R.T. functionality of + your hardware please refer to smartmontools. +endef + +define Host/Configure +endef + +define Host/Compile + $(RM) -rf $(HOST_BUILD_DIR)/strpool + $(HOSTCC) $(HOST_CFLAGS) $(HOST_LDFLAGS) \ + -o $(HOST_BUILD_DIR)/strpool \ + $(HOST_BUILD_DIR)/strpool.c +endef + +define Host/Install + $(INSTALL_DIR) $(STAGING_DIR_HOSTPKG)/bin + $(INSTALL_BIN) $(HOST_BUILD_DIR)/strpool $(STAGING_DIR_HOSTPKG)/bin +endef + +define Build/Configure + $(RM) -rf $(PKG_BUILD_DIR)/strpool + $(RM) $(PKG_BUILD_DIR)/strpool.c + $(Build/Configure/Default) +endef + +define Build/InstallDev + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/*.la \ + $(1)/usr/lib + + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/*.so* \ + $(1)/usr/lib + + $(INSTALL_DIR) $(1)/usr/include + $(CP) $(PKG_INSTALL_DIR)/usr/include/*.h \ + $(1)/usr/include + + $(INSTALL_DIR) $(1)/usr/lib/pkgconfig + $(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/*.pc \ + $(1)/usr/lib/pkgconfig/ +endef + +define Package/libatasmart/install + $(INSTALL_DIR) $(1)/usr/lib + $(CP) $(PKG_INSTALL_DIR)/usr/lib/*.so* \ + $(1)/usr/lib +endef + +$(eval $(call HostBuild)) +$(eval $(call BuildPackage,libatasmart)) diff --git a/libs/libatasmart/patches/001-fix-cross-compile.patch b/libs/libatasmart/patches/001-fix-cross-compile.patch new file mode 100644 index 000000000..93d8633b5 --- /dev/null +++ b/libs/libatasmart/patches/001-fix-cross-compile.patch @@ -0,0 +1,43 @@ +--- a/configure.ac ++++ b/configure.ac +@@ -114,7 +114,6 @@ dnl################################### + + AC_CONFIG_FILES([ + Makefile +-strpool/Makefile + libatasmart.pc + ]) + AC_OUTPUT +--- a/Makefile.am ++++ b/Makefile.am +@@ -22,7 +22,6 @@ AM_LDFLAGS = $(GCLDFLAGS) + dist_doc_DATA = README + + EXTRA_DIST = \ +- autogen.sh \ + LGPL \ + README \ + atasmart.c \ +@@ -47,9 +46,6 @@ EXTRA_DIST = \ + blob-examples/WDC_WD5000AAKS--00TMA0-12.01C01 \ + vala/atasmart.vapi + +-# build the strpool tool first +-SUBDIRS = strpool . +- + CLEANFILES = atasmart.strpool.c + + MAINTAINERCLEANFILES = +@@ -94,10 +90,7 @@ libatasmart_la_CFLAGS = \ + BUILT_SOURCES = \ + atasmart.strpool.c + +-strpool/strpool: +- $(MAKE) -C strpool strpool +- +-atasmart.strpool.c: atasmart.c strpool/strpool +- $(top_builddir)/strpool/strpool $< $@ ++atasmart.strpool.c: atasmart.c ++ $(STAGING_DIR_HOSTPKG)/bin/strpool $< $@ + + ACLOCAL_AMFLAGS = -I m4 diff --git a/libs/libatasmart/src/atasmart.strpool.c b/libs/libatasmart/src/atasmart.strpool.c new file mode 100644 index 000000000..990cf240d --- /dev/null +++ b/libs/libatasmart/src/atasmart.strpool.c @@ -0,0 +1,3235 @@ +/* Saved 149 relocations, saved 7 strings (90 b) due to suffix compression. */ +static const char _strpool_[] = + "16 Byte SCSI ATA SAT Passthru\0" + "12 Byte SCSI ATA SAT Passthru\0" + "Native Linux IDE\0" + "Sunplus SCSI ATA Passthru\0" + "JMicron SCSI ATA Passthru\0" + "Blob\0" + "Automatic\0" + "None\0" + "sat16\0" + "sat12\0" + "linux-ide\0" + "sunplus\0" + "jmicron\0" + "none\0" + "auto\0" + "Off-line data collection activity was never started.\0" + "Off-line data collection activity was completed without error.\0" + "Off-line activity in progress.\0" + "Off-line data collection activity was suspended by an interrupting command from host.\0" + "Off-line data collection activity was aborted by an interrupting command from host.\0" + "Off-line data collection activity was aborted by the device with a fatal error.\0" + "Unknown status\0" + "The previous self-test routine completed without error or no self-test has ever been run.\0" + "The self-test routine was aborted by the host.\0" + "The self-test routine was interrupted by the host with a hardware or software reset.\0" + "A fatal error or unknown test error occurred while the device was executing its self-test routine and the device was unable to complete the self-test routine.\0" + "The previous self-test completed having a test element that failed and the test element that failed.\0" + "The previous self-test completed having the electrical element of the test failed.\0" + "The previous self-test completed having the servo (and/or seek) test element of the test failed.\0" + "The previous self-test completed having the read element of the test failed.\0" + "The previous self-test completed having a test element that failed and the device is suspected of having handling damage.\0" + "Self-test routine in progress\0" + "raw-read-error-rate\0" + "throughput-performance\0" + /*** Suppressed due to suffix: + "spin-up-time\0" ***/ + /*** Suppressed due to suffix: + "start-stop-count\0" ***/ + "reallocated-sector-count\0" + "read-channel-margin\0" + "seek-error-rate\0" + "seek-time-performance\0" + "power-on-hours\0" + "spin-retry-count\0" + "calibration-retry-count\0" + "power-cycle-count\0" + "read-soft-error-rate\0" + /*** Suppressed due to suffix: + "available-reserved-space\0" ***/ + "program-fail-count\0" + "erase-fail-count\0" + "program-fail-count-chip\0" + "erase-fail-count-chip\0" + "wear-leveling-count\0" + "used-reserved-blocks-chip\0" + "used-reserved-blocks-total\0" + "unused-reserved-blocks\0" + "program-fail-count-total\0" + "erase-fail-count-total\0" + "runtime-bad-block-total\0" + "end-to-end-error\0" + "reported-uncorrect\0" + "command-timeout\0" + "high-fly-writes\0" + "airflow-temperature-celsius\0" + "g-sense-error-rate\0" + "power-off-retract-count\0" + "load-cycle-count\0" + "temperature-celsius-2\0" + "hardware-ecc-recovered\0" + "reallocated-event-count\0" + "current-pending-sector\0" + "offline-uncorrectable\0" + "udma-crc-error-count\0" + "multi-zone-error-rate\0" + "soft-read-error-rate\0" + "ta-increase-count\0" + "run-out-cancel\0" + "shock-count-write-open\0" + "shock-rate-write-open\0" + "flying-height\0" + "spin-high-current\0" + "spin-buzz\0" + "offline-seek-performance\0" + "disk-shift\0" + "g-sense-error-rate-2\0" + "loaded-hours\0" + "load-retry-count\0" + "load-friction\0" + "load-cycle-count-2\0" + "load-in-time\0" + "torq-amp-count\0" + "power-off-retract-count-2\0" + "head-amplitude\0" + /*** Suppressed due to suffix: + "temperature-celsius\0" ***/ + "endurance-remaining\0" + "power-on-seconds-2\0" + "uncorrectable-ecc-count\0" + "good-block-rate\0" + "head-flying-hours\0" + /*** Suppressed due to suffix: + "total-lbas-written\0" ***/ + "total-lbas-read\0" + "read-error-retry-rate\0" + "9_POWERONMINUTES\0" + "9_POWERONSECONDS\0" + "9_POWERONHALFMINUTES\0" + "192_EMERGENCYRETRACTCYCLECT\0" + "193_LOADUNLOAD\0" + "194_10XCELSIUS\0" + "194_UNKNOWN\0" + "200_WRITEERRORCOUNT\0" + "201_DETECTEDTACOUNT\0" + "5_UNKNOWN\0" + "9_UNKNOWN\0" + "197_UNKNOWN\0" + "198_UNKNOWN\0" + "190_UNKNOWN\0" + "232_AVAILABLERESERVEDSPACE\0" + "233_MEDIAWEAROUTINDICATOR\0" + "225_TOTALLBASWRITTEN\0" + "4_UNUSED\0" + "226_TIMEWORKLOADMEDIAWEAR\0" + "227_TIMEWORKLOADHOSTREADS\0" + "228_WORKLOADTIMER\0" + "3_UNUSED\0" + "spin-up-time\0" + "start-stop-count\0" + "power-on-minutes\0" + "power-on-seconds\0" + "power-on-half-minutes\0" + "emergency-retract-cycle-count\0" + "temperature-centi-celsius\0" + "write-error-count\0" + "detected-ta-count\0" + "total-lbas-written\0" + "timed-workload-media-wear\0" + "timed-workload-host-reads\0" + "workload-timer\0" + "available-reserved-space\0" + "media-wearout-indicator\0" + /*** Suppressed due to suffix: + "\0" ***/ + "ms\0" + "sectors\0" + "mK\0" + /*** Suppressed due to suffix: + "%\0" ***/ + "%\0" + "MB\0" + "GOOD\0" + "BAD_ATTRIBUTE_IN_THE_PAST\0" + "BAD_SECTOR\0" + "BAD_ATTRIBUTE_NOW\0" + "BAD_SECTOR_MANY\0" + "BAD_STATUS\0"; +#ifndef STRPOOL +#define STRPOOL +#endif +#ifndef _P +#define _P(x) (_strpool_ + ((x) - (const char*) 1)) +#endif + +#line 1 "atasmart.c" +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + This file is part of libatasmart. + + Copyright 2008 Lennart Poettering + + libatasmart is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 2.1 of the + License, or (at your option) any later version. + + libatasmart is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with libatasmart. If not, If not, see + . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atasmart.h" + +#ifndef STRPOOL +#define _P(x) x +#endif + +#define SK_TIMEOUT 2000 + +typedef enum SkDirection { + SK_DIRECTION_NONE, + SK_DIRECTION_IN, + SK_DIRECTION_OUT, + _SK_DIRECTION_MAX +} SkDirection; + +typedef enum SkDiskType { + /* These three will be autotested for: */ + SK_DISK_TYPE_ATA_PASSTHROUGH_12, /* ATA passthrough over SCSI transport, 12-byte version */ + SK_DISK_TYPE_ATA_PASSTHROUGH_16, /* ATA passthrough over SCSI transport, 16-byte version */ + SK_DISK_TYPE_LINUX_IDE, /* Classic Linux /dev/hda ioctls */ + + /* These three will not be autotested for */ + SK_DISK_TYPE_SUNPLUS, /* SunPlus USB/ATA bridges */ + SK_DISK_TYPE_JMICRON, /* JMicron USB/ATA bridges */ + SK_DISK_TYPE_BLOB, /* From a file */ + SK_DISK_TYPE_NONE, /* No access method */ + SK_DISK_TYPE_AUTO, /* We don't know yet */ + _SK_DISK_TYPE_MAX, + _SK_DISK_TYPE_TEST_MAX = SK_DISK_TYPE_SUNPLUS /* only auto test until here */ +} SkDiskType; + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define MAKE_TAG(a,b,c,d) \ + (((uint32_t) d << 24) | \ + ((uint32_t) c << 16) | \ + ((uint32_t) b << 8) | \ + ((uint32_t) a)) +#else +#define MAKE_TAG(a,b,c,d) \ + (((uint32_t) a << 24) | \ + ((uint32_t) b << 16) | \ + ((uint32_t) c << 8) | \ + ((uint32_t) d)) +#endif + +typedef enum SkBlobTag { + SK_BLOB_TAG_IDENTIFY = MAKE_TAG('I', 'D', 'F', 'Y'), + SK_BLOB_TAG_SMART_STATUS = MAKE_TAG('S', 'M', 'S', 'T'), + SK_BLOB_TAG_SMART_DATA = MAKE_TAG('S', 'M', 'D', 'T'), + SK_BLOB_TAG_SMART_THRESHOLDS = MAKE_TAG('S', 'M', 'T', 'H') +} SkBlobTag; + +struct SkDisk { + char *name; + int fd; + SkDiskType type; + + uint64_t size; + + uint8_t identify[512]; + uint8_t smart_data[512]; + uint8_t smart_thresholds[512]; + + SkBool smart_initialized:1; + + SkBool identify_valid:1; + SkBool smart_data_valid:1; + SkBool smart_thresholds_valid:1; + + SkBool blob_smart_status:1; + SkBool blob_smart_status_valid:1; + + SkBool attribute_verification_bad:1; + + SkIdentifyParsedData identify_parsed_data; + SkSmartParsedData smart_parsed_data; + + /* cache for commonly used attributes */ + SkBool attribute_cache_valid:1; + SkBool bad_attribute_now:1; + SkBool bad_attribute_in_the_past:1; + SkBool reallocated_sector_count_found:1; + SkBool current_pending_sector_found:1; + uint64_t reallocated_sector_count; + uint64_t current_pending_sector; + + void *blob; +}; + +/* ATA commands */ +typedef enum SkAtaCommand { + SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC, + SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1, + SK_ATA_COMMAND_SMART = 0xB0, + SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5 +} SkAtaCommand; + +/* ATA SMART subcommands (ATA8 7.52.1) */ +typedef enum SkSmartCommand { + SK_SMART_COMMAND_READ_DATA = 0xD0, + SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1, + SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4, + SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8, + SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9, + SK_SMART_COMMAND_RETURN_STATUS = 0xDA +} SkSmartCommand; + +/* Hmm, if the data we parse is out of a certain range just consider it misparsed */ +#define SK_MKELVIN_VALID_MIN ((uint64_t) ((-15LL*1000LL) + 273150LL)) +#define SK_MKELVIN_VALID_MAX ((uint64_t) ((100LL*1000LL) + 273150LL)) + +#define SK_MSECOND_VALID_MIN 1ULL +#define SK_MSECOND_VALID_SHORT_MAX (60ULL * 60ULL * 1000ULL) +#define SK_MSECOND_VALID_LONG_MAX (30ULL * 365ULL * 24ULL * 60ULL * 60ULL * 1000ULL) + +static int init_smart(SkDisk *d); + +static const char *disk_type_to_human_string(SkDiskType type) { + + /* %STRINGPOOLSTART% */ + static const char* const map[_SK_DISK_TYPE_MAX] = { + [SK_DISK_TYPE_ATA_PASSTHROUGH_16] = ((const char*) 1), + [SK_DISK_TYPE_ATA_PASSTHROUGH_12] = ((const char*) 31), + [SK_DISK_TYPE_LINUX_IDE] = ((const char*) 61), + [SK_DISK_TYPE_SUNPLUS] = ((const char*) 78), + [SK_DISK_TYPE_JMICRON] = ((const char*) 104), + [SK_DISK_TYPE_BLOB] = ((const char*) 130), + [SK_DISK_TYPE_AUTO] = ((const char*) 135), + [SK_DISK_TYPE_NONE] = ((const char*) 145) + }; + /* %STRINGPOOLSTOP% */ + + if (type >= _SK_DISK_TYPE_MAX) + return NULL; + + return _P(map[type]); +} + +static const char *disk_type_to_prefix_string(SkDiskType type) { + + /* %STRINGPOOLSTART% */ + static const char* const map[_SK_DISK_TYPE_MAX] = { + [SK_DISK_TYPE_ATA_PASSTHROUGH_16] = ((const char*) 150), + [SK_DISK_TYPE_ATA_PASSTHROUGH_12] = ((const char*) 156), + [SK_DISK_TYPE_LINUX_IDE] = ((const char*) 162), + [SK_DISK_TYPE_SUNPLUS] = ((const char*) 172), + [SK_DISK_TYPE_JMICRON] = ((const char*) 180), + [SK_DISK_TYPE_NONE] = ((const char*) 188), + [SK_DISK_TYPE_AUTO] = ((const char*) 193), + }; + /* %STRINGPOOLSTOP% */ + + if (type >= _SK_DISK_TYPE_MAX) + return NULL; + + return _P(map[type]); +} + +static const char *disk_type_from_string(const char *s, SkDiskType *type) { + unsigned u; + + assert(s); + assert(type); + + for (u = 0; u < _SK_DISK_TYPE_MAX; u++) { + const char *t; + size_t l; + + if (!(t = disk_type_to_prefix_string(u))) + continue; + + l = strlen(t); + + if (strncmp(s, t, l)) + continue; + + if (s[l] != ':') + continue; + + *type = u; + + return s + l + 1; + } + + return NULL; +} + +static SkBool disk_smart_is_available(SkDisk *d) { + return d->identify_valid && !!(d->identify[164] & 1); +} + +static SkBool disk_smart_is_enabled(SkDisk *d) { + return d->identify_valid && !!(d->identify[170] & 1); +} + +static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) { + assert(d->smart_data_valid); + + return !!(d->smart_data[367] & 32); +} +static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) { + assert(d->smart_data_valid); + + return !!(d->smart_data[367] & 16); +} + +static SkBool disk_smart_is_start_test_available(SkDisk *d) { + assert(d->smart_data_valid); + + return !!(d->smart_data[367] & 1); +} + +static SkBool disk_smart_is_abort_test_available(SkDisk *d) { + assert(d->smart_data_valid); + + return !!(d->smart_data[367] & 41); +} + +static int disk_linux_ide_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) { + uint8_t *bytes = cmd_data; + int ret; + + assert(d->type == SK_DISK_TYPE_LINUX_IDE); + + switch (direction) { + + case SK_DIRECTION_OUT: + + /* We could use HDIO_DRIVE_TASKFILE here, but + * that's a deprecated ioctl(), hence we don't + * do it. And we don't need writing anyway. */ + + errno = ENOTSUP; + return -1; + + case SK_DIRECTION_IN: { + uint8_t *ioctl_data; + + /* We have HDIO_DRIVE_CMD which can only read, but not write, + * and cannot do LBA. We use it for all read commands. */ + + ioctl_data = alloca(4 + *len); + memset(ioctl_data, 0, 4 + *len); + + ioctl_data[0] = (uint8_t) command; /* COMMAND */ + ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3]; /* SECTOR/NSECTOR */ + ioctl_data[2] = bytes[1]; /* FEATURE */ + ioctl_data[3] = bytes[3]; /* NSECTOR */ + + if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0) + return ret; + + memset(bytes, 0, 12); + bytes[11] = ioctl_data[0]; + bytes[1] = ioctl_data[1]; + bytes[3] = ioctl_data[2]; + + memcpy(data, ioctl_data+4, *len); + + return ret; + } + + case SK_DIRECTION_NONE: { + uint8_t ioctl_data[7]; + + /* We have HDIO_DRIVE_TASK which can neither read nor + * write, but can do LBA. We use it for all commands that + * do neither read nor write */ + + memset(ioctl_data, 0, sizeof(ioctl_data)); + + ioctl_data[0] = (uint8_t) command; /* COMMAND */ + ioctl_data[1] = bytes[1]; /* FEATURE */ + ioctl_data[2] = bytes[3]; /* NSECTOR */ + + ioctl_data[3] = bytes[9]; /* LBA LOW */ + ioctl_data[4] = bytes[8]; /* LBA MID */ + ioctl_data[5] = bytes[7]; /* LBA HIGH */ + ioctl_data[6] = bytes[10]; /* SELECT */ + + if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data))) + return ret; + + memset(bytes, 0, 12); + bytes[11] = ioctl_data[0]; + bytes[1] = ioctl_data[1]; + bytes[3] = ioctl_data[2]; + + bytes[9] = ioctl_data[3]; + bytes[8] = ioctl_data[4]; + bytes[7] = ioctl_data[5]; + + bytes[10] = ioctl_data[6]; + + return ret; + } + + default: + assert(FALSE); + return -1; + } +} + +/* Sends a SCSI command block */ +static int sg_io(int fd, int direction, + const void *cdb, size_t cdb_len, + void *data, size_t data_len, + void *sense, size_t sense_len) { + + struct sg_io_hdr io_hdr; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = cdb_len; + io_hdr.dxferp = data; + io_hdr.dxfer_len = data_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sense_len; + io_hdr.dxfer_direction = direction; + io_hdr.timeout = SK_TIMEOUT; + + return ioctl(fd, SG_IO, &io_hdr); +} + +static int disk_passthrough_16_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) { + uint8_t *bytes = cmd_data; + uint8_t cdb[16]; + uint8_t sense[32]; + uint8_t *desc = sense+8; + int ret; + + static const int direction_map[] = { + [SK_DIRECTION_NONE] = SG_DXFER_NONE, + [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV, + [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV + }; + + assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH_16); + + /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8 + * ATA Command Pass-Through": + * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */ + + memset(cdb, 0, sizeof(cdb)); + + cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */ + + if (direction == SK_DIRECTION_NONE) { + cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */ + cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */ + + } else if (direction == SK_DIRECTION_IN) { + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + + } else if (direction == SK_DIRECTION_OUT) { + cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */ + cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */ + } + + cdb[3] = bytes[0]; /* FEATURES */ + cdb[4] = bytes[1]; + + cdb[5] = bytes[2]; /* SECTORS */ + cdb[6] = bytes[3]; + + cdb[8] = bytes[9]; /* LBA LOW */ + cdb[10] = bytes[8]; /* LBA MID */ + cdb[12] = bytes[7]; /* LBA HIGH */ + + cdb[13] = bytes[10] & 0x4F; /* SELECT */ + cdb[14] = (uint8_t) command; + + memset(sense, 0, sizeof(sense)); + + if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0) + return ret; + + if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) { + errno = EIO; + return -1; + } + + memset(bytes, 0, 12); + + bytes[1] = desc[3]; + bytes[2] = desc[4]; + bytes[3] = desc[5]; + bytes[9] = desc[7]; + bytes[8] = desc[9]; + bytes[7] = desc[11]; + bytes[10] = desc[12]; + bytes[11] = desc[13]; + + return ret; +} + +static int disk_passthrough_12_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) { + uint8_t *bytes = cmd_data; + uint8_t cdb[12]; + uint8_t sense[32]; + uint8_t *desc = sense+8; + int ret; + + static const int direction_map[] = { + [SK_DIRECTION_NONE] = SG_DXFER_NONE, + [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV, + [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV + }; + + assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH_12); + + /* ATA Pass-Through 12 byte command, as described in "T10 04-262r8 + * ATA Command Pass-Through": + * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */ + + memset(cdb, 0, sizeof(cdb)); + + cdb[0] = 0xa1; /* OPERATION CODE: 12 byte pass through */ + + if (direction == SK_DIRECTION_NONE) { + cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */ + cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */ + + } else if (direction == SK_DIRECTION_IN) { + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + + } else if (direction == SK_DIRECTION_OUT) { + cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */ + cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */ + } + + cdb[3] = bytes[1]; /* FEATURES */ + cdb[4] = bytes[3]; /* SECTORS */ + + cdb[5] = bytes[9]; /* LBA LOW */ + cdb[6] = bytes[8]; /* LBA MID */ + cdb[7] = bytes[7]; /* LBA HIGH */ + + cdb[8] = bytes[10] & 0x4F; /* SELECT */ + cdb[9] = (uint8_t) command; + + memset(sense, 0, sizeof(sense)); + + if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0) + return ret; + + if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) { + errno = EIO; + return -1; + } + + memset(bytes, 0, 12); + + bytes[1] = desc[3]; /* FEATURES */ + bytes[2] = desc[4]; /* STATUS */ + bytes[3] = desc[5]; /* SECTORS */ + bytes[9] = desc[7]; /* LBA LOW */ + bytes[8] = desc[9]; /* LBA MID */ + bytes[7] = desc[11]; /* LBA HIGH */ + bytes[10] = desc[12]; /* SELECT */ + bytes[11] = desc[13]; /* ERROR */ + + return ret; +} + +static int disk_sunplus_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) { + uint8_t *bytes = cmd_data; + uint8_t cdb[12]; + uint8_t sense[32], buf[8]; + int ret; + static const int direction_map[] = { + [SK_DIRECTION_NONE] = SG_DXFER_NONE, + [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV, + [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV + }; + + assert(d->type == SK_DISK_TYPE_SUNPLUS); + + /* SunplusIT specific SCSI ATA pass-thru. Inspired by smartmonutils' support for these bridges */ + + memset(cdb, 0, sizeof(cdb)); + + cdb[0] = 0xF8; /* OPERATION CODE: Sunplus specific */ + cdb[1] = 0x00; /* Subcommand: Pass-thru */ + cdb[2] = 0x22; + + if (direction == SK_DIRECTION_NONE) + cdb[3] = 0x00; /* protocol */ + else if (direction == SK_DIRECTION_IN) + cdb[3] = 0x10; /* protocol */ + else if (direction == SK_DIRECTION_OUT) + cdb[3] = 0x11; /* protocol */ + + cdb[4] = bytes[3]; /* size? */ + cdb[5] = bytes[1]; /* FEATURES */ + cdb[6] = bytes[3]; /* SECTORS */ + cdb[7] = bytes[9]; /* LBA LOW */ + cdb[8] = bytes[8]; /* LBA MID */ + cdb[9] = bytes[7]; /* LBA HIGH */ + cdb[10] = bytes[10] | 0xA0; /* SELECT */ + cdb[11] = (uint8_t) command; + + memset(sense, 0, sizeof(sense)); + + /* Issue request */ + if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0) + return ret; + + memset(cdb, 0, sizeof(cdb)); + + cdb[0] = 0xF8; + cdb[1] = 0x00; + cdb[2] = 0x21; + + memset(buf, 0, sizeof(buf)); + + /* Ask for response */ + if ((ret = sg_io(d->fd, SG_DXFER_FROM_DEV, cdb, sizeof(cdb), buf, sizeof(buf), sense, sizeof(sense))) < 0) + return ret; + + memset(bytes, 0, 12); + + bytes[2] = buf[1]; /* ERROR */ + bytes[3] = buf[2]; /* SECTORS */ + bytes[9] = buf[3]; /* LBA LOW */ + bytes[8] = buf[4]; /* LBA MID */ + bytes[7] = buf[5]; /* LBA HIGH */ + bytes[10] = buf[6]; /* SELECT */ + bytes[11] = buf[7]; /* STATUS */ + + return ret; +} + +static int disk_jmicron_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* _data, size_t *_len) { + uint8_t *bytes = cmd_data; + uint8_t cdb[12]; + uint8_t sense[32]; + uint8_t port; + int ret; + SkBool is_smart_status = FALSE; + void *data = _data; + size_t len = _len ? *_len : 0; + uint8_t smart_status = 0; + + static const int direction_map[] = { + [SK_DIRECTION_NONE] = SG_DXFER_NONE, + [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV, + [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV + }; + + assert(d->type == SK_DISK_TYPE_JMICRON); + + /* JMicron specific SCSI ATA pass-thru. Inspired by smartmonutils' support for these bridges */ + + memset(cdb, 0, sizeof(cdb)); + + cdb[0] = 0xdf; /* operation code */ + cdb[1] = 0x10; + cdb[2] = 0x00; + cdb[3] = 0x00; /* size HI */ + cdb[4] = sizeof(port); /* size LO */ + cdb[5] = 0x00; + cdb[6] = 0x72; /* register address HI */ + cdb[7] = 0x0f; /* register address LO */ + cdb[8] = 0x00; + cdb[9] = 0x00; + cdb[10] = 0x00; + cdb[11] = 0xfd; + + memset(sense, 0, sizeof(sense)); + + if ((ret = sg_io(d->fd, SG_DXFER_FROM_DEV, cdb, sizeof(cdb), &port, sizeof(port), sense, sizeof(sense))) < 0) + return ret; + + /* Port & 0x04 is port #0, Port & 0x40 is port #1 */ + if (!(port & 0x44)) + return -EIO; + + cdb[0] = 0xdf; /* OPERATION CODE: 12 byte pass through */ + + if (command == SK_ATA_COMMAND_SMART && bytes[1] == SK_SMART_COMMAND_RETURN_STATUS) { + /* We need to rewrite the SMART status request */ + is_smart_status = TRUE; + direction = SK_DIRECTION_IN; + data = &smart_status; + len = sizeof(smart_status); + cdb[1] = 0x10; + } else if (direction == SK_DIRECTION_NONE) + cdb[1] = 0x10; + else if (direction == SK_DIRECTION_IN) + cdb[1] = 0x10; + else if (direction == SK_DIRECTION_OUT) + cdb[1] = 0x00; + + cdb[2] = 0x00; + + cdb[3] = (uint8_t) (len >> 8); + cdb[4] = (uint8_t) (len & 0xFF); + + cdb[5] = bytes[1]; /* FEATURES */ + cdb[6] = bytes[3]; /* SECTORS */ + + cdb[7] = bytes[9]; /* LBA LOW */ + cdb[8] = bytes[8]; /* LBA MID */ + cdb[9] = bytes[7]; /* LBA HIGH */ + + cdb[10] = bytes[10] | ((port & 0x04) ? 0xA0 : 0xB0); /* SELECT */ + cdb[11] = (uint8_t) command; + + memset(sense, 0, sizeof(sense)); + + if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len, sense, sizeof(sense))) < 0) + return ret; + + memset(bytes, 0, 12); + + if (is_smart_status) { + if (smart_status == 0x01 || smart_status == 0xc2) { + bytes[7] = 0xc2; /* LBA HIGH */ + bytes[8] = 0x4f; /* LBA MID */ + } else if (smart_status == 0x00 || smart_status == 0x2c) { + bytes[7] = 0x2c; /* LBA HIGH */ + bytes[8] = 0xf4; /* LBA MID */ + } else + return -EIO; + } else { + uint8_t regbuf[16]; + + cdb[0] = 0xdf; /* operation code */ + cdb[1] = 0x10; + cdb[2] = 0x00; + cdb[3] = 0x00; /* size HI */ + cdb[4] = sizeof(regbuf); /* size LO */ + cdb[5] = 0x00; + cdb[6] = (port & 0x04) ? 0x80 : 0x90; /* register address HI */ + cdb[7] = 0x00; /* register address LO */ + cdb[8] = 0x00; + cdb[9] = 0x00; + cdb[10] = 0x00; + cdb[11] = 0xfd; + + if ((ret = sg_io(d->fd, SG_DXFER_FROM_DEV, cdb, sizeof(cdb), regbuf, sizeof(regbuf), sense, sizeof(sense))) < 0) + return ret; + + bytes[2] = regbuf[14]; /* STATUS */ + bytes[3] = regbuf[0]; /* SECTORS */ + bytes[9] = regbuf[6]; /* LBA LOW */ + bytes[8] = regbuf[4]; /* LBA MID */ + bytes[7] = regbuf[10]; /* LBA HIGH */ + bytes[10] = regbuf[9]; /* SELECT */ + bytes[11] = regbuf[13]; /* ERROR */ + } + + return ret; +} + +static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) { + + static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = { + [SK_DISK_TYPE_LINUX_IDE] = disk_linux_ide_command, + [SK_DISK_TYPE_ATA_PASSTHROUGH_12] = disk_passthrough_12_command, + [SK_DISK_TYPE_ATA_PASSTHROUGH_16] = disk_passthrough_16_command, + [SK_DISK_TYPE_SUNPLUS] = disk_sunplus_command, + [SK_DISK_TYPE_JMICRON] = disk_jmicron_command, + [SK_DISK_TYPE_BLOB] = NULL, + [SK_DISK_TYPE_AUTO] = NULL, + [SK_DISK_TYPE_NONE] = NULL + }; + + assert(d); + assert(d->type <= _SK_DISK_TYPE_MAX); + assert(direction <= _SK_DIRECTION_MAX); + + assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0)); + assert(direction != SK_DIRECTION_NONE || (!data && !len)); + + if (!disk_command_table[d->type]) { + errno = -ENOTSUP; + return -1; + } + + return disk_command_table[d->type](d, command, direction, cmd_data, data, len); +} + +static int disk_identify_device(SkDisk *d) { + uint16_t cmd[6]; + int ret; + size_t len = 512; + const uint8_t *p; + + if (d->type == SK_DISK_TYPE_BLOB) + return 0; + + memset(d->identify, 0, len); + memset(cmd, 0, sizeof(cmd)); + + cmd[1] = htons(1); + + if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0) + return ret; + + if (len != 512) { + errno = EIO; + return -1; + } + + /* Check if IDENTIFY data is all NULs */ + for (p = d->identify; p < (const uint8_t*) d->identify+len; p++) + if (*p) { + p = NULL; + break; + } + + if (p) { + errno = EIO; + return -1; + } + + d->identify_valid = TRUE; + + return 0; +} + +int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) { + int ret; + uint16_t cmd[6]; + uint8_t status; + + if (!d->identify_valid) { + errno = ENOTSUP; + return -1; + } + + if (d->type == SK_DISK_TYPE_BLOB) { + errno = ENOTSUP; + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + + if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0) + return ret; + + if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) { + errno = EIO; + return -1; + } + + status = ntohs(cmd[1]) & 0xFF; + *awake = status == 0xFF || status == 0x80; /* idle and active/idle is considered awake */ + + return 0; +} + +static int disk_smart_enable(SkDisk *d, SkBool b) { + uint16_t cmd[6]; + + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } + + if (d->type == SK_DISK_TYPE_BLOB) { + errno = ENOTSUP; + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + + cmd[0] = htons(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS); + cmd[2] = htons(0x0000U); + cmd[3] = htons(0x00C2U); + cmd[4] = htons(0x4F00U); + + return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0); +} + +int sk_disk_smart_read_data(SkDisk *d) { + uint16_t cmd[6]; + int ret; + size_t len = 512; + + if (init_smart(d) < 0) + return -1; + + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } + + if (d->type == SK_DISK_TYPE_BLOB) + return 0; + + memset(cmd, 0, sizeof(cmd)); + + cmd[0] = htons(SK_SMART_COMMAND_READ_DATA); + cmd[1] = htons(1); + cmd[2] = htons(0x0000U); + cmd[3] = htons(0x00C2U); + cmd[4] = htons(0x4F00U); + + if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0) + return ret; + + d->smart_data_valid = TRUE; + + return ret; +} + +static int disk_smart_read_thresholds(SkDisk *d) { + uint16_t cmd[6]; + int ret; + size_t len = 512; + + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } + + if (d->type == SK_DISK_TYPE_BLOB) + return 0; + + memset(cmd, 0, sizeof(cmd)); + + cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS); + cmd[1] = htons(1); + cmd[2] = htons(0x0000U); + cmd[3] = htons(0x00C2U); + cmd[4] = htons(0x4F00U); + + if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_thresholds, &len)) < 0) + return ret; + + d->smart_thresholds_valid = TRUE; + + return ret; +} + +int sk_disk_smart_status(SkDisk *d, SkBool *good) { + uint16_t cmd[6]; + int ret; + + if (init_smart(d) < 0) + return -1; + + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } + + if (d->type == SK_DISK_TYPE_BLOB) { + + if (d->blob_smart_status_valid) { + *good = d->blob_smart_status; + return 0; + } + + errno = ENXIO; + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + + cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS); + cmd[1] = htons(0x0000U); + cmd[3] = htons(0x00C2U); + cmd[4] = htons(0x4F00U); + + if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0) + return ret; + + /* SAT/USB bridges truncate packets, so we only check for 4F, + * not for 2C on those */ + if ((d->type == SK_DISK_TYPE_ATA_PASSTHROUGH_12 || cmd[3] == htons(0x00C2U)) && + cmd[4] == htons(0x4F00U)) + *good = TRUE; + else if ((d->type == SK_DISK_TYPE_ATA_PASSTHROUGH_12 || cmd[3] == htons(0x002CU)) && + cmd[4] == htons(0xF400U)) + *good = FALSE; + else { + errno = EIO; + return -1; + } + + return ret; +} + +int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) { + uint16_t cmd[6]; + int ret; + + if (init_smart(d) < 0) + return -1; + + if (!disk_smart_is_available(d)) { + errno = ENOTSUP; + return -1; + } + + if (d->type == SK_DISK_TYPE_BLOB) { + errno = ENOTSUP; + return -1; + } + + if (!d->smart_data_valid) + if ((ret = sk_disk_smart_read_data(d)) < 0) + return -1; + + assert(d->smart_data_valid); + + if (test != SK_SMART_SELF_TEST_SHORT && + test != SK_SMART_SELF_TEST_EXTENDED && + test != SK_SMART_SELF_TEST_CONVEYANCE && + test != SK_SMART_SELF_TEST_ABORT) { + errno = EINVAL; + return -1; + } + + if (!disk_smart_is_start_test_available(d) + || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d)) + || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d)) + || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) { + errno = ENOTSUP; + return -1; + } + + if (test == SK_SMART_SELF_TEST_ABORT && + !disk_smart_is_abort_test_available(d)) { + errno = ENOTSUP; + return -1; + } + + memset(cmd, 0, sizeof(cmd)); + + cmd[0] = htons(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE); + cmd[2] = htons(0x0000U); + cmd[3] = htons(0x00C2U); + cmd[4] = htons(0x4F00U | (uint16_t) test); + + return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL); +} + +static void swap_strings(char *s, size_t len) { + assert((len & 1) == 0); + + for (; len > 0; s += 2, len -= 2) { + char t; + t = s[0]; + s[0] = s[1]; + s[1] = t; + } +} + +static void clean_strings(char *s) { + char *e; + + for (e = s; *e; e++) + if (*e < ' ' || *e >= 127) + *e = ' '; +} + +static void drop_spaces(char *s) { + char *d = s; + SkBool prev_space = FALSE; + + s += strspn(s, " "); + + for (;*s; s++) { + + if (prev_space) { + if (*s != ' ') { + prev_space = FALSE; + *(d++) = ' '; + *(d++) = *s; + } + } else { + if (*s == ' ') + prev_space = TRUE; + else + *(d++) = *s; + } + } + + *d = 0; +} + +static void read_string(char *d, uint8_t *s, size_t len) { + memcpy(d, s, len); + d[len] = 0; + swap_strings(d, len); + clean_strings(d); + drop_spaces(d); +} + +int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) { + assert(d); + assert(ipd); + + if (!d->identify_valid) { + errno = ENOENT; + return -1; + } + + read_string(d->identify_parsed_data.serial, d->identify+20, 20); + read_string(d->identify_parsed_data.firmware, d->identify+46, 8); + read_string(d->identify_parsed_data.model, d->identify+54, 40); + + *ipd = &d->identify_parsed_data; + + return 0; +} + +int sk_disk_smart_is_available(SkDisk *d, SkBool *b) { + assert(d); + assert(b); + + if (!d->identify_valid) { + errno = ENOTSUP; + return -1; + } + + *b = disk_smart_is_available(d); + return 0; +} + +int sk_disk_identify_is_available(SkDisk *d, SkBool *b) { + assert(d); + assert(b); + + *b = d->identify_valid; + return 0; +} + +const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) { + + /* %STRINGPOOLSTART% */ + static const char* const map[] = { + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = ((const char*) 198), + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = ((const char*) 251), + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = ((const char*) 314), + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = ((const char*) 345), + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = ((const char*) 431), + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = ((const char*) 515), + [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = ((const char*) 595) + }; + /* %STRINGPOOLSTOP% */ + + if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX) + return NULL; + + return _P(map[status]); +} + +const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) { + + /* %STRINGPOOLSTART% */ + static const char* const map[] = { + [SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER] = ((const char*) 610), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = ((const char*) 700), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = ((const char*) 747), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL] = ((const char*) 832), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN] = ((const char*) 991), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = ((const char*) 1092), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO] = ((const char*) 1175), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = ((const char*) 1272), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING] = ((const char*) 1349), + [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = ((const char*) 1471) + }; + /* %STRINGPOOLSTOP% */ + + if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX) + return NULL; + + return _P(map[status]); +} + +const char* sk_smart_self_test_to_string(SkSmartSelfTest test) { + + switch (test) { + case SK_SMART_SELF_TEST_SHORT: + return "short"; + case SK_SMART_SELF_TEST_EXTENDED: + return "extended"; + case SK_SMART_SELF_TEST_CONVEYANCE: + return "conveyance"; + case SK_SMART_SELF_TEST_ABORT: + return "abort"; + } + + return NULL; +} + +SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) { + assert(d); + + if (!d->start_test_available) + return FALSE; + + switch (test) { + case SK_SMART_SELF_TEST_SHORT: + case SK_SMART_SELF_TEST_EXTENDED: + return d->short_and_extended_test_available; + case SK_SMART_SELF_TEST_CONVEYANCE: + return d->conveyance_test_available; + case SK_SMART_SELF_TEST_ABORT: + return d->abort_test_available; + default: + return FALSE; + } +} + +unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) { + assert(d); + + if (!sk_smart_self_test_available(d, test)) + return 0; + + switch (test) { + case SK_SMART_SELF_TEST_SHORT: + return d->short_test_polling_minutes; + case SK_SMART_SELF_TEST_EXTENDED: + return d->extended_test_polling_minutes; + case SK_SMART_SELF_TEST_CONVEYANCE: + return d->conveyance_test_polling_minutes; + default: + return 0; + } +} + +static void make_pretty(SkSmartAttributeParsedData *a) { + uint64_t fourtyeight; + + if (!a->name) + return; + + if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN) + return; + + fourtyeight = + ((uint64_t) a->raw[0]) | + (((uint64_t) a->raw[1]) << 8) | + (((uint64_t) a->raw[2]) << 16) | + (((uint64_t) a->raw[3]) << 24) | + (((uint64_t) a->raw[4]) << 32) | + (((uint64_t) a->raw[5]) << 40); + + if (!strcmp(a->name, "spin-up-time")) + a->pretty_value = fourtyeight & 0xFFFF; + else if (!strcmp(a->name, "airflow-temperature-celsius") || + !strcmp(a->name, "temperature-celsius") || + !strcmp(a->name, "temperature-celsius-2")) + a->pretty_value = (fourtyeight & 0xFFFF)*1000 + 273150; + else if (!strcmp(a->name, "temperature-centi-celsius")) + a->pretty_value = (fourtyeight & 0xFFFF)*100 + 273150; + else if (!strcmp(a->name, "power-on-minutes")) + a->pretty_value = fourtyeight * 60 * 1000; + else if (!strcmp(a->name, "power-on-seconds") || + !strcmp(a->name, "power-on-seconds-2")) + a->pretty_value = fourtyeight * 1000; + else if (!strcmp(a->name, "power-on-half-minutes")) + a->pretty_value = fourtyeight * 30 * 1000; + else if (!strcmp(a->name, "power-on-hours") || + !strcmp(a->name, "loaded-hours") || + !strcmp(a->name, "head-flying-hours")) + a->pretty_value = (fourtyeight & 0xFFFFFFFFU) * 60 * 60 * 1000; + else if (!strcmp(a->name, "reallocated-sector-count") || + !strcmp(a->name, "current-pending-sector")) + a->pretty_value = fourtyeight & 0xFFFFFFFFU; + else if (!strcmp(a->name, "endurance-remaining") || + !strcmp(a->name, "available-reserved-space")) + a->pretty_value = a->current_value; + else if (!strcmp(a->name, "total-lbas-written") || + !strcmp(a->name, "total-lbas-read")) + a->pretty_value = fourtyeight * 65536LLU * 512LLU / 1000000LLU; + else if (!strcmp(a->name, "timed-workload-media-wear") || + !strcmp(a->name, "timed-workload-host-reads")) + a->pretty_value = (double)fourtyeight / 1024LLU; + else if (!strcmp(a->name, "workload-timer")) + a->pretty_value = fourtyeight * 60 * 1000; + else + a->pretty_value = fourtyeight; +} + +typedef void (*SkSmartAttributeVerify)(SkDisk *d, SkSmartAttributeParsedData *a); + +typedef struct SkSmartAttributeInfo { + const char *name; + SkSmartAttributeUnit unit; + SkSmartAttributeVerify verify; +} SkSmartAttributeInfo; + +static void verify_temperature(SkDisk *d, SkSmartAttributeParsedData *a) { + assert(a); + assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_MKELVIN); + + if (a->pretty_value < SK_MKELVIN_VALID_MIN || + a->pretty_value > SK_MKELVIN_VALID_MAX) { + a->pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN; + d->attribute_verification_bad = TRUE; + } +} + +static void verify_short_time(SkDisk *d, SkSmartAttributeParsedData *a) { + assert(a); + assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_MSECONDS); + + if (a->pretty_value < SK_MSECOND_VALID_MIN || + a->pretty_value > SK_MSECOND_VALID_SHORT_MAX) { + a->pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN; + d->attribute_verification_bad = TRUE; + } +} + +static void verify_long_time(SkDisk *d, SkSmartAttributeParsedData *a) { + assert(a); + assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_MSECONDS); + + if (a->pretty_value < SK_MSECOND_VALID_MIN || + a->pretty_value > SK_MSECOND_VALID_LONG_MAX) { + a->pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN; + d->attribute_verification_bad = TRUE; + } +} + +static void verify_sectors(SkDisk *d, SkSmartAttributeParsedData *a) { + uint64_t max_sectors; + + assert(d); + assert(a); + assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_SECTORS); + + max_sectors = d->size / 512ULL; + + if (a->pretty_value == 0xffffffffULL || + a->pretty_value == 0xffffffffffffULL || + (max_sectors > 0 && a->pretty_value > max_sectors)) { + a->pretty_value = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN; + d->attribute_verification_bad = TRUE; + } else { + if ((!strcmp(a->name, "reallocated-sector-count") || + !strcmp(a->name, "current-pending-sector")) && + a->pretty_value > 0) + a->warn = TRUE; + } +} + +/* This data is stolen from smartmontools */ + +/* %STRINGPOOLSTART% */ +static const SkSmartAttributeInfo const attribute_info[256] = { + [1] = { ((const char*) 1501), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [2] = { ((const char*) 1521), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [3] = { ((const char*) 3133), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_short_time }, + [4] = { ((const char*) 3146), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [5] = { ((const char*) 1544), SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors }, + [6] = { ((const char*) 1569), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [7] = { ((const char*) 1589), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [8] = { ((const char*) 1605), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [9] = { ((const char*) 1627), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time }, + [10] = { ((const char*) 1642), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [11] = { ((const char*) 1659), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [12] = { ((const char*) 1683), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [13] = { ((const char*) 1701), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [170] = { ((const char*) 3397), SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL }, + [171] = { ((const char*) 1722), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [172] = { ((const char*) 1741), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [175] = { ((const char*) 1758), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [176] = { ((const char*) 1782), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [177] = { ((const char*) 1804), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [178] = { ((const char*) 1824), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [179] = { ((const char*) 1850), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [180] = { ((const char*) 1877), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [181] = { ((const char*) 1900), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [182] = { ((const char*) 1925), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [183] = { ((const char*) 1948), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [184] = { ((const char*) 1972), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [187] = { ((const char*) 1989), SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors }, + [188] = { ((const char*) 2008), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [189] = { ((const char*) 2024), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [190] = { ((const char*) 2040), SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature }, + [191] = { ((const char*) 2068), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [192] = { ((const char*) 2087), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [193] = { ((const char*) 2111), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [194] = { ((const char*) 2128), SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature }, + [195] = { ((const char*) 2150), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [196] = { ((const char*) 2173), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [197] = { ((const char*) 2197), SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors }, + [198] = { ((const char*) 2220), SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors }, + [199] = { ((const char*) 2242), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [200] = { ((const char*) 2263), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [201] = { ((const char*) 2285), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [202] = { ((const char*) 2306), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [203] = { ((const char*) 2324), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [204] = { ((const char*) 2339), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [205] = { ((const char*) 2362), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [206] = { ((const char*) 2384), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [207] = { ((const char*) 2398), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [208] = { ((const char*) 2416), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [209] = { ((const char*) 2426), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [220] = { ((const char*) 2451), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [221] = { ((const char*) 2462), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [222] = { ((const char*) 2483), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time }, + [223] = { ((const char*) 2496), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [224] = { ((const char*) 2513), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [225] = { ((const char*) 2527), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [226] = { ((const char*) 2546), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_short_time }, + [227] = { ((const char*) 2559), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [228] = { ((const char*) 2574), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL }, + [230] = { ((const char*) 2600), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [231] = { ((const char*) 2048), SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature }, + + /* http://www.adtron.com/pdf/SMART_for_XceedLite_SATA_RevA.pdf */ + [232] = { ((const char*) 2615), SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL }, + [233] = { ((const char*) 2635), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + [234] = { ((const char*) 2654), SK_SMART_ATTRIBUTE_UNIT_SECTORS, NULL }, + [235] = { ((const char*) 2678), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL }, + + [240] = { ((const char*) 2694), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time }, + [241] = { ((const char*) 3311), SK_SMART_ATTRIBUTE_UNIT_MB, NULL }, + [242] = { ((const char*) 2712), SK_SMART_ATTRIBUTE_UNIT_MB, NULL }, + [250] = { ((const char*) 2728), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL } +}; +/* %STRINGPOOLSTOP% */ + +typedef enum SkSmartQuirk { + SK_SMART_QUIRK_9_POWERONMINUTES = 0x000001, + SK_SMART_QUIRK_9_POWERONSECONDS = 0x000002, + SK_SMART_QUIRK_9_POWERONHALFMINUTES = 0x000004, + SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 0x000008, + SK_SMART_QUIRK_193_LOADUNLOAD = 0x000010, + SK_SMART_QUIRK_194_10XCELSIUS = 0x000020, + SK_SMART_QUIRK_194_UNKNOWN = 0x000040, + SK_SMART_QUIRK_200_WRITEERRORCOUNT = 0x000080, + SK_SMART_QUIRK_201_DETECTEDTACOUNT = 0x000100, + SK_SMART_QUIRK_5_UNKNOWN = 0x000200, + SK_SMART_QUIRK_9_UNKNOWN = 0x000400, + SK_SMART_QUIRK_197_UNKNOWN = 0x000800, + SK_SMART_QUIRK_198_UNKNOWN = 0x001000, + SK_SMART_QUIRK_190_UNKNOWN = 0x002000, + SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE = 0x004000, + SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR = 0x008000, + SK_SMART_QUIRK_225_TOTALLBASWRITTEN = 0x010000, + SK_SMART_QUIRK_4_UNUSED = 0x020000, + SK_SMART_QUIRK_226_TIMEWORKLOADMEDIAWEAR = 0x040000, + SK_SMART_QUIRK_227_TIMEWORKLOADHOSTREADS = 0x080000, + SK_SMART_QUIRK_228_WORKLOADTIMER = 0x100000, + SK_SMART_QUIRK_3_UNUSED = 0x200000 +} SkSmartQuirk; + +/* %STRINGPOOLSTART% */ +static const char *quirk_name[] = { + ((const char*) 2750), + ((const char*) 2767), + ((const char*) 2784), + ((const char*) 2805), + ((const char*) 2833), + ((const char*) 2848), + ((const char*) 2863), + ((const char*) 2875), + ((const char*) 2895), + ((const char*) 2915), + ((const char*) 2925), + ((const char*) 2935), + ((const char*) 2947), + ((const char*) 2959), + ((const char*) 2971), + ((const char*) 2998), + ((const char*) 3024), + ((const char*) 3045), + ((const char*) 3054), + ((const char*) 3080), + ((const char*) 3106), + ((const char*) 3124), + NULL +}; +/* %STRINGPOOLSTOP% */ + +typedef struct SkSmartQuirkDatabase { + const char *model; + const char *firmware; + SkSmartQuirk quirk; +} SkSmartQuirkDatabase; + +static const SkSmartQuirkDatabase quirk_database[] = { { + + /*** Fujitsu */ + "^(" + "FUJITSU MHY2120BH|" + "FUJITSU MHY2250BH" + ")$", + "^0085000B$", /* seems to be specific to this firmware */ + SK_SMART_QUIRK_9_POWERONMINUTES| + SK_SMART_QUIRK_197_UNKNOWN| + SK_SMART_QUIRK_198_UNKNOWN + }, { + "^FUJITSU MHR2040AT$", + NULL, + SK_SMART_QUIRK_9_POWERONSECONDS| + SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT| + SK_SMART_QUIRK_200_WRITEERRORCOUNT + }, { + "^FUJITSU MHS20[6432]0AT( .)?$", + NULL, + SK_SMART_QUIRK_9_POWERONSECONDS| + SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT| + SK_SMART_QUIRK_200_WRITEERRORCOUNT| + SK_SMART_QUIRK_201_DETECTEDTACOUNT + }, { + "^(" + "FUJITSU M1623TAU|" + "FUJITSU MHG2...ATU?.*|" + "FUJITSU MHH2...ATU?.*|" + "FUJITSU MHJ2...ATU?.*|" + "FUJITSU MHK2...ATU?.*|" + "FUJITSU MHL2300AT|" + "FUJITSU MHM2(20|15|10|06)0AT|" + "FUJITSU MHN2...AT|" + "FUJITSU MHR2020AT|" + "FUJITSU MHT2...(AH|AS|AT|BH)U?.*|" + "FUJITSU MHU2...ATU?.*|" + "FUJITSU MHV2...(AH|AS|AT|BH|BS|BT).*|" + "FUJITSU MP[A-G]3...A[HTEV]U?.*" + ")$", + NULL, + SK_SMART_QUIRK_9_POWERONSECONDS + }, { + + /*** Samsung ***/ + "^(" + "SAMSUNG SV4012H|" + "SAMSUNG SP(0451|08[0124]2|12[0145]3|16[0145]4)[CN]" + ")$", + NULL, + SK_SMART_QUIRK_9_POWERONHALFMINUTES + }, { + "^(" + "SAMSUNG SV0412H|" + "SAMSUNG SV1204H" + ")$", + NULL, + SK_SMART_QUIRK_9_POWERONHALFMINUTES| + SK_SMART_QUIRK_194_10XCELSIUS + }, { + "^SAMSUNG SP40A2H$", + "^RR100-07$", + SK_SMART_QUIRK_9_POWERONHALFMINUTES + }, { + "^SAMSUNG SP80A4H$", + "^RT100-06$", + SK_SMART_QUIRK_9_POWERONHALFMINUTES + }, { + "^SAMSUNG SP8004H$", + "^QW100-61$", + SK_SMART_QUIRK_9_POWERONHALFMINUTES + }, { + + /*** Maxtor */ + "^(" + "Maxtor 2B0(0[468]|1[05]|20)H1|" + "Maxtor 4G(120J6|160J[68])|" + "Maxtor 4D0(20H1|40H2|60H3|80H4)" + ")$", + NULL, + SK_SMART_QUIRK_9_POWERONMINUTES| + SK_SMART_QUIRK_194_UNKNOWN + }, { + "^(" + "Maxtor 2F0[234]0[JL]0|" + "Maxtor 8(1280A2|2160A4|2560A4|3840A6|4000A6|5120A8)|" + "Maxtor 8(2160D2|3228D3|3240D3|4320D4|6480D6|8400D8|8455D8)|" + "Maxtor 9(0510D4|0576D4|0648D5|0720D5|0840D6|0845D6|0864D6|1008D7|1080D8|1152D8)|" + "Maxtor 9(1(360|350|202)D8|1190D7|10[12]0D6|0840D5|06[48]0D4|0510D3|1(350|202)E8|1010E6|0840E5|0640E4)|" + "Maxtor 9(0512D2|0680D3|0750D3|0913D4|1024D4|1360D6|1536D6|1792D7|2048D8)|" + "Maxtor 9(2732U8|2390U7|204[09]U6|1707U5|1366U4|1024U3|0845U3|0683U2)|" + "Maxtor 4(R0[68]0[JL]0|R1[26]0L0|A160J0|R120L4)|" + "Maxtor (91728D8|91512D7|91303D6|91080D5|90845D4|90645D3|90648D[34]|90432D2)|" + "Maxtor 9(0431U1|0641U2|0871U2|1301U3|1741U4)|" + "Maxtor (94091U8|93071U6|92561U5|92041U4|91731U4|91531U3|91361U3|91021U2|90841U2|90651U2)|" + "Maxtor (33073U4|32049U3|31536U2|30768U1|33073H4|32305H3|31536H2|30768H1)|" + "Maxtor (93652U8|92739U6|91826U4|91369U3|90913U2|90845U2|90435U1)|" + "Maxtor 9(0684U2|1024U2|1362U3|1536U3|2049U4|2562U5|3073U6|4098U8)|" + "Maxtor (54098[UH]8|53073[UH]6|52732[UH]6|52049[UH]4|51536[UH]3|51369[UH]3|51024[UH]2)|" + "Maxtor 3(1024H1|1535H2|2049H2|3073H3|4098H4)( B)?|" + "Maxtor 5(4610H6|4098H6|3073H4|2049H3|1536H2|1369H2|1023H2)|" + "Maxtor 9(1023U2|1536U2|2049U3|2305U3|3073U4|4610U6|6147U8)|" + "Maxtor 9(1023H2|1536H2|2049H3|2305H3|3073H4|4098H6|4610H6|6147H8)|" + "Maxtor 5T0(60H6|40H4|30H3|20H2|10H1)|" + "Maxtor (98196H8|96147H6)|" + "Maxtor 4W(100H6|080H6|060H4|040H3|030H2)|" + "Maxtor 6(E0[234]|K04)0L0|" + "Maxtor 6(B(30|25|20|16|12|10|08)0[MPRS]|L(080[MLP]|(100|120)[MP]|160[MP]|200[MPRS]|250[RS]|300[RS]))0|" + "Maxtor 6Y((060|080|120|160)L0|(060|080|120|160|200|250)P0|(060|080|120|160|200|250)M0)|" + "Maxtor 7Y250[PM]0|" + "Maxtor [45]A(25|30|32)0[JN]0|" + "Maxtor 7L(25|30)0[SR]0" + ")$", + NULL, + SK_SMART_QUIRK_9_POWERONMINUTES + }, { + + + /*** Hitachi */ + "^(" + "HITACHI_DK14FA-20B|" + "HITACHI_DK23..-..B?|" + "HITACHI_DK23FA-20J|HTA422020F9AT[JN]0|" + "HE[JN]4230[23]0F9AT00|" + "HTC4260[23]0G5CE00|HTC4260[56]0G8CE00" + ")$", + NULL, + SK_SMART_QUIRK_9_POWERONMINUTES| + SK_SMART_QUIRK_193_LOADUNLOAD + }, { + "^HTS541010G9SA00$", + "^MBZOC60P$", + SK_SMART_QUIRK_5_UNKNOWN + }, { + + /*** Apple SSD (?) http://bugs.freedesktop.org/show_bug.cgi?id=24700 + https://bugs.launchpad.net/ubuntu/+source/gnome-disk-utility/+bug/438136/comments/4 */ + "^MCCOE64GEMPP$", + "^2.9.0[3-9]$", + SK_SMART_QUIRK_5_UNKNOWN| + SK_SMART_QUIRK_190_UNKNOWN + }, { + + /*** Intel */ + "^INTEL SSDSA2(CT|BT|CW|BW)[0-9]{3}G3.*$", /* 320 Series */ + NULL, + SK_SMART_QUIRK_3_UNUSED| + SK_SMART_QUIRK_4_UNUSED| + SK_SMART_QUIRK_225_TOTALLBASWRITTEN| + SK_SMART_QUIRK_226_TIMEWORKLOADMEDIAWEAR| + SK_SMART_QUIRK_227_TIMEWORKLOADHOSTREADS| + SK_SMART_QUIRK_228_WORKLOADTIMER| + SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE| + SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR + }, { + NULL, + NULL, + 0 + } +}; + +static int match(const char*regex, const char *s, SkBool *result) { + int k; + regex_t re; + + *result = FALSE; + + if (regcomp(&re, regex, REG_EXTENDED|REG_NOSUB) != 0) { + errno = EINVAL; + return -1; + } + + if ((k = regexec(&re, s, 0, NULL, 0)) != 0) { + + if (k != REG_NOMATCH) { + regfree(&re); + errno = EINVAL; + return -1; + } + + } else + *result = TRUE; + + regfree(&re); + + return 0; +} + +static int lookup_quirks(const char *model, const char *firmware, SkSmartQuirk *quirk) { + int k; + const SkSmartQuirkDatabase *db; + + *quirk = 0; + + for (db = quirk_database; db->model || db->firmware; db++) { + + if (db->model) { + SkBool matching = FALSE; + + if ((k = match(db->model, model, &matching)) < 0) + return k; + + if (!matching) + continue; + } + + if (db->firmware) { + SkBool matching = FALSE; + + if ((k = match(db->firmware, firmware, &matching)) < 0) + return k; + + if (!matching) + continue; + } + + *quirk = db->quirk; + return 0; + } + + return 0; +} + +static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) { + const SkIdentifyParsedData *ipd; + SkSmartQuirk quirk = 0; + + /* These are the complex ones */ + if (sk_disk_identify_parse(d, &ipd) < 0) + return NULL; + + if (lookup_quirks(ipd->model, ipd->firmware, &quirk) < 0) + return NULL; + + if (quirk) { + switch (id) { + case 3: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_3_UNUSED) { + static const SkSmartAttributeInfo a = { + ((const char*) 3133), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 4: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_4_UNUSED) { + static const SkSmartAttributeInfo a = { + ((const char*) 3146), SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 5: + if (quirk & SK_SMART_QUIRK_5_UNKNOWN) + return NULL; + + break; + + case 9: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_9_POWERONMINUTES) { + static const SkSmartAttributeInfo a = { + ((const char*) 3163), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time + }; + return &a; + + } else if (quirk & SK_SMART_QUIRK_9_POWERONSECONDS) { + static const SkSmartAttributeInfo a = { + ((const char*) 3180), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time + }; + return &a; + + } else if (quirk & SK_SMART_QUIRK_9_POWERONHALFMINUTES) { + static const SkSmartAttributeInfo a = { + ((const char*) 3197), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time + }; + return &a; + } else if (quirk & SK_SMART_QUIRK_9_UNKNOWN) + return NULL; + /* %STRINGPOOLSTOP% */ + + break; + + case 190: + if (quirk & SK_SMART_QUIRK_190_UNKNOWN) + return NULL; + + break; + + case 192: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT) { + static const SkSmartAttributeInfo a = { + ((const char*) 3219), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 194: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_194_10XCELSIUS) { + static const SkSmartAttributeInfo a = { + ((const char*) 3249), SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature + }; + return &a; + } else if (quirk & SK_SMART_QUIRK_194_UNKNOWN) + return NULL; + /* %STRINGPOOLSTOP% */ + + break; + + case 197: + if (quirk & SK_SMART_QUIRK_197_UNKNOWN) + return NULL; + + break; + + case 198: + if (quirk & SK_SMART_QUIRK_198_UNKNOWN) + return NULL; + + break; + + case 200: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_200_WRITEERRORCOUNT) { + static const SkSmartAttributeInfo a = { + ((const char*) 3275), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 201: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_201_DETECTEDTACOUNT) { + static const SkSmartAttributeInfo a = { + ((const char*) 3293), SK_SMART_ATTRIBUTE_UNIT_NONE, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 225: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_225_TOTALLBASWRITTEN) { + static const SkSmartAttributeInfo a = { + ((const char*) 3311), SK_SMART_ATTRIBUTE_UNIT_MB, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 226: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_226_TIMEWORKLOADMEDIAWEAR) { + static const SkSmartAttributeInfo a = { + ((const char*) 3330), SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 227: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_227_TIMEWORKLOADHOSTREADS) { + static const SkSmartAttributeInfo a = { + ((const char*) 3356), SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 228: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_228_WORKLOADTIMER) { + static const SkSmartAttributeInfo a = { + ((const char*) 3382), SK_SMART_ATTRIBUTE_UNIT_MSECONDS, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + + break; + + case 232: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE) { + static const SkSmartAttributeInfo a = { + ((const char*) 3397), SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + break; + + case 233: + /* %STRINGPOOLSTART% */ + if (quirk & SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR) { + static const SkSmartAttributeInfo a = { + ((const char*) 3422), SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL + }; + return &a; + } + /* %STRINGPOOLSTOP% */ + break; + + } + } + + /* These are the simple cases */ + if (attribute_info[id].name) + return &attribute_info[id]; + + return NULL; +} + +int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) { + + if (!d->smart_data_valid) { + errno = ENOENT; + return -1; + } + + switch (d->smart_data[362]) { + case 0x00: + case 0x80: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER; + break; + + case 0x02: + case 0x82: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS; + break; + + case 0x03: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS; + break; + + case 0x04: + case 0x84: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED; + break; + + case 0x05: + case 0x85: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED; + break; + + case 0x06: + case 0x86: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL; + break; + + default: + d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN; + break; + } + + d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF); + d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF; + + d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8); + + d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d); + d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d); + d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d); + d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d); + + d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372]; + d->smart_parsed_data.extended_test_polling_minutes = d->smart_data[373] != 0xFF ? d->smart_data[373] : ((uint16_t) d->smart_data[376] << 8 | (uint16_t) d->smart_data[375]); + d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374]; + + *spd = &d->smart_parsed_data; + + return 0; +} + +static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) { + uint8_t *p; + unsigned n; + + if (!d->smart_thresholds_valid) + goto fail; + + for (n = 0, p = d->smart_thresholds+2; n < 30; n++, p+=12) + if (p[0] == a->id) + break; + + if (n >= 30) + goto fail; + + a->threshold = p[1]; + a->threshold_valid = p[1] != 0xFE; + + a->good_now_valid = FALSE; + a->good_now = TRUE; + a->good_in_the_past_valid = FALSE; + a->good_in_the_past = TRUE; + + /* Always-Fail and Always-Passing thresholds are not relevant + * for our assessment. */ + if (p[1] >= 1 && p[1] <= 0xFD) { + + if (a->worst_value_valid) { + a->good_in_the_past = a->good_in_the_past && (a->worst_value > a->threshold); + a->good_in_the_past_valid = TRUE; + } + + if (a->current_value_valid) { + a->good_now = a->good_now && (a->current_value > a->threshold); + a->good_now_valid = TRUE; + } + } + + a->warn = + (a->good_now_valid && !a->good_now) || + (a->good_in_the_past_valid && !a->good_in_the_past); + + return; + +fail: + a->threshold_valid = FALSE; + a->good_now_valid = FALSE; + a->good_in_the_past_valid = FALSE; + a->warn = FALSE; +} + +int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) { + uint8_t *p; + unsigned n; + + if (!d->smart_data_valid) { + errno = ENOENT; + return -1; + } + + for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) { + SkSmartAttributeParsedData a; + const SkSmartAttributeInfo *i; + char *an = NULL; + + if (p[0] == 0) + continue; + + memset(&a, 0, sizeof(a)); + a.id = p[0]; + a.current_value = p[3]; + a.current_value_valid = p[3] >= 1 && p[3] <= 0xFD; + a.worst_value = p[4]; + a.worst_value_valid = p[4] >= 1 && p[4] <= 0xFD; + + a.flags = ((uint16_t) p[2] << 8) | p[1]; + a.prefailure = !!(p[1] & 1); + a.online = !!(p[1] & 2); + + memcpy(a.raw, p+5, 6); + + if ((i = lookup_attribute(d, p[0]))) { + a.name = _P(i->name); + a.pretty_unit = i->unit; + } else { + if (asprintf(&an, "attribute-%u", a.id) < 0) { + errno = ENOMEM; + return -1; + } + + a.name = an; + a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN; + } + + make_pretty(&a); + + find_threshold(d, &a); + + if (i && i->verify) + i->verify(d, &a); + + cb(d, &a, userdata); + free(an); + } + + return 0; +} + +static const char *yes_no(SkBool b) { + return b ? "yes" : "no"; +} + +const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) { + + /* %STRINGPOOLSTART% */ + const char * const map[] = { + [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL, + [SK_SMART_ATTRIBUTE_UNIT_NONE] = ((const char*) 30), + [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = ((const char*) 3446), + [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = ((const char*) 3449), + [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = ((const char*) 3457), + [SK_SMART_ATTRIBUTE_UNIT_PERCENT] = ((const char*) 3460), + [SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT] = ((const char*) 3460), + [SK_SMART_ATTRIBUTE_UNIT_MB] = ((const char*) 3462) + }; + /* %STRINGPOOLSTOP% */ + + if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX) + return NULL; + + return _P(map[unit]); +} + +struct attr_helper { + uint64_t *value; + SkBool found; +}; + +static void temperature_cb(SkDisk *d, const SkSmartAttributeParsedData *a, struct attr_helper *ah) { + + if (a->pretty_unit != SK_SMART_ATTRIBUTE_UNIT_MKELVIN) + return; + + if (!strcmp(a->name, "temperature-centi-celsius") || + !strcmp(a->name, "temperature-celsius") || + !strcmp(a->name, "temperature-celsius-2") || + !strcmp(a->name, "airflow-temperature-celsius")) { + + if (!ah->found || a->pretty_value > *ah->value) + *ah->value = a->pretty_value; + + ah->found = TRUE; + } +} + +int sk_disk_smart_get_temperature(SkDisk *d, uint64_t *kelvin) { + struct attr_helper ah; + + assert(d); + assert(kelvin); + + ah.found = FALSE; + ah.value = kelvin; + + if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) temperature_cb, &ah) < 0) + return -1; + + if (!ah.found) { + errno = ENOENT; + return -1; + } + + return 0; +} + +static void power_on_cb(SkDisk *d, const SkSmartAttributeParsedData *a, struct attr_helper *ah) { + + if (a->pretty_unit != SK_SMART_ATTRIBUTE_UNIT_MSECONDS) + return; + + if (!strcmp(a->name, "power-on-minutes") || + !strcmp(a->name, "power-on-seconds") || + !strcmp(a->name, "power-on-seconds-2") || + !strcmp(a->name, "power-on-half-minutes") || + !strcmp(a->name, "power-on-hours")) { + + if (!ah->found || a->pretty_value > *ah->value) + *ah->value = a->pretty_value; + + ah->found = TRUE; + } +} + +int sk_disk_smart_get_power_on(SkDisk *d, uint64_t *mseconds) { + struct attr_helper ah; + + assert(d); + assert(mseconds); + + ah.found = FALSE; + ah.value = mseconds; + + if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) power_on_cb, &ah) < 0) + return -1; + + if (!ah.found) { + errno = ENOENT; + return -1; + } + + return 0; +} + +static void power_cycle_cb(SkDisk *d, const SkSmartAttributeParsedData *a, struct attr_helper *ah) { + + if (a->pretty_unit != SK_SMART_ATTRIBUTE_UNIT_NONE) + return; + + if (!strcmp(a->name, "power-cycle-count")) { + + if (!ah->found || a->pretty_value > *ah->value) + *ah->value = a->pretty_value; + + ah->found = TRUE; + } +} + +int sk_disk_smart_get_power_cycle(SkDisk *d, uint64_t *count) { + struct attr_helper ah; + + assert(d); + assert(count); + + ah.found = FALSE; + ah.value = count; + + if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) power_cycle_cb, &ah) < 0) + return -1; + + if (!ah.found) { + errno = ENOENT; + return -1; + } + + return 0; +} + +static void fill_cache_cb(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) { + + if (a->prefailure) { + if (a->good_now_valid && !a->good_now) + d->bad_attribute_now = TRUE; + + if (a->good_in_the_past_valid && !a->good_in_the_past) + d->bad_attribute_in_the_past = TRUE; + } + + if (a->pretty_unit != SK_SMART_ATTRIBUTE_UNIT_SECTORS) + return; + + if (!strcmp(a->name, "reallocated-sector-count")) { + if (a->pretty_value > d->reallocated_sector_count) + d->reallocated_sector_count = a->pretty_value; + d->reallocated_sector_count_found = TRUE; + } + + if (!strcmp(a->name, "current-pending-sector")) { + if (a->pretty_value > d->current_pending_sector) + d->current_pending_sector = a->pretty_value; + d->current_pending_sector_found = TRUE; + } +} + +static int fill_cache(SkDisk *d) { + if (d->attribute_cache_valid) + return 0; + + if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) fill_cache_cb, NULL) >= 0) { + d->attribute_cache_valid = TRUE; + return 0; + } else + return -1; +} + +int sk_disk_smart_get_bad(SkDisk *d, uint64_t *sectors) { + assert(d); + assert(sectors); + + if (fill_cache (d) < 0) + return -1; + + if (!d->reallocated_sector_count_found && !d->current_pending_sector_found) { + errno = ENOENT; + return -1; + } + + if (d->reallocated_sector_count_found && d->current_pending_sector_found) + *sectors = d->reallocated_sector_count + d->current_pending_sector; + else if (d->reallocated_sector_count_found) + *sectors = d->reallocated_sector_count; + else + *sectors = d->current_pending_sector; + + return 0; +} + +const char* sk_smart_overall_to_string(SkSmartOverall overall) { + + /* %STRINGPOOLSTART% */ + const char * const map[] = { + [SK_SMART_OVERALL_GOOD] = ((const char*) 3465), + [SK_SMART_OVERALL_BAD_ATTRIBUTE_IN_THE_PAST] = ((const char*) 3470), + [SK_SMART_OVERALL_BAD_SECTOR] = ((const char*) 3496), + [SK_SMART_OVERALL_BAD_ATTRIBUTE_NOW] = ((const char*) 3507), + [SK_SMART_OVERALL_BAD_SECTOR_MANY] = ((const char*) 3525), + [SK_SMART_OVERALL_BAD_STATUS] = ((const char*) 3541), + }; + /* %STRINGPOOLSTOP% */ + + if (overall >= _SK_SMART_OVERALL_MAX) + return NULL; + + return _P(map[overall]); +} + +static uint64_t u64log2(uint64_t n) { + unsigned r; + + if (n <= 1) + return 0; + + r = 0; + for (;;) { + n = n >> 1; + if (!n) + return r; + r++; + } +} + +int sk_disk_smart_get_overall(SkDisk *d, SkSmartOverall *overall) { + SkBool good; + uint64_t sectors, sector_threshold; + + assert(d); + assert(overall); + + /* First, check SMART self-assesment */ + if (sk_disk_smart_status(d, &good) < 0) + return -1; + + if (!good) { + *overall = SK_SMART_OVERALL_BAD_STATUS; + return 0; + } + + /* Second, check if the number of bad sectors is greater than + * a certain threshold */ + if (sk_disk_smart_get_bad(d, §ors) < 0) { + if (errno != ENOENT) + return -1; + sectors = 0; + } else { + + /* We use log2(n_sectors)*1024 as a threshold here. We + * had to pick something, and this makes a bit of + * sense, or doesn't it? */ + sector_threshold = u64log2(d->size/512) * 1024; + + if (sectors >= sector_threshold) { + *overall = SK_SMART_OVERALL_BAD_SECTOR_MANY; + return 0; + } + } + + /* Third, check if any of the SMART attributes is bad */ + if (fill_cache (d) < 0) + return -1; + + if (d->bad_attribute_now) { + *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE_NOW; + return 0; + } + + /* Fourth, check if there are any bad sectors at all */ + if (sectors > 0) { + *overall = SK_SMART_OVERALL_BAD_SECTOR; + return 0; + } + + /* Fifth, check if any of the SMART attributes ever was bad */ + if (d->bad_attribute_in_the_past) { + *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE_IN_THE_PAST; + return 0; + } + + /* Sixth, there's really nothing to complain about, so give it a pass */ + *overall = SK_SMART_OVERALL_GOOD; + return 0; +} + +static char* print_name(char *s, size_t len, uint8_t id, const char *k) { + + if (k) + strncpy(s, k, len); + else + snprintf(s, len, "%u", id); + + s[len-1] = 0; + + return s; +} + +static char *print_value(char *s, size_t len, uint64_t pretty_value, SkSmartAttributeUnit pretty_unit) { + + switch (pretty_unit) { + case SK_SMART_ATTRIBUTE_UNIT_MSECONDS: + + if (pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU) + snprintf(s, len, "%0.1f years", ((double) pretty_value)/(1000.0*60*60*24*365)); + else if (pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU) + snprintf(s, len, "%0.1f months", ((double) pretty_value)/(1000.0*60*60*24*30)); + else if (pretty_value >= 1000LLU*60LLU*60LLU*24LLU) + snprintf(s, len, "%0.1f days", ((double) pretty_value)/(1000.0*60*60*24)); + else if (pretty_value >= 1000LLU*60LLU*60LLU) + snprintf(s, len, "%0.1f h", ((double) pretty_value)/(1000.0*60*60)); + else if (pretty_value >= 1000LLU*60LLU) + snprintf(s, len, "%0.1f min", ((double) pretty_value)/(1000.0*60)); + else if (pretty_value >= 1000LLU) + snprintf(s, len, "%0.1f s", ((double) pretty_value)/(1000.0)); + else + snprintf(s, len, "%llu ms", (unsigned long long) pretty_value); + + break; + + case SK_SMART_ATTRIBUTE_UNIT_MKELVIN: + snprintf(s, len, "%0.1f C", ((double) pretty_value - 273150) / 1000); + break; + + case SK_SMART_ATTRIBUTE_UNIT_SECTORS: + snprintf(s, len, "%llu sectors", (unsigned long long) pretty_value); + break; + + case SK_SMART_ATTRIBUTE_UNIT_PERCENT: + snprintf(s, len, "%llu%%", (unsigned long long) pretty_value); + break; + + case SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT: + snprintf(s, len, "%0.3f%%", (double) pretty_value); + break; + + case SK_SMART_ATTRIBUTE_UNIT_MB: + if (pretty_value >= 1000000LLU) + snprintf(s, len, "%0.3f TB", (double) pretty_value / 1000000LLU); + else if (pretty_value >= 1000LLU) + snprintf(s, len, "%0.3f GB", (double) pretty_value / 1000LLU); + else + snprintf(s, len, "%llu MB", (unsigned long long) pretty_value); + break; + + case SK_SMART_ATTRIBUTE_UNIT_NONE: + snprintf(s, len, "%llu", (unsigned long long) pretty_value); + break; + + case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN: + snprintf(s, len, "n/a"); + break; + + case _SK_SMART_ATTRIBUTE_UNIT_MAX: + assert(FALSE); + } + + s[len-1] = 0; + + return s; +} + +#define HIGHLIGHT "\x1B[1m" +#define ENDHIGHLIGHT "\x1B[0m" + +static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) { + char name[32]; + char pretty[32]; + char tt[32], tw[32], tc[32]; + SkBool highlight; + + snprintf(tt, sizeof(tt), "%3u", a->threshold); + tt[sizeof(tt)-1] = 0; + snprintf(tw, sizeof(tw), "%3u", a->worst_value); + tw[sizeof(tw)-1] = 0; + snprintf(tc, sizeof(tc), "%3u", a->current_value); + tc[sizeof(tc)-1] = 0; + + highlight = a->warn && isatty(1); + + if (highlight) + fprintf(stderr, HIGHLIGHT); + + printf("%3u %-27s %-3s %-3s %-3s %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-4s %-4s\n", + a->id, + print_name(name, sizeof(name), a->id, a->name), + a->current_value_valid ? tc : "n/a", + a->worst_value_valid ? tw : "n/a", + a->threshold_valid ? tt : "n/a", + print_value(pretty, sizeof(pretty), a->pretty_value, a->pretty_unit), + a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5], + a->prefailure ? "prefail" : "old-age", + a->online ? "online" : "offline", + a->good_now_valid ? yes_no(a->good_now) : "n/a", + a->good_in_the_past_valid ? yes_no(a->good_in_the_past) : "n/a"); + + if (highlight) + fprintf(stderr, ENDHIGHLIGHT); +} + +int sk_disk_dump(SkDisk *d) { + int ret; + SkBool awake = FALSE; + uint64_t size; + + assert(d); + + printf("Device: %s%s%s\n" + "Type: %s\n", + d->name ? disk_type_to_prefix_string(d->type) : "", + d->name ? ":" : "", + d->name ? d->name : "n/a", + disk_type_to_human_string(d->type)); + + ret = sk_disk_get_size(d, &size); + if (ret >= 0) + printf("Size: %lu MiB\n", (unsigned long) (d->size/1024/1024)); + else + printf("Size: %s\n", strerror(errno)); + + if (d->identify_valid) { + const SkIdentifyParsedData *ipd; + SkSmartQuirk quirk = 0; + unsigned i; + + if ((ret = sk_disk_identify_parse(d, &ipd)) < 0) + return ret; + + printf("Model: [%s]\n" + "Serial: [%s]\n" + "Firmware: [%s]\n" + "SMART Available: %s\n", + ipd->model, + ipd->serial, + ipd->firmware, + yes_no(disk_smart_is_available(d))); + + if ((ret = lookup_quirks(ipd->model, ipd->firmware, &quirk))) + return ret; + + printf("Quirks:"); + + for (i = 0; quirk_name[i]; i++) + if (quirk & (1<= 0 ? yes_no(awake) : strerror(errno)); + + if (disk_smart_is_available(d)) { + SkSmartOverall overall; + const SkSmartParsedData *spd; + SkBool good; + char pretty[32]; + uint64_t value, power_on; + + ret = sk_disk_smart_status(d, &good); + printf("%sSMART Disk Health Good: %s%s\n", + ret >= 0 && !good ? HIGHLIGHT : "", + ret >= 0 ? yes_no(good) : strerror(errno), + ret >= 0 && !good ? ENDHIGHLIGHT : ""); + if ((ret = sk_disk_smart_read_data(d)) < 0) + return ret; + + if ((ret = sk_disk_smart_parse(d, &spd)) < 0) + return ret; + + printf("Off-line Data Collection Status: [%s]\n" + "Total Time To Complete Off-Line Data Collection: %u s\n" + "Self-Test Execution Status: [%s]\n" + "Percent Self-Test Remaining: %u%%\n" + "Conveyance Self-Test Available: %s\n" + "Short/Extended Self-Test Available: %s\n" + "Start Self-Test Available: %s\n" + "Abort Self-Test Available: %s\n" + "Short Self-Test Polling Time: %u min\n" + "Extended Self-Test Polling Time: %u min\n" + "Conveyance Self-Test Polling Time: %u min\n", + sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status), + spd->total_offline_data_collection_seconds, + sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status), + spd->self_test_execution_percent_remaining, + yes_no(spd->conveyance_test_available), + yes_no(spd->short_and_extended_test_available), + yes_no(spd->start_test_available), + yes_no(spd->abort_test_available), + spd->short_test_polling_minutes, + spd->extended_test_polling_minutes, + spd->conveyance_test_polling_minutes); + + if (sk_disk_smart_get_bad(d, &value) < 0) + printf("Bad Sectors: %s\n", strerror(errno)); + else + printf("%sBad Sectors: %s%s\n", + value > 0 ? HIGHLIGHT : "", + print_value(pretty, sizeof(pretty), value, SK_SMART_ATTRIBUTE_UNIT_SECTORS), + value > 0 ? ENDHIGHLIGHT : ""); + + if (sk_disk_smart_get_power_on(d, &power_on) < 0) { + printf("Powered On: %s\n", strerror(errno)); + power_on = 0; + } else + printf("Powered On: %s\n", print_value(pretty, sizeof(pretty), power_on, SK_SMART_ATTRIBUTE_UNIT_MSECONDS)); + + if (sk_disk_smart_get_power_cycle(d, &value) < 0) + printf("Power Cycles: %s\n", strerror(errno)); + else { + printf("Power Cycles: %llu\n", (unsigned long long) value); + + if (value > 0 && power_on > 0) + printf("Average Powered On Per Power Cycle: %s\n", print_value(pretty, sizeof(pretty), power_on/value, SK_SMART_ATTRIBUTE_UNIT_MSECONDS)); + } + + if (sk_disk_smart_get_temperature(d, &value) < 0) + printf("Temperature: %s\n", strerror(errno)); + else + printf("Temperature: %s\n", print_value(pretty, sizeof(pretty), value, SK_SMART_ATTRIBUTE_UNIT_MKELVIN)); + + printf("Attribute Parsing Verification: %s\n", + d->attribute_verification_bad ? "Bad" : "Good"); + + if (sk_disk_smart_get_overall(d, &overall) < 0) + printf("Overall Status: %s\n", strerror(errno)); + else + printf("%sOverall Status: %s%s\n", + overall != SK_SMART_OVERALL_GOOD ? HIGHLIGHT : "", + sk_smart_overall_to_string(overall), + overall != SK_SMART_OVERALL_GOOD ? ENDHIGHLIGHT : ""); + + printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-4s %-4s\n", + "ID#", + "Name", + "Value", + "Worst", + "Thres", + "Pretty", + "Raw", + "Type", + "Updates", + "Good", + "Good/Past"); + + if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0) + return ret; + } else + printf("ATA SMART not supported.\n"); + + return 0; +} + +int sk_disk_get_size(SkDisk *d, uint64_t *bytes) { + assert(d); + assert(bytes); + + if (d->size == (uint64_t) -1) { + errno = ENODATA; + return -1; + } + + *bytes = d->size; + return 0; +} + +static int disk_find_type(SkDisk *d, dev_t devnum) { + struct udev *udev; + struct udev_device *dev = NULL, *usb; + int r = -1; + const char *a; + + assert(d); + + if (!(udev = udev_new())) { + errno = ENXIO; + goto finish; + } + + if (!(dev = udev_device_new_from_devnum(udev, 'b', devnum))) { + errno = ENODEV; + goto finish; + } + + if ((a = udev_device_get_property_value(dev, "ID_ATA_SMART_ACCESS"))) { + unsigned u; + + for (u = 0; u < _SK_DISK_TYPE_MAX; u++) { + const char *t; + + if (!(t = disk_type_to_prefix_string(u))) + continue; + + if (!strcmp(a, t)) { + d->type = u; + r = 0; + goto finish; + } + } + + d->type = SK_DISK_TYPE_NONE; + r = 0; + goto finish; + } + + if ((usb = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"))) { + const char *product, *vendor; + uint32_t pid, vid; + + if (!(product = udev_device_get_sysattr_value(usb, "idProduct")) || + sscanf(product, "%04x", &pid) != 1) { + errno = ENODEV; + goto finish; + } + + if (!(vendor = udev_device_get_sysattr_value(usb, "idVendor")) || + sscanf(vendor, "%04x", &vid) != 1) { + errno = ENODEV; + goto finish; + } + + if ((vid == 0x0928 && pid == 0x0000)) + /* This Oxford Semiconductor bridge seems to + * choke on SAT commands. Let's explicitly + * black list it here. + * + * http://bugs.freedesktop.org/show_bug.cgi?id=24951 */ + d->type = SK_DISK_TYPE_NONE; + else if ((vid == 0x152d && pid == 0x2329) || + (vid == 0x152d && pid == 0x2338) || + (vid == 0x152d && pid == 0x2339)) + /* Some JMicron bridges seem to choke on SMART + * commands, so let's explicitly black list + * them here. + * + * https://bugzilla.redhat.com/show_bug.cgi?id=515881 + * + * At least some of the JMicron bridges with + * these vids/pids choke on the jmicron access + * mode. To make sure we don't break things + * for people we now disable this by + * default. */ + d->type = SK_DISK_TYPE_NONE; + else if ((vid == 0x152d && pid == 0x2336)) + /* This JMicron bridge seems to always work + * with SMART commands send with the jmicron + * access mode. */ + d->type = SK_DISK_TYPE_JMICRON; + else if ((vid == 0x0c0b && pid == 0xb159) || + (vid == 0x04fc && pid == 0x0c25) || + (vid == 0x04fc && pid == 0x0c15)) + d->type = SK_DISK_TYPE_SUNPLUS; + else + d->type = SK_DISK_TYPE_ATA_PASSTHROUGH_12; + + } else if (udev_device_get_parent_with_subsystem_devtype(dev, "ide", NULL)) + d->type = SK_DISK_TYPE_LINUX_IDE; + else if (udev_device_get_parent_with_subsystem_devtype(dev, "scsi", NULL)) + d->type = SK_DISK_TYPE_ATA_PASSTHROUGH_16; + else + d->type = SK_DISK_TYPE_AUTO; + + r = 0; + +finish: + if (dev) + udev_device_unref(dev); + + if (udev) + udev_unref(udev); + + return r; +} + +static int init_smart(SkDisk *d) { + /* We don't do the SMART initialization right-away, since some + * drivers spin up when we do that */ + + int ret; + + if (d->smart_initialized) + return 0; + + d->smart_initialized = TRUE; + + /* Check if driver can do SMART, and enable if necessary */ + if (!disk_smart_is_available(d)) + return 0; + + if (!disk_smart_is_enabled(d)) { + if ((ret = disk_smart_enable(d, TRUE)) < 0) + goto fail; + + if ((ret = disk_identify_device(d)) < 0) + goto fail; + + if (!disk_smart_is_enabled(d)) { + errno = EIO; + ret = -1; + goto fail; + } + } + + disk_smart_read_thresholds(d); + ret = 0; + +fail: + return ret; +} + +int sk_disk_open(const char *name, SkDisk **_d) { + SkDisk *d; + int ret = -1; + struct stat st; + + assert(_d); + + if (!(d = calloc(1, sizeof(SkDisk)))) { + errno = ENOMEM; + goto fail; + } + + d->fd = -1; + d->size = (uint64_t) -1; + + if (!name) + d->type = SK_DISK_TYPE_BLOB; + else { + const char *dn; + + d->type = SK_DISK_TYPE_AUTO; + + if (!(dn = disk_type_from_string(name, &d->type))) + dn = name; + + if (!(d->name = strdup(dn))) { + errno = ENOMEM; + goto fail; + } + + if ((d->fd = open(d->name, + O_RDONLY|O_NOCTTY|O_NONBLOCK +#ifdef O_CLOEXEC + |O_CLOEXEC +#endif + + )) < 0) { + ret = d->fd; + goto fail; + } + + if ((ret = fstat(d->fd, &st)) < 0) + goto fail; + + if (!S_ISBLK(st.st_mode)) { + errno = ENODEV; + ret = -1; + goto fail; + } + + /* So, it's a block device. Let's make sure the ioctls work */ + if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0) + goto fail; + + if (d->size <= 0 || d->size == (uint64_t) -1) { + errno = EIO; + ret = -1; + goto fail; + } + + /* OK, it's a real block device with a size. Now let's find the suitable API */ + if (d->type == SK_DISK_TYPE_AUTO) + if ((ret = disk_find_type(d, st.st_rdev)) < 0) + goto fail; + + if (d->type == SK_DISK_TYPE_AUTO) { + /* We have no clue, so let's autotest for a working API */ + for (d->type = 0; d->type < _SK_DISK_TYPE_TEST_MAX; d->type++) + if (disk_identify_device(d) >= 0) + break; + if (d->type >= _SK_DISK_TYPE_TEST_MAX) + d->type = SK_DISK_TYPE_NONE; + } else + disk_identify_device(d); + } + + *_d = d; + + return 0; + +fail: + + if (d) + sk_disk_free(d); + + return ret; +} + +void sk_disk_free(SkDisk *d) { + assert(d); + + if (d->fd >= 0) + close(d->fd); + + free(d->name); + free(d->blob); + free(d); +} + +int sk_disk_get_blob(SkDisk *d, const void **blob, size_t *rsize) { + size_t size; + SkBool good, have_good = FALSE; + uint32_t *p; + + assert(d); + assert(blob); + assert(rsize); + + size = + (d->identify_valid ? 8 + sizeof(d->identify) : 0) + + (d->smart_data_valid ? 8 + sizeof(d->smart_data) : 0) + + (d->smart_thresholds_valid ? 8 + sizeof(d->smart_thresholds) : 0); + + if (sk_disk_smart_status(d, &good) >= 0) { + size += 12; + have_good = TRUE; + } + + if (size <= 0) { + errno = ENODATA; + return -1; + } + + free(d->blob); + if (!(d->blob = malloc(size))) { + errno = ENOMEM; + return -1; + } + + p = d->blob; + + /* These memory accesses are only OK as long as all our + * objects are sensibly aligned, which they are... */ + + if (d->identify_valid) { + p[0] = SK_BLOB_TAG_IDENTIFY; + p[1] = htonl(sizeof(d->identify)); + p += 2; + + memcpy(p, d->identify, sizeof(d->identify)); + p = (uint32_t*) ((uint8_t*) p + sizeof(d->identify)); + } + + if (have_good) { + p[0] = SK_BLOB_TAG_SMART_STATUS; + p[1] = htonl(4); + p[2] = htonl(!!good); + p += 3; + } + + if (d->smart_data_valid) { + p[0] = SK_BLOB_TAG_SMART_DATA; + p[1] = htonl(sizeof(d->smart_data)); + p += 2; + + memcpy(p, d->smart_data, sizeof(d->smart_data)); + p = (uint32_t*) ((uint8_t*) p + sizeof(d->smart_data)); + } + + if (d->smart_thresholds_valid) { + p[0] = SK_BLOB_TAG_SMART_THRESHOLDS; + p[1] = htonl(sizeof(d->smart_thresholds)); + p += 2; + + memcpy(p, d->smart_thresholds, sizeof(d->smart_thresholds)); + p = (uint32_t*) ((uint8_t*) p + sizeof(d->smart_thresholds)); + } + + assert((size_t) ((uint8_t*) p - (uint8_t*) d->blob) == size); + + *blob = d->blob; + *rsize = size; + + return 0; +} + +int sk_disk_set_blob(SkDisk *d, const void *blob, size_t size) { + const uint32_t *p; + size_t left; + SkBool idv = FALSE, sdv = FALSE, stv = FALSE, bssv = FALSE; + + assert(d); + assert(blob); + + if (d->type != SK_DISK_TYPE_BLOB) { + errno = ENODEV; + return -1; + } + + if (size <= 0) { + errno = EINVAL; + return -1; + } + + /* First run, verify if everything makes sense */ + p = blob; + left = size; + while (left > 0) { + uint32_t tag, tsize; + + if (left < 8) { + errno = EINVAL; + return -1; + } + + memcpy(&tag, p, 4); + memcpy(&tsize, p+1, 4); + p += 2; + left -= 8; + + if (left < ntohl(tsize)) { + errno = EINVAL; + return -1; + } + + switch (tag) { + + case SK_BLOB_TAG_IDENTIFY: + if (ntohl(tsize) != sizeof(d->identify) || idv) { + errno = EINVAL; + return -1; + } + idv = TRUE; + break; + + case SK_BLOB_TAG_SMART_STATUS: + if (ntohl(tsize) != 4 || bssv) { + errno = EINVAL; + return -1; + } + bssv = TRUE; + break; + + case SK_BLOB_TAG_SMART_DATA: + if (ntohl(tsize) != sizeof(d->smart_data) || sdv) { + errno = EINVAL; + return -1; + } + sdv = TRUE; + break; + + case SK_BLOB_TAG_SMART_THRESHOLDS: + if (ntohl(tsize) != sizeof(d->smart_thresholds) || stv) { + errno = EINVAL; + return -1; + } + stv = TRUE; + break; + } + + p = (uint32_t*) ((uint8_t*) p + ntohl(tsize)); + left -= ntohl(tsize); + } + + if (!idv) { + errno = -ENODATA; + return -1; + } + + d->identify_valid = idv; + d->smart_data_valid = sdv; + d->smart_thresholds_valid = stv; + d->blob_smart_status_valid = bssv; + + /* Second run, actually copy things in */ + p = blob; + left = size; + while (left > 0) { + uint32_t tag, tsize; + + assert(left >= 8); + memcpy(&tag, p, 4); + memcpy(&tsize, p+1, 4); + p += 2; + left -= 8; + + assert(left >= ntohl(tsize)); + + switch (tag) { + + case SK_BLOB_TAG_IDENTIFY: + assert(ntohl(tsize) == sizeof(d->identify)); + memcpy(d->identify, p, sizeof(d->identify)); + break; + + case SK_BLOB_TAG_SMART_STATUS: { + uint32_t ok; + assert(ntohl(tsize) == 4); + memcpy(&ok, p, 4); + d->blob_smart_status = !!ok; + break; + } + + case SK_BLOB_TAG_SMART_DATA: + assert(ntohl(tsize) == sizeof(d->smart_data)); + memcpy(d->smart_data, p, sizeof(d->smart_data)); + break; + + case SK_BLOB_TAG_SMART_THRESHOLDS: + assert(ntohl(tsize) == sizeof(d->smart_thresholds)); + memcpy(d->smart_thresholds, p, sizeof(d->smart_thresholds)); + break; + } + + p = (uint32_t*) ((uint8_t*) p + ntohl(tsize)); + left -= ntohl(tsize); + } + + return 0; +}