--- /dev/null
+/* 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, §ors) < 0) {
+ if (errno != ENOENT)
+ return -1;
+ sectors = 0;
+ } else {
+
+ /* We use log2(n_sectors)*1024 as a threshold here. We
+ * had to pick something, and this makes a bit of
+ * sense, or doesn't it? */
+ sector_threshold = u64log2(d->size/512) * 1024;
+
+ if (sectors >= sector_threshold) {
+ *overall = SK_SMART_OVERALL_BAD_SECTOR_MANY;
+ return 0;
+ }
+ }
+
+ /* Third, check if any of the SMART attributes is bad */
+ if (fill_cache (d) < 0)
+ return -1;
+
+ if (d->bad_attribute_now) {
+ *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE_NOW;
+ return 0;
+ }
+
+ /* Fourth, check if there are any bad sectors at all */
+ if (sectors > 0) {
+ *overall = SK_SMART_OVERALL_BAD_SECTOR;
+ return 0;
+ }
+
+ /* Fifth, check if any of the SMART attributes ever was bad */
+ if (d->bad_attribute_in_the_past) {
+ *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE_IN_THE_PAST;
+ return 0;
+ }
+
+ /* Sixth, there's really nothing to complain about, so give it a pass */
+ *overall = SK_SMART_OVERALL_GOOD;
+ return 0;
+}
+
+static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
+
+ if (k)
+ strncpy(s, k, len);
+ else
+ snprintf(s, len, "%u", id);
+
+ s[len-1] = 0;
+
+ return s;
+}
+
+static char *print_value(char *s, size_t len, uint64_t pretty_value, SkSmartAttributeUnit pretty_unit) {
+
+ switch (pretty_unit) {
+ case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
+
+ if (pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
+ snprintf(s, len, "%0.1f years", ((double) pretty_value)/(1000.0*60*60*24*365));
+ else if (pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
+ snprintf(s, len, "%0.1f months", ((double) pretty_value)/(1000.0*60*60*24*30));
+ else if (pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
+ snprintf(s, len, "%0.1f days", ((double) pretty_value)/(1000.0*60*60*24));
+ else if (pretty_value >= 1000LLU*60LLU*60LLU)
+ snprintf(s, len, "%0.1f h", ((double) pretty_value)/(1000.0*60*60));
+ else if (pretty_value >= 1000LLU*60LLU)
+ snprintf(s, len, "%0.1f min", ((double) pretty_value)/(1000.0*60));
+ else if (pretty_value >= 1000LLU)
+ snprintf(s, len, "%0.1f s", ((double) pretty_value)/(1000.0));
+ else
+ snprintf(s, len, "%llu ms", (unsigned long long) pretty_value);
+
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
+ snprintf(s, len, "%0.1f C", ((double) pretty_value - 273150) / 1000);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
+ snprintf(s, len, "%llu sectors", (unsigned long long) pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_PERCENT:
+ snprintf(s, len, "%llu%%", (unsigned long long) pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT:
+ snprintf(s, len, "%0.3f%%", (double) pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_MB:
+ if (pretty_value >= 1000000LLU)
+ snprintf(s, len, "%0.3f TB", (double) pretty_value / 1000000LLU);
+ else if (pretty_value >= 1000LLU)
+ snprintf(s, len, "%0.3f GB", (double) pretty_value / 1000LLU);
+ else
+ snprintf(s, len, "%llu MB", (unsigned long long) pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_NONE:
+ snprintf(s, len, "%llu", (unsigned long long) pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
+ snprintf(s, len, "n/a");
+ break;
+
+ case _SK_SMART_ATTRIBUTE_UNIT_MAX:
+ assert(FALSE);
+ }
+
+ s[len-1] = 0;
+
+ return s;
+}
+
+#define HIGHLIGHT "\x1B[1m"
+#define ENDHIGHLIGHT "\x1B[0m"
+
+static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
+ char name[32];
+ char pretty[32];
+ char tt[32], tw[32], tc[32];
+ SkBool highlight;
+
+ snprintf(tt, sizeof(tt), "%3u", a->threshold);
+ tt[sizeof(tt)-1] = 0;
+ snprintf(tw, sizeof(tw), "%3u", a->worst_value);
+ tw[sizeof(tw)-1] = 0;
+ snprintf(tc, sizeof(tc), "%3u", a->current_value);
+ tc[sizeof(tc)-1] = 0;
+
+ highlight = a->warn && isatty(1);
+
+ if (highlight)
+ fprintf(stderr, HIGHLIGHT);
+
+ printf("%3u %-27s %-3s %-3s %-3s %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-4s %-4s\n",
+ a->id,
+ print_name(name, sizeof(name), a->id, a->name),
+ a->current_value_valid ? tc : "n/a",
+ a->worst_value_valid ? tw : "n/a",
+ a->threshold_valid ? tt : "n/a",
+ print_value(pretty, sizeof(pretty), a->pretty_value, a->pretty_unit),
+ a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5],
+ a->prefailure ? "prefail" : "old-age",
+ a->online ? "online" : "offline",
+ a->good_now_valid ? yes_no(a->good_now) : "n/a",
+ a->good_in_the_past_valid ? yes_no(a->good_in_the_past) : "n/a");
+
+ if (highlight)
+ fprintf(stderr, ENDHIGHLIGHT);
+}
+
+int sk_disk_dump(SkDisk *d) {
+ int ret;
+ SkBool awake = FALSE;
+ uint64_t size;
+
+ assert(d);
+
+ printf("Device: %s%s%s\n"
+ "Type: %s\n",
+ d->name ? disk_type_to_prefix_string(d->type) : "",
+ d->name ? ":" : "",
+ d->name ? d->name : "n/a",
+ disk_type_to_human_string(d->type));
+
+ ret = sk_disk_get_size(d, &size);
+ if (ret >= 0)
+ printf("Size: %lu MiB\n", (unsigned long) (d->size/1024/1024));
+ else
+ printf("Size: %s\n", strerror(errno));
+
+ if (d->identify_valid) {
+ const SkIdentifyParsedData *ipd;
+ SkSmartQuirk quirk = 0;
+ unsigned i;
+
+ if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
+ return ret;
+
+ printf("Model: [%s]\n"
+ "Serial: [%s]\n"
+ "Firmware: [%s]\n"
+ "SMART Available: %s\n",
+ ipd->model,
+ ipd->serial,
+ ipd->firmware,
+ yes_no(disk_smart_is_available(d)));
+
+ if ((ret = lookup_quirks(ipd->model, ipd->firmware, &quirk)))
+ return ret;
+
+ printf("Quirks:");
+
+ for (i = 0; quirk_name[i]; i++)
+ if (quirk & (1<<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;
+}