Handle duplicated Available Reserved Space
[platform/upstream/libatasmart.git] / atasmart.c
index c778831..f228aef 100644 (file)
@@ -42,6 +42,8 @@
 #include <linux/fs.h>
 #include <sys/types.h>
 #include <regex.h>
+#include <sys/param.h>
+#include <libudev.h>
 
 #include "atasmart.h"
 
@@ -59,14 +61,42 @@ typedef enum SkDirection {
 } SkDirection;
 
 typedef enum SkDiskType {
-        SK_DISK_TYPE_ATA_PASSTHROUGH_12, /* ATA passthrough over SCSI, 12-byte version */
-        SK_DISK_TYPE_ATA_PASSTHROUGH_16, /* ATA passthrough over SCSI transport */
-        SK_DISK_TYPE_ATA,
-        SK_DISK_TYPE_UNKNOWN,
-        SK_DISK_TYPE_BLOB,
-        _SK_DISK_TYPE_MAX
+        /* 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;
@@ -74,18 +104,25 @@ struct SkDisk {
 
         uint64_t size;
 
-        struct {
-                uint8_t identify[512];
-                uint8_t smart_data[512];
-                uint8_t smart_threshold_data[512];
-        } __attribute__((packed)) blob;
+        uint8_t identify[512];
+        uint8_t smart_data[512];
+        uint8_t smart_thresholds[512];
+
+        SkBool smart_initialized:1;
 
-        SkBool identify_data_valid:1;
+        SkBool identify_valid:1;
         SkBool smart_data_valid:1;
-        SkBool smart_threshold_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;
+
+        void *blob;
 };
 
 /* ATA commands */
@@ -106,15 +143,28 @@ typedef enum SkSmartCommand {
         SK_SMART_COMMAND_RETURN_STATUS = 0xDA
 } SkSmartCommand;
 
-static const char *disk_type_to_string(SkDiskType type) {
+/* 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] = "16 Byte SCSI ATA SAT Passthru",
                 [SK_DISK_TYPE_ATA_PASSTHROUGH_12] = "12 Byte SCSI ATA SAT Passthru",
-                [SK_DISK_TYPE_ATA] = "Native ATA",
+                [SK_DISK_TYPE_LINUX_IDE] = "Native Linux IDE",
+                [SK_DISK_TYPE_SUNPLUS] = "Sunplus SCSI ATA Passthru",
+                [SK_DISK_TYPE_JMICRON] = "JMicron SCSI ATA Passthru",
                 [SK_DISK_TYPE_BLOB] = "Blob",
-                [SK_DISK_TYPE_UNKNOWN] = "Unknown"
+                [SK_DISK_TYPE_AUTO] = "Automatic",
+                [SK_DISK_TYPE_NONE] = "None"
         };
         /* %STRINGPOOLSTOP% */
 
@@ -124,42 +174,91 @@ static const char *disk_type_to_string(SkDiskType type) {
         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] = "sat16",
+                [SK_DISK_TYPE_ATA_PASSTHROUGH_12] = "sat12",
+                [SK_DISK_TYPE_LINUX_IDE] = "linux-ide",
+                [SK_DISK_TYPE_SUNPLUS] = "sunplus",
+                [SK_DISK_TYPE_JMICRON] = "jmicron",
+                [SK_DISK_TYPE_NONE] = "none",
+                [SK_DISK_TYPE_AUTO] = "auto",
+        };
+        /* %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_data_valid && !!(d->blob.identify[164] & 1);
+        return d->identify_valid && !!(d->identify[164] & 1);
 }
 
 static SkBool disk_smart_is_enabled(SkDisk *d) {
-        return d->identify_data_valid && !!(d->blob.identify[170] & 1);
+        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->blob.smart_data[367] & 32);
+        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->blob.smart_data[367] & 16);
+        return !!(d->smart_data[367] & 16);
 }
 
 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
         assert(d->smart_data_valid);
 
-        return !!(d->blob.smart_data[367] & 1);
+        return !!(d->smart_data[367] & 1);
 }
 
 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
         assert(d->smart_data_valid);
 
-        return !!(d->blob.smart_data[367] & 41);
+        return !!(d->smart_data[367] & 41);
 }
 
-static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
+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_ATA);
+        assert(d->type == SK_DISK_TYPE_LINUX_IDE);
 
         switch (direction) {
 
@@ -314,7 +413,7 @@ static int disk_passthrough_16_command(SkDisk *d, SkAtaCommand command, SkDirect
 
         memset(sense, 0, sizeof(sense));
 
-        if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
+        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) {
@@ -384,7 +483,7 @@ static int disk_passthrough_12_command(SkDisk *d, SkAtaCommand command, SkDirect
 
         memset(sense, 0, sizeof(sense));
 
-        if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[4] * 512, sense, sizeof(sense))) < 0)
+        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) {
@@ -394,14 +493,205 @@ static int disk_passthrough_12_command(SkDisk *d, SkAtaCommand command, SkDirect
 
         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];
+        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;
 }
@@ -409,9 +699,14 @@ static int disk_passthrough_12_command(SkDisk *d, SkAtaCommand command, SkDirect
 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_ATA] = disk_ata_command,
+                [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_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);
@@ -421,6 +716,11 @@ static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction,
         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);
 }
 
@@ -428,15 +728,17 @@ 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->blob.identify, &len)) < 0)
+        if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
                 return ret;
 
         if (len != 512) {
@@ -444,7 +746,19 @@ static int disk_identify_device(SkDisk *d) {
                 return -1;
         }
 
-        d->identify_data_valid = TRUE;
+        /* 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;
 }
@@ -454,7 +768,7 @@ int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
         uint16_t cmd[6];
         uint8_t status;
 
-        if (!d->identify_data_valid) {
+        if (!d->identify_valid) {
                 errno = ENOTSUP;
                 return -1;
         }
@@ -508,6 +822,9 @@ int sk_disk_smart_read_data(SkDisk *d) {
         int ret;
         size_t len = 512;
 
+        if (init_smart(d) < 0)
+                return -1;
+
         if (!disk_smart_is_available(d)) {
                 errno = ENOTSUP;
                 return -1;
@@ -524,7 +841,7 @@ int sk_disk_smart_read_data(SkDisk *d) {
         cmd[3] = htons(0x00C2U);
         cmd[4] = htons(0x4F00U);
 
-        if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->blob.smart_data, &len)) < 0)
+        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;
@@ -553,10 +870,10 @@ static int disk_smart_read_thresholds(SkDisk *d) {
         cmd[3] = htons(0x00C2U);
         cmd[4] = htons(0x4F00U);
 
-        if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->blob.smart_threshold_data, &len)) < 0)
+        if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_thresholds, &len)) < 0)
                 return ret;
 
-        d->smart_threshold_data_valid = TRUE;
+        d->smart_thresholds_valid = TRUE;
 
         return ret;
 }
@@ -565,13 +882,22 @@ 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) {
-                errno = ENOTSUP;
+
+                if (d->blob_smart_status_valid) {
+                        *good = d->blob_smart_status;
+                        return 0;
+                }
+
+                errno = ENXIO;
                 return -1;
         }
 
@@ -585,10 +911,12 @@ int sk_disk_smart_status(SkDisk *d, SkBool *good) {
         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
                 return ret;
 
-        if (cmd[3] == htons(0x00C2U) &&
+        /* 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 (cmd[3] == htons(0x002CU) &&
+        else if ((d->type == SK_DISK_TYPE_ATA_PASSTHROUGH_12 || cmd[3] == htons(0x002CU)) &&
                  cmd[4] == htons(0xF400U))
                 *good = FALSE;
         else {
@@ -603,6 +931,9 @@ 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;
@@ -707,14 +1038,14 @@ int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
         assert(d);
         assert(ipd);
 
-        if (!d->identify_data_valid) {
+        if (!d->identify_valid) {
                 errno = ENOENT;
                 return -1;
         }
 
-        read_string(d->identify_parsed_data.serial, d->blob.identify+20, 20);
-        read_string(d->identify_parsed_data.firmware, d->blob.identify+46, 8);
-        read_string(d->identify_parsed_data.model, d->blob.identify+54, 40);
+        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;
 
@@ -725,7 +1056,7 @@ int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
         assert(d);
         assert(b);
 
-        if (!d->identify_data_valid) {
+        if (!d->identify_valid) {
                 errno = ENOTSUP;
                 return -1;
         }
@@ -738,7 +1069,7 @@ int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
         assert(d);
         assert(b);
 
-        *b = d->identify_data_valid;
+        *b = d->identify_valid;
         return 0;
 }
 
@@ -864,89 +1195,175 @@ static void make_pretty(SkSmartAttributeParsedData *a) {
         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 = (((uint64_t) a->raw[0]) | (uint64_t) a->raw[1]) * 60 * 1000;
-        else if (!strcmp(a->name, "power-on-seconds"))
+                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 * 60 * 60 * 1000;
+                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 * 65535 * 512 / 1000000000;
         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 (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]   = { "raw-read-error-rate",         SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [2]   = { "throughput-performance",      SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [3]   = { "spin-up-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-        [4]   = { "start-stop-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [5]   = { "reallocated-sector-count",    SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-        [6]   = { "read-channel-margin",         SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [7]   = { "seek-error-rate",             SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [8]   = { "seek-time-performance",       SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [9]   = { "power-on-hours",              SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-        [10]  = { "spin-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-        [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
-        [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [192] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [193] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [194] = { "temperature-celsius-2",       SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
-        [195] = { "hardware-ecc-recovered",      SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [196] = { "reallocated-event-count",     SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-        [197] = { "current-pending-sector",      SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-        [198] = { "offline-uncorrectable",       SK_SMART_ATTRIBUTE_UNIT_SECTORS },
-        [199] = { "udma-crc-error-count",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [200] = { "multi-zone-error-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [201] = { "soft-read-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [202] = { "ta-increase-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [203] = { "run-out-cancel",              SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [204] = { "shock-count-write-open",      SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [205] = { "shock-rate-write-open",       SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [206] = { "flying-height",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [207] = { "spin-high-current",           SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [208] = { "spin-buzz",                   SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
-        [209] = { "offline-seek-performance",    SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [220] = { "disk-shift",                  SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [221] = { "g-sense-error-rate-2",        SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [222] = { "loaded-hours",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-        [223] = { "load-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [224] = { "load-friction",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [225] = { "load-cycle-count-2",          SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [226] = { "load-in-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-        [227] = { "torq-amp-count",              SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [228] = { "power-off-retract-count-2",   SK_SMART_ATTRIBUTE_UNIT_NONE },
-        [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
-        [231] = { "temperature-celsius",         SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
-        [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
-        [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE }
+        [1]   = { "raw-read-error-rate",         SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [2]   = { "throughput-performance",      SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [3]   = { "spin-up-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_short_time },
+        [4]   = { "start-stop-count",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [5]   = { "reallocated-sector-count",    SK_SMART_ATTRIBUTE_UNIT_SECTORS,  verify_sectors },
+        [6]   = { "read-channel-margin",         SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [7]   = { "seek-error-rate",             SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [8]   = { "seek-time-performance",       SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [9]   = { "power-on-hours",              SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time },
+        [10]  = { "spin-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [170] = { "available-reserved-space",    SK_SMART_ATTRIBUTE_UNIT_PERCENT,  NULL },
+        [184] = { "end-to-end-error",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS,  verify_sectors },
+        [188] = { "command-timeout",             SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN,  verify_temperature },
+        [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [192] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [193] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [194] = { "temperature-celsius-2",       SK_SMART_ATTRIBUTE_UNIT_MKELVIN,  verify_temperature },
+        [195] = { "hardware-ecc-recovered",      SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [196] = { "reallocated-event-count",     SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [197] = { "current-pending-sector",      SK_SMART_ATTRIBUTE_UNIT_SECTORS,  verify_sectors },
+        [198] = { "offline-uncorrectable",       SK_SMART_ATTRIBUTE_UNIT_SECTORS,  verify_sectors },
+        [199] = { "udma-crc-error-count",        SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [200] = { "multi-zone-error-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [201] = { "soft-read-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [202] = { "ta-increase-count",           SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [203] = { "run-out-cancel",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [204] = { "shock-count-write-open",      SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [205] = { "shock-rate-write-open",       SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [206] = { "flying-height",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [207] = { "spin-high-current",           SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [208] = { "spin-buzz",                   SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [209] = { "offline-seek-performance",    SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [220] = { "disk-shift",                  SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [221] = { "g-sense-error-rate-2",        SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [222] = { "loaded-hours",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time },
+        [223] = { "load-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [224] = { "load-friction",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [225] = { "load-cycle-count-2",          SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [226] = { "load-in-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_short_time },
+        [227] = { "torq-amp-count",              SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [228] = { "power-off-retract-count-2",   SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [231] = { "temperature-celsius",         SK_SMART_ATTRIBUTE_UNIT_MKELVIN,  verify_temperature },
+
+        /* http://www.adtron.com/pdf/SMART_for_XceedLite_SATA_RevA.pdf */
+        [232] = { "endurance-remaining",         SK_SMART_ATTRIBUTE_UNIT_PERCENT,  NULL },
+        [233] = { "power-on-seconds-2",          SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [234] = { "uncorrectable-ecc-count",     SK_SMART_ATTRIBUTE_UNIT_SECTORS,  NULL },
+        [235] = { "good-block-rate",             SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+
+        [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time },
+        [241] = { "total-lbas-written",          SK_SMART_ATTRIBUTE_UNIT_GB,  NULL },
+        [242] = { "total-lbas-read",             SK_SMART_ATTRIBUTE_UNIT_GB,  NULL },
+        [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL }
 };
 /* %STRINGPOOLSTOP% */
 
 typedef enum SkSmartQuirk {
-        SK_SMART_QUIRK_9_POWERONMINUTES = 1,
-        SK_SMART_QUIRK_9_POWERONSECONDS = 2,
-        SK_SMART_QUIRK_9_POWERONHALFMINUTES = 4,
-        SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 8,
-        SK_SMART_QUIRK_193_LOADUNLOAD = 16,
-        SK_SMART_QUIRK_194_10XCELSIUS = 32,
-        SK_SMART_QUIRK_194_UNKNOWN = 64,
-        SK_SMART_QUIRK_200_WRITEERRORCOUNT = 128,
-        SK_SMART_QUIRK_201_DETECTEDTACOUNT = 256,
+        SK_SMART_QUIRK_9_POWERONMINUTES            = 0x00001,
+        SK_SMART_QUIRK_9_POWERONSECONDS            = 0x00002,
+        SK_SMART_QUIRK_9_POWERONHALFMINUTES        = 0x00004,
+        SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 0x00008,
+        SK_SMART_QUIRK_193_LOADUNLOAD              = 0x00010,
+        SK_SMART_QUIRK_194_10XCELSIUS              = 0x00020,
+        SK_SMART_QUIRK_194_UNKNOWN                 = 0x00040,
+        SK_SMART_QUIRK_200_WRITEERRORCOUNT         = 0x00080,
+        SK_SMART_QUIRK_201_DETECTEDTACOUNT         = 0x00100,
+        SK_SMART_QUIRK_5_UNKNOWN                   = 0x00200,
+        SK_SMART_QUIRK_9_UNKNOWN                   = 0x00400,
+        SK_SMART_QUIRK_197_UNKNOWN                 = 0x00800,
+        SK_SMART_QUIRK_198_UNKNOWN                 = 0x01000,
+        SK_SMART_QUIRK_190_UNKNOWN                 = 0x02000,
+        SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE  = 0x04000,
+        SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR   = 0x08000,
+        SK_SMART_QUIRK_225_TOTALLBASWRITTEN        = 0x10000
 } SkSmartQuirk;
 
 /* %STRINGPOOLSTART% */
@@ -960,6 +1377,12 @@ static const char *quirk_name[] = {
         "194_UNKNOWN",
         "200_WRITEERRORCOUNT",
         "201_DETECTEDTACOUNT",
+        "5_UNKNOWN",
+        "9_UNKNOWN",
+        "197_UNKNOWN",
+        "198_UNKNOWN",
+        "190_UNKNOWN",
+        "232_AVAILABLERESERVEDSPACE",
         NULL
 };
 /* %STRINGPOOLSTOP% */
@@ -973,6 +1396,15 @@ typedef struct 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|
@@ -1091,7 +1523,26 @@ static const SkSmartQuirkDatabase quirk_database[] = { {
                 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 SSDSA2CW[0-9]{3}G3$",
+                NULL,
+                SK_SMART_QUIRK_225_TOTALLBASWRITTEN|
+                SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE|
+                SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR
+        }, {
                 NULL,
                 NULL,
                 0
@@ -1174,35 +1625,48 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
         if (quirk) {
                 switch (id) {
 
+                        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 = {
-                                                "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+                                                "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time
                                         };
                                         return &a;
 
                                 } else if (quirk & SK_SMART_QUIRK_9_POWERONSECONDS) {
                                         static const SkSmartAttributeInfo a = {
-                                                "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+                                                "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time
                                         };
                                         return &a;
 
                                 } else if (quirk & SK_SMART_QUIRK_9_POWERONHALFMINUTES) {
                                         static const SkSmartAttributeInfo a = {
-                                                "power-on-half-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+                                                "power-on-half-minutes", 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 = {
-                                                "emergency-retract-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE
+                                                "emergency-retract-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL
                                         };
                                         return &a;
                                 }
@@ -1214,7 +1678,7 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
                                 /* %STRINGPOOLSTART% */
                                 if (quirk & SK_SMART_QUIRK_194_10XCELSIUS) {
                                         static const SkSmartAttributeInfo a = {
-                                                "temperature-centi-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN
+                                                "temperature-centi-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature
                                         };
                                         return &a;
                                 } else if (quirk & SK_SMART_QUIRK_194_UNKNOWN)
@@ -1223,11 +1687,23 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
 
                                 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 = {
-                                                "write-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE
+                                                "write-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL
                                         };
                                         return &a;
                                 }
@@ -1239,13 +1715,48 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
                                 /* %STRINGPOOLSTART% */
                                 if (quirk & SK_SMART_QUIRK_201_DETECTEDTACOUNT) {
                                         static const SkSmartAttributeInfo a = {
-                                                "detected-ta-count", SK_SMART_ATTRIBUTE_UNIT_NONE
+                                                "detected-ta-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL
                                         };
                                         return &a;
                                 }
                                 /* %STRINGPOOLSTOP% */
 
                                 break;
+
+                        case 225:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_225_TOTALLBASWRITTEN) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "total-lbas-written", SK_SMART_ATTRIBUTE_UNIT_GB, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
+
+                        case 232:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "available-reserved-space", SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+                                break;
+
+                        case 233:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "media-wearout-indicator", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+                                break;
+
                 }
         }
 
@@ -1263,7 +1774,7 @@ int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
                 return -1;
         }
 
