libatasmart: initial checkin
authorFlorian Eckert <fe@dev.tdt.de>
Fri, 5 Mar 2021 16:26:45 +0000 (17:26 +0100)
committerFlorian Eckert <fe@dev.tdt.de>
Tue, 16 Mar 2021 12:23:08 +0000 (13:23 +0100)
This library is required by the smart plugin of the collectd.

Signed-off-by: Florian Eckert <fe@dev.tdt.de>
libs/libatasmart/Makefile [new file with mode: 0644]
libs/libatasmart/patches/001-fix-cross-compile.patch [new file with mode: 0644]
libs/libatasmart/src/atasmart.strpool.c [new file with mode: 0644]

diff --git a/libs/libatasmart/Makefile b/libs/libatasmart/Makefile
new file mode 100644 (file)
index 0000000..097bd49
--- /dev/null
@@ -0,0 +1,98 @@
+#
+# Copyright (C) 2021 TDT AG <development@tdt.de>
+#
+# 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 <fe@dev.tdt.de>
+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 (file)
index 0000000..93d8633
--- /dev/null
@@ -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 (file)
index 0000000..990cf24
--- /dev/null
@@ -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
+    <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <alloca.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <scsi/scsi.h>
+#include <scsi/sg.h>
+#include <scsi/scsi_ioctl.h>
+#include <linux/hdreg.h>
+#include <linux/fs.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <sys/param.h>
+#include <libudev.h>
+
+#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, &sectors) < 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<<i))
+                                printf(" %s", _P(quirk_name[i]));
+
+                printf("\n");
+        }
+
+        ret = sk_disk_check_sleep_mode(d, &awake);
+        printf("Awake: %s\n",
+               ret >= 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;
+}