| /* 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; |
| } |