-        switch (d->blob.smart_data[362]) {
+        switch (d->smart_data[362]) {
                 case 0x00:
                 case 0x80:
                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
@@ -1298,19 +1809,19 @@ int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
                         break;
         }
 
-        d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->blob.smart_data[363] & 0xF);
-        d->smart_parsed_data.self_test_execution_status = (d->blob.smart_data[363] >> 4) & 0xF;
+        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->blob.smart_data[364] | ((uint16_t) d->blob.smart_data[365] << 8);
+        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->blob.smart_data[372];
-        d->smart_parsed_data.extended_test_polling_minutes = d->blob.smart_data[373] != 0xFF ? d->blob.smart_data[373] : ((uint16_t) d->blob.smart_data[376] << 8 | (uint16_t) d->blob.smart_data[375]);
-        d->smart_parsed_data.conveyance_test_polling_minutes = d->blob.smart_data[374];
+        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;
 
@@ -1321,41 +1832,50 @@ static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
         uint8_t *p;
         unsigned n;
 
-        if (!d->smart_threshold_data_valid) {
-                a->threshold_valid = FALSE;
-                return;
-        }
+        if (!d->smart_thresholds_valid)
+                goto fail;
 
-        for (n = 0, p = d->blob.smart_threshold_data+2; n < 30; n++, p+=12)
+        for (n = 0, p = d->smart_thresholds+2; n < 30; n++, p+=12)
                 if (p[0] == a->id)
                         break;
 
-        if (n >= 30) {
-                a->threshold_valid = FALSE;
-                a->good_valid = FALSE;
-                return;
-        }
+        if (n >= 30)
+                goto fail;
 
         a->threshold = p[1];
         a->threshold_valid = p[1] != 0xFE;
 
-        a->good_valid = FALSE;
-        a->good = TRUE;
+        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 = a->good && (a->worst_value > a->threshold);
-                        a->good_valid = TRUE;
+                        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 = a->good && (a->current_value > a->threshold);
-                        a->good_valid = TRUE;
+                        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) {
@@ -1367,7 +1887,7 @@ int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb,
                 return -1;
         }
 
-        for (n = 0, p = d->blob.smart_data + 2; n < 30; n++, p+=12) {
+        for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
                 SkSmartAttributeParsedData a;
                 const SkSmartAttributeInfo *i;
                 char *an = NULL;
@@ -1405,18 +1925,10 @@ int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb,
 
                 find_threshold(d, &a);
 
-                /* Handle a few fields specially */
-                if ((!strcmp(a.name, "reallocated-sector-count") ||
-                     !strcmp(a.name, "reallocated-event-count") ||
-                     !strcmp(a.name, "current-pending-sector")) &&
-                    a.pretty_unit == SK_SMART_ATTRIBUTE_UNIT_SECTORS &&
-                    a.pretty_value > 0) {
-                        a.good = FALSE;
-                        a.good_valid = TRUE;
-                }
+                if (i && i->verify)
+                        i->verify(d, &a);
 
                 cb(d, &a, userdata);
-
                 free(an);
         }
 
@@ -1435,7 +1947,9 @@ const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
                 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
                 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
                 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
-                [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
+                [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK",
+                [SK_SMART_ATTRIBUTE_UNIT_PERCENT] = "%",
+                [SK_SMART_ATTRIBUTE_UNIT_GB] = "GB"
         };
         /* %STRINGPOOLSTOP% */
 
@@ -1494,6 +2008,7 @@ static void power_on_cb(SkDisk *d, const SkSmartAttributeParsedData *a, struct a
 
         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")) {
 
@@ -1524,13 +2039,46 @@ int sk_disk_smart_get_power_on(SkDisk *d, uint64_t *mseconds) {
         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 reallocated_cb(SkDisk *d, const SkSmartAttributeParsedData *a, struct attr_helper *ah) {
 
         if (a->pretty_unit != SK_SMART_ATTRIBUTE_UNIT_SECTORS)
                 return;
 
-        if (!strcmp(a->name, "reallocated-sector-count") ||
-            !strcmp(a->name, "reallocated-event-count")) {
+        if (!strcmp(a->name, "reallocated-sector-count")) {
 
                 if (!ah->found || a->pretty_value > *ah->value)
                         *ah->value = a->pretty_value;
@@ -1592,9 +2140,11 @@ const char* sk_smart_overall_to_string(SkSmartOverall overall) {
         /* %STRINGPOOLSTART% */
         const char * const map[] = {
                 [SK_SMART_OVERALL_GOOD] = "GOOD",
+                [SK_SMART_OVERALL_BAD_ATTRIBUTE_IN_THE_PAST] = "BAD_ATTRIBUTE_IN_THE_PAST",
+                [SK_SMART_OVERALL_BAD_SECTOR] = "BAD_SECTOR",
+                [SK_SMART_OVERALL_BAD_ATTRIBUTE_NOW] = "BAD_ATTRIBUTE_NOW",
+                [SK_SMART_OVERALL_BAD_SECTOR_MANY] = "BAD_SECTOR_MANY",
                 [SK_SMART_OVERALL_BAD_STATUS] = "BAD_STATUS",
-                [SK_SMART_OVERALL_BAD_ATTRIBUTE] = "BAD_ATTRIBUTE",
-                [SK_SMART_OVERALL_BAD_SECTOR] = "BAD_SECTOR"
         };
         /* %STRINGPOOLSTOP% */
 
@@ -1604,23 +2154,39 @@ const char* sk_smart_overall_to_string(SkSmartOverall overall) {
         return _P(map[overall]);
 }
 
-static void bad_attribute_cb(SkDisk *d, const SkSmartAttributeParsedData *a, SkBool *good) {
-        if (a->good_valid && !a->good)
+static void bad_attribute_now_cb(SkDisk *d, const SkSmartAttributeParsedData *a, SkBool *good) {
+        if (a->prefailure && a->good_now_valid && !a->good_now)
                 *good = FALSE;
 }
 
+static void bad_attribute_in_the_past_cb(SkDisk *d, const SkSmartAttributeParsedData *a, SkBool *good) {
+        if (a->prefailure && a->good_in_the_past_valid && !a->good_in_the_past)
+                *good = FALSE;
+}
+
+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;
+        uint64_t sectors, sector_threshold;
 
         assert(d);
         assert(overall);
 
-        if (d->type == SK_DISK_TYPE_BLOB) {
-                errno = ENOTSUP;
-                return -1;
-        }
-
+        /* First, check SMART self-assesment */
         if (sk_disk_smart_status(d, &good) < 0)
                 return -1;
 
@@ -1629,23 +2195,51 @@ int sk_disk_smart_get_overall(SkDisk *d, SkSmartOverall *overall) {
                 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;
-        } else if (sectors > 0) {
+                sectors = 0;
+        } else {
+
+                /* We use log2(n_sectors) 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);
+
+                if (sectors >= sector_threshold) {
+                        *overall = SK_SMART_OVERALL_BAD_SECTOR_MANY;
+                        return 0;
+                }
+        }
+
+        /* Third, check if any of the SMART attributes is bad */
+        good = TRUE;
+        if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) bad_attribute_now_cb, &good) < 0)
+                return -1;
+
+        if (!good) {
+                *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 */
         good = TRUE;
-        if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) bad_attribute_cb, &good) < 0)
+        if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) bad_attribute_in_the_past_cb, &good) < 0)
                 return -1;
 
         if (!good) {
-                *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE;
+                *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;
 }
@@ -1692,6 +2286,14 @@ static char *print_value(char *s, size_t len, uint64_t pretty_value, SkSmartAttr
                         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_GB:
+                        snprintf(s, len, "%llu GB", (unsigned long long) pretty_value);
+                        break;
+
                 case SK_SMART_ATTRIBUTE_UNIT_NONE:
                         snprintf(s, len, "%llu", (unsigned long long) pretty_value);
                         break;
@@ -1725,12 +2327,12 @@ static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a,
         snprintf(tc, sizeof(tc), "%3u", a->current_value);
         tc[sizeof(tc)-1] = 0;
 
-        highlight = a->good_valid && !a->good && isatty(1);
+        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 %-3s\n",
+        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",
@@ -1740,7 +2342,8 @@ static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a,
                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_valid ? yes_no(a->good) : "n/a");
+               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);
@@ -1753,10 +2356,12 @@ int sk_disk_dump(SkDisk *d) {
 
         assert(d);
 
-        printf("Device: %s\n"
+        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_string(d->type));
+               disk_type_to_human_string(d->type));
 
         ret = sk_disk_get_size(d, &size);
         if (ret >= 0)
@@ -1764,7 +2369,7 @@ int sk_disk_dump(SkDisk *d) {
         else
                 printf("Size: %s\n", strerror(errno));
 
-        if (d->identify_data_valid) {
+        if (d->identify_valid) {
                 const SkIdentifyParsedData *ipd;
                 SkSmartQuirk quirk = 0;
                 unsigned i;
@@ -1791,7 +2396,6 @@ int sk_disk_dump(SkDisk *d) {
                                 printf(" %s", _P(quirk_name[i]));
 
                 printf("\n");
-
         }
 
         ret = sk_disk_check_sleep_mode(d, &awake);
@@ -1803,12 +2407,13 @@ int sk_disk_dump(SkDisk *d) {
                 const SkSmartParsedData *spd;
                 SkBool good;
                 char pretty[32];
-                uint64_t value;
+                uint64_t value, power_on;
 
                 ret = sk_disk_smart_status(d, &good);
-                printf("SMART Disk Health Good: %s\n",
-                       ret >= 0 ? yes_no(good) : strerror(errno));
-
+                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;
 
@@ -1846,16 +2451,29 @@ int sk_disk_dump(SkDisk *d) {
                                print_value(pretty, sizeof(pretty), value, SK_SMART_ATTRIBUTE_UNIT_SECTORS),
                                value > 0 ? ENDHIGHLIGHT : "");
 
-                if (sk_disk_smart_get_power_on(d, &value) < 0)
+                if (sk_disk_smart_get_power_on(d, &power_on) < 0) {
                         printf("Powered On: %s\n", strerror(errno));
-                else
-                        printf("Powered On: %s\n", print_value(pretty, sizeof(pretty), value, SK_SMART_ATTRIBUTE_UNIT_MSECONDS));
+                        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
@@ -1864,7 +2482,7 @@ int sk_disk_dump(SkDisk *d) {
                                sk_smart_overall_to_string(overall),
                                overall != SK_SMART_OVERALL_GOOD ? ENDHIGHLIGHT : "");
 
-                printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-3s\n",
+                printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-4s %-4s\n",
                        "ID#",
                        "Name",
                        "Value",
@@ -1874,11 +2492,13 @@ int sk_disk_dump(SkDisk *d) {
                        "Raw",
                        "Type",
                        "Updates",
-                       "Good");
+                       "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;
 }
@@ -1896,6 +2516,150 @@ int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
         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;
@@ -1908,18 +2672,25 @@ int sk_disk_open(const char *name, SkDisk **_d) {
                 goto fail;
         }
 
-        if (!name) {
-                d->fd = -1;
+        d->fd = -1;
+        d->size = (uint64_t) -1;
+
+        if (!name)
                 d->type = SK_DISK_TYPE_BLOB;
-                d->size = (uint64_t) -1;
-        } else {
+        else {
+                const char *dn;
+
+                d->type = SK_DISK_TYPE_AUTO;
 
-                if (!(d->name = strdup(name))) {
+                if (!(dn = disk_type_from_string(name, &d->type)))
+                        dn = name;
+
+                if (!(d->name = strdup(dn))) {
                         errno = ENOMEM;
                         goto fail;
                 }
 
-                if ((d->fd = open(name,
+                if ((d->fd = open(d->name,
                                   O_RDONLY|O_NOCTTY|O_NONBLOCK
 #ifdef O_CLOEXEC
                                   |O_CLOEXEC
@@ -1949,31 +2720,20 @@ int sk_disk_open(const char *name, SkDisk **_d) {
                         goto fail;
                 }
 
-                /* OK, it's a real block device with a size. Find a way to
-                 * identify the device. */
-                for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
-                        if (disk_identify_device(d) >= 0)
-                                break;
-
-                /* Check if driver can do SMART, and enable if necessary */
-                if (disk_smart_is_available(d)) {
-
-                        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);
-                }
+                /* 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;
@@ -1995,33 +2755,92 @@ void sk_disk_free(SkDisk *d) {
                 close(d->fd);
 
         free(d->name);
+        free(d->blob);
         free(d);
 }
 
-int sk_disk_get_blob(SkDisk *d, const void **blob, size_t *size) {
+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(size);
+        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 (!d->identify_data_valid) {
+        if (size <= 0) {
                 errno = ENODATA;
                 return -1;
         }
 
-        *blob = &d->blob;
-        *size = sizeof(d->blob.identify);
+        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) {
-                *size += sizeof(d->blob.smart_data);
+                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;
 
-                if (d->smart_threshold_data_valid)
-                        *size += sizeof(d->blob.smart_threshold_data);
+                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);
 
@@ -2030,25 +2849,124 @@ int sk_disk_set_blob(SkDisk *d, const void *blob, size_t size) {
                 return -1;
         }
 
-        if (size == sizeof(d->blob.identify)+sizeof(d->blob.smart_data)+sizeof(d->blob.smart_threshold_data)) {
-                d->identify_data_valid = TRUE;
-                d->smart_data_valid = TRUE;
-                d->smart_threshold_data_valid = TRUE;
-        } else if (size == sizeof(d->blob.identify)+sizeof(d->blob.smart_data)) {
-                d->identify_data_valid = TRUE;
-                d->smart_data_valid = TRUE;
-                d->smart_threshold_data_valid = FALSE;
-        } else if (size == sizeof(d->blob.identify)) {
-                d->identify_data_valid = TRUE;
-                d->smart_data_valid = FALSE;
-                d->smart_threshold_data_valid = FALSE;
-        } else {
+        if (size <= 0) {
                 errno = EINVAL;
                 return -1;
         }
 
-        memset(&d->blob, 0, sizeof(d->blob));
-        memcpy(&d->blob, blob, size);
+        /* 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;
 }