1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
4 This file is part of libatasmart.
6 Copyright 2008 Lennart Poettering
8 libatasmart is free software; you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as
10 published by the Free Software Foundation, either version 2.1 of the
11 License, or (at your option) any later version.
13 libatasmart is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with libatasmart. If not, If not, see
20 <http://www.gnu.org/licenses/>.
27 #include <arpa/inet.h>
37 #include <sys/ioctl.h>
38 #include <scsi/scsi.h>
40 #include <scsi/scsi_ioctl.h>
41 #include <linux/hdreg.h>
46 #define SK_TIMEOUT 2000
48 typedef enum SkDirection {
55 typedef enum SkDiskType {
56 SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
69 uint8_t identify[512];
70 uint8_t smart_data[512];
71 uint8_t smart_threshold_data[512];
73 SkBool identify_data_valid:1;
74 SkBool smart_data_valid:1;
75 SkBool smart_threshold_data_valid:1;
77 SkIdentifyParsedData identify_parsed_data;
78 SkSmartParsedData smart_parsed_data;
82 typedef enum SkAtaCommand {
83 SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
84 SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
85 SK_ATA_COMMAND_SMART = 0xB0,
86 SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
89 /* ATA SMART subcommands (ATA8 7.52.1) */
90 typedef enum SkSmartCommand {
91 SK_SMART_COMMAND_READ_DATA = 0xD0,
92 SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
93 SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
94 SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
95 SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
96 SK_SMART_COMMAND_RETURN_STATUS = 0xDA
99 static SkBool disk_smart_is_available(SkDisk *d) {
100 return d->identify_data_valid && !!(d->identify[164] & 1);
103 static SkBool disk_smart_is_enabled(SkDisk *d) {
104 return d->identify_data_valid && !!(d->identify[170] & 1);
107 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
108 assert(d->smart_data_valid);
110 return !!(d->smart_data[367] & 32);
112 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
113 assert(d->smart_data_valid);
115 return !!(d->smart_data[367] & 16);
118 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
119 assert(d->smart_data_valid);
121 return !!(d->smart_data[367] & 1);
124 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
125 assert(d->smart_data_valid);
127 return !!(d->smart_data[367] & 41);
130 static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
131 uint8_t *bytes = cmd_data;
134 assert(d->type == SK_DISK_TYPE_ATA);
138 case SK_DIRECTION_OUT:
140 /* We could use HDIO_DRIVE_TASKFILE here, but
141 * that's a deprecated ioctl(), hence we don't
142 * do it. And we don't need writing anyway. */
147 case SK_DIRECTION_IN: {
150 /* We have HDIO_DRIVE_CMD which can only read, but not write,
151 * and cannot do LBA. We use it for all read commands. */
153 ioctl_data = alloca(4 + *len);
154 memset(ioctl_data, 0, 4 + *len);
156 ioctl_data[0] = (uint8_t) command; /* COMMAND */
157 ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3]; /* SECTOR/NSECTOR */
158 ioctl_data[2] = bytes[1]; /* FEATURE */
159 ioctl_data[3] = bytes[3]; /* NSECTOR */
161 if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
164 memset(bytes, 0, 12);
165 bytes[11] = ioctl_data[0];
166 bytes[1] = ioctl_data[1];
167 bytes[3] = ioctl_data[2];
169 memcpy(data, ioctl_data+4, *len);
174 case SK_DIRECTION_NONE: {
175 uint8_t ioctl_data[7];
177 /* We have HDIO_DRIVE_TASK which can neither read nor
178 * write, but can do LBA. We use it for all commands that
179 * do neither read nor write */
181 memset(ioctl_data, 0, sizeof(ioctl_data));
183 ioctl_data[0] = (uint8_t) command; /* COMMAND */
184 ioctl_data[1] = bytes[1]; /* FEATURE */
185 ioctl_data[2] = bytes[3]; /* NSECTOR */
187 ioctl_data[3] = bytes[9]; /* LBA LOW */
188 ioctl_data[4] = bytes[8]; /* LBA MID */
189 ioctl_data[5] = bytes[7]; /* LBA HIGH */
190 ioctl_data[6] = bytes[10]; /* SELECT */
192 if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
195 memset(bytes, 0, 12);
196 bytes[11] = ioctl_data[0];
197 bytes[1] = ioctl_data[1];
198 bytes[3] = ioctl_data[2];
200 bytes[9] = ioctl_data[3];
201 bytes[8] = ioctl_data[4];
202 bytes[7] = ioctl_data[5];
204 bytes[10] = ioctl_data[6];
215 /* Sends a SCSI command block */
216 static int sg_io(int fd, int direction,
217 const void *cdb, size_t cdb_len,
218 void *data, size_t data_len,
219 void *sense, size_t sense_len) {
221 struct sg_io_hdr io_hdr;
223 memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
225 io_hdr.interface_id = 'S';
226 io_hdr.cmdp = (unsigned char*) cdb;
227 io_hdr.cmd_len = cdb_len;
228 io_hdr.dxferp = data;
229 io_hdr.dxfer_len = data_len;
231 io_hdr.mx_sb_len = sense_len;
232 io_hdr.dxfer_direction = direction;
233 io_hdr.timeout = SK_TIMEOUT;
235 return ioctl(fd, SG_IO, &io_hdr);
238 static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
239 uint8_t *bytes = cmd_data;
242 uint8_t *desc = sense+8;
245 static const int direction_map[] = {
246 [SK_DIRECTION_NONE] = SG_DXFER_NONE,
247 [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
248 [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
251 assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
253 /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
254 * ATA Command Pass-Through":
255 * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
257 memset(cdb, 0, sizeof(cdb));
259 cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
261 if (direction == SK_DIRECTION_NONE) {
262 cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */
263 cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
265 } else if (direction == SK_DIRECTION_IN) {
266 cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */
267 cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
269 } else if (direction == SK_DIRECTION_OUT) {
270 cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */
271 cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
274 cdb[3] = bytes[0]; /* FEATURES */
277 cdb[5] = bytes[2]; /* SECTORS */
280 cdb[8] = bytes[9]; /* LBA LOW */
281 cdb[10] = bytes[8]; /* LBA MID */
282 cdb[12] = bytes[7]; /* LBA HIGH */
284 cdb[13] = bytes[10] & 0x4F; /* SELECT */
285 cdb[14] = (uint8_t) command;
287 if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
290 if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
295 memset(bytes, 0, 12);
303 bytes[10] = desc[12];
304 bytes[11] = desc[13];
309 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
311 static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = {
312 [SK_DISK_TYPE_ATA] = disk_ata_command,
313 [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
317 assert(d->type <= _SK_DISK_TYPE_MAX);
318 assert(direction <= _SK_DIRECTION_MAX);
320 assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
321 assert(direction != SK_DIRECTION_NONE || (!data && !len));
323 return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
326 static int disk_identify_device(SkDisk *d) {
331 memset(cmd, 0, sizeof(cmd));
335 if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
343 d->identify_data_valid = TRUE;
348 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
352 if (!d->identify_data_valid) {
357 memset(cmd, 0, sizeof(cmd));
359 if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
362 if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
367 *awake = ntohs(cmd[1]) == 0xFF;
372 static int disk_smart_enable(SkDisk *d, SkBool b) {
375 if (!disk_smart_is_available(d)) {
380 memset(cmd, 0, sizeof(cmd));
382 cmd[0] = htons(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
383 cmd[2] = htons(0x0000U);
384 cmd[3] = htons(0x00C2U);
385 cmd[4] = htons(0x4F00U);
387 return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
390 int sk_disk_smart_read_data(SkDisk *d) {
395 if (!disk_smart_is_available(d)) {
400 memset(cmd, 0, sizeof(cmd));
402 cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
404 cmd[2] = htons(0x0000U);
405 cmd[3] = htons(0x00C2U);
406 cmd[4] = htons(0x4F00U);
408 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
411 d->smart_data_valid = TRUE;
416 static int disk_smart_read_thresholds(SkDisk *d) {
421 if (!disk_smart_is_available(d)) {
426 memset(cmd, 0, sizeof(cmd));
428 cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
430 cmd[2] = htons(0x0000U);
431 cmd[3] = htons(0x00C2U);
432 cmd[4] = htons(0x4F00U);
434 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
437 d->smart_threshold_data_valid = TRUE;
442 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
446 if (!disk_smart_is_available(d)) {
451 memset(cmd, 0, sizeof(cmd));
453 cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
454 cmd[1] = htons(0x0000U);
455 cmd[3] = htons(0x00C2U);
456 cmd[4] = htons(0x4F00U);
458 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
461 if (cmd[3] == htons(0x00C2U) &&
462 cmd[4] == htons(0x4F00U))
464 else if (cmd[3] == htons(0x002CU) &&
465 cmd[4] == htons(0xF400U))
475 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
479 if (!disk_smart_is_available(d)) {
484 if (!d->smart_data_valid)
485 if ((ret = sk_disk_smart_read_data(d)) < 0)
488 assert(d->smart_data_valid);
490 if (test != SK_SMART_SELF_TEST_SHORT &&
491 test != SK_SMART_SELF_TEST_EXTENDED &&
492 test != SK_SMART_SELF_TEST_CONVEYANCE &&
493 test != SK_SMART_SELF_TEST_ABORT) {
498 if (!disk_smart_is_start_test_available(d)
499 || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d))
500 || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d))
501 || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) {
506 if (test == SK_SMART_SELF_TEST_ABORT &&
507 !disk_smart_is_abort_test_available(d)) {
512 memset(cmd, 0, sizeof(cmd));
514 cmd[0] = htons(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE);
515 cmd[2] = htons(0x0000U);
516 cmd[3] = htons(0x00C2U);
517 cmd[4] = htons(0x4F00U | (uint16_t) test);
519 return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
522 static void swap_strings(char *s, size_t len) {
523 assert((len & 1) == 0);
525 for (; len > 0; s += 2, len -= 2) {
533 static void clean_strings(char *s) {
537 if (*e < ' ' || *e >= 127)
541 static void drop_spaces(char *s) {
543 SkBool prev_space = FALSE;
565 static void read_string(char *d, uint8_t *s, size_t len) {
568 swap_strings(d, len);
573 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
575 if (!d->identify_data_valid) {
580 read_string(d->identify_parsed_data.serial, d->identify+20, 20);
581 read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
582 read_string(d->identify_parsed_data.model, d->identify+54, 40);
584 *ipd = &d->identify_parsed_data;
589 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
591 if (!d->identify_data_valid) {
596 *b = disk_smart_is_available(d);
600 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
602 *b = d->identify_data_valid;
606 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
608 static const char* const map[] = {
609 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
610 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
611 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
612 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
613 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
614 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
615 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
618 if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
624 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
626 static const char* const map[] = {
627 [SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER] = "The previous self-test routine completed without error or no self-test has ever been run.",
628 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.",
629 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.",
630 [SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL] = "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.",
631 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN] = "The previous self-test completed having a test element that failed and the test element that failed.",
632 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.",
633 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO] = "The previous self-test completed having the servo (and/or seek) test element of the test failed.",
634 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.",
635 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING] = "The previous self-test completed having a test element that failed and the device is suspected of having handling damage.",
636 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress"
639 if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
645 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
648 case SK_SMART_SELF_TEST_SHORT:
650 case SK_SMART_SELF_TEST_EXTENDED:
652 case SK_SMART_SELF_TEST_CONVEYANCE:
654 case SK_SMART_SELF_TEST_ABORT:
661 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
663 if (!d->start_test_available)
667 case SK_SMART_SELF_TEST_SHORT:
668 case SK_SMART_SELF_TEST_EXTENDED:
669 return d->short_and_extended_test_available;
670 case SK_SMART_SELF_TEST_CONVEYANCE:
671 return d->conveyance_test_available;
672 case SK_SMART_SELF_TEST_ABORT:
673 return d->abort_test_available;
679 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
681 if (!sk_smart_self_test_available(d, test))
685 case SK_SMART_SELF_TEST_SHORT:
686 return d->short_test_polling_minutes;
687 case SK_SMART_SELF_TEST_EXTENDED:
688 return d->extended_test_polling_minutes;
689 case SK_SMART_SELF_TEST_CONVEYANCE:
690 return d->conveyance_test_polling_minutes;
696 typedef struct SkSmartAttributeInfo {
698 SkSmartAttributeUnit unit;
699 } SkSmartAttributeInfo;
701 /* This data is stolen from smartmontools */
702 static const SkSmartAttributeInfo const attribute_info[255] = {
703 [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
704 [2] = { "throughput-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
705 [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
706 [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
707 [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
708 [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
709 [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
710 [8] = { "seek-time-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
711 [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
712 [11] = { "calibration-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
713 [12] = { "power-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
714 [13] = { "read-soft-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
715 [187] = { "reported-uncorrect", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
716 [189] = { "high-fly-writes", SK_SMART_ATTRIBUTE_UNIT_NONE },
717 [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
718 [191] = { "g-sense-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
719 [192] = { "power-off-retract-count-1", SK_SMART_ATTRIBUTE_UNIT_NONE },
720 [193] = { "load-cycle-count-1", SK_SMART_ATTRIBUTE_UNIT_NONE },
721 [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
722 [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE },
723 [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
724 [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
725 [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
726 [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
727 [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
728 [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
729 [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
730 [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_NONE },
731 [204] = { "shock-count-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
732 [205] = { "shock-rate-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
733 [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
734 [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
735 [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
736 [209] = { "offline-seek-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
737 [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
738 [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
739 [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
740 [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
741 [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
742 [225] = { "load-cycle-count-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
743 [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
744 [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
745 [228] = { "power-off-retract-count-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
746 [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
747 [231] = { "temperature-celsius-1", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
748 [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
749 [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }
752 static void make_pretty(SkSmartAttributeParsedData *a) {
753 uint64_t fourtyeight;
758 if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
762 ((uint64_t) a->raw[0]) |
763 (((uint64_t) a->raw[1]) << 8) |
764 (((uint64_t) a->raw[2]) << 16) |
765 (((uint64_t) a->raw[3]) << 24) |
766 (((uint64_t) a->raw[4]) << 32) |
767 (((uint64_t) a->raw[5]) << 40);
769 if (!strcmp(a->name, "spin-up-time"))
770 a->pretty_value = fourtyeight & 0xFFFF;
771 else if (!strcmp(a->name, "airflow-temperature-celsius") ||
772 !strcmp(a->name, "temperature-celsius-1") ||
773 !strcmp(a->name, "temperature-celsius-2")) {
774 a->pretty_value = (fourtyeight & 0xFFFF)*1000 + 273150;
775 } else if (!strcmp(a->name, "power-on-minutes"))
776 a->pretty_value = fourtyeight * 60 * 1000;
777 else if (!strcmp(a->name, "power-on-seconds"))
778 a->pretty_value = fourtyeight * 1000;
779 else if (!strcmp(a->name, "power-on-hours") ||
780 !strcmp(a->name, "loaded-hours") ||
781 !strcmp(a->name, "head-flying-hours"))
782 a->pretty_value = fourtyeight * 60 * 60 * 1000;
784 a->pretty_value = fourtyeight;
787 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
788 const SkIdentifyParsedData *ipd;
790 /* These are the simple cases */
791 if (attribute_info[id].name)
792 return &attribute_info[id];
794 /* These are the complex ones */
795 if (sk_disk_identify_parse(d, &ipd) < 0)
799 /* We might want to add further special cases/quirks
800 * here eventually. */
804 static const SkSmartAttributeInfo maxtor = {
805 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
807 static const SkSmartAttributeInfo fujitsu = {
808 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
810 static const SkSmartAttributeInfo others = {
811 "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
814 if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
816 else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
826 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
828 if (!d->smart_data_valid) {
833 switch (d->smart_data[362]) {
836 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
841 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
845 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
850 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
855 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
860 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
864 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
868 d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
869 d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF;
871 d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
873 d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d);
874 d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d);
875 d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d);
876 d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d);
878 d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
879 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]);
880 d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
882 *spd = &d->smart_parsed_data;
887 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
891 if (!d->smart_threshold_data_valid) {
892 a->threshold_valid = FALSE;
896 for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
901 a->threshold_valid = FALSE;
902 a->good_valid = FALSE;
907 a->threshold_valid = p[1] != 0xFE;
909 a->good_valid = FALSE;
912 /* Always-Fail and Always-Pssing thresholds are not relevant
913 * for our assessment. */
914 if (p[1] >= 1 && p[1] <= 0xFD) {
916 if (a->worst_value_valid) {
917 a->good = a->good && (a->worst_value > a->threshold);
918 a->good_valid = TRUE;
921 if (a->current_value_valid) {
922 a->good = a->good && (a->current_value > a->threshold);
923 a->good_valid = TRUE;
928 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
932 if (!d->smart_data_valid) {
937 for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
938 SkSmartAttributeParsedData a;
939 const SkSmartAttributeInfo *i;
945 memset(&a, 0, sizeof(a));
947 a.current_value = p[3];
948 a.current_value_valid = p[3] >= 1 && p[3] <= 0xFD;
949 a.worst_value = p[4];
950 a.worst_value_valid = p[4] >= 1 && p[4] <= 0xFD;
952 a.flags = ((uint16_t) p[2] << 8) | p[1];
953 a.prefailure = !!(p[1] & 1);
954 a.online = !!(p[1] & 2);
956 memcpy(a.raw, p+5, 6);
958 if ((i = lookup_attribute(d, p[0]))) {
960 a.pretty_unit = i->unit;
962 if (asprintf(&an, "attribute-%u", a.id) < 0) {
968 a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
973 find_threshold(d, &a);
983 static const char *yes_no(SkBool b) {
984 return b ? "yes" : "no";
987 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
989 const char * const map[] = {
990 [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
991 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
992 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
993 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
994 [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
997 if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
1003 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
1008 snprintf(s, len, "%u", id);
1016 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1018 switch (a->pretty_unit) {
1019 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1021 if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
1022 snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
1023 else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
1024 snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
1025 else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
1026 snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
1027 else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
1028 snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
1029 else if (a->pretty_value >= 1000LLU*60LLU)
1030 snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
1031 else if (a->pretty_value >= 1000LLU)
1032 snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
1034 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1038 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1039 snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1042 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1043 snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1046 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1047 snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1050 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1051 snprintf(s, len, "n/a");
1054 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1063 #define HIGHLIGHT "\x1B[1m"
1064 #define ENDHIGHLIGHT "\x1B[0m"
1066 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1069 char tt[32], tw[32], tc[32];
1072 snprintf(tt, sizeof(tt), "%3u", a->threshold);
1073 tt[sizeof(tt)-1] = 0;
1074 snprintf(tw, sizeof(tw), "%3u", a->worst_value);
1075 tw[sizeof(tw)-1] = 0;
1076 snprintf(tc, sizeof(tc), "%3u", a->current_value);
1077 tc[sizeof(tc)-1] = 0;
1079 highlight = a->good_valid && !a->good && isatty(1);
1082 fprintf(stderr, HIGHLIGHT);
1084 printf("%3u %-27s %-3s %-3s %-3s %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-3s\n",
1086 print_name(name, sizeof(name), a->id, a->name),
1087 a->current_value_valid ? tc : "n/a",
1088 a->worst_value_valid ? tw : "n/a",
1089 a->threshold_valid ? tt : "n/a",
1090 print_value(pretty, sizeof(pretty), a),
1091 a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5],
1092 a->prefailure ? "prefail" : "old-age",
1093 a->online ? "online" : "offline",
1094 a->good_valid ? yes_no(a->good) : "n/a");
1097 fprintf(stderr, ENDHIGHLIGHT);
1100 int sk_disk_dump(SkDisk *d) {
1102 SkBool awake = FALSE;
1104 printf("Device: %s\n"
1107 (unsigned long) (d->size/1024/1024));
1109 if (d->identify_data_valid) {
1110 const SkIdentifyParsedData *ipd;
1112 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1115 printf("Model: [%s]\n"
1118 "SMART Available: %s\n",
1122 yes_no(disk_smart_is_available(d)));
1125 ret = sk_disk_check_sleep_mode(d, &awake);
1126 printf("Awake: %s\n",
1127 ret >= 0 ? yes_no(awake) : "unknown");
1129 if (disk_smart_is_available(d)) {
1130 const SkSmartParsedData *spd;
1133 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1136 printf("Disk Health Good: %s\n",
1139 if ((ret = sk_disk_smart_read_data(d)) < 0)
1142 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1145 printf("Off-line Data Collection Status: [%s]\n"
1146 "Total Time To Complete Off-Line Data Collection: %u s\n"
1147 "Self-Test Execution Status: [%s]\n"
1148 "Percent Self-Test Remaining: %u%%\n"
1149 "Conveyance Self-Test Available: %s\n"
1150 "Short/Extended Self-Test Available: %s\n"
1151 "Start Self-Test Available: %s\n"
1152 "Abort Self-Test Available: %s\n"
1153 "Short Self-Test Polling Time: %u min\n"
1154 "Extended Self-Test Polling Time: %u min\n"
1155 "Conveyance Self-Test Polling Time: %u min\n",
1156 sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
1157 spd->total_offline_data_collection_seconds,
1158 sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
1159 spd->self_test_execution_percent_remaining,
1160 yes_no(spd->conveyance_test_available),
1161 yes_no(spd->short_and_extended_test_available),
1162 yes_no(spd->start_test_available),
1163 yes_no(spd->abort_test_available),
1164 spd->short_test_polling_minutes,
1165 spd->extended_test_polling_minutes,
1166 spd->conveyance_test_polling_minutes);
1168 printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-3s\n",
1180 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1187 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1193 int sk_disk_open(const char *name, SkDisk **_d) {
1201 if (!(d = calloc(1, sizeof(SkDisk)))) {
1206 if (!(d->name = strdup(name))) {
1211 if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1216 if ((ret = fstat(d->fd, &st)) < 0)
1219 if (!S_ISBLK(st.st_mode)) {
1225 /* So, it's a block device. Let's make sure the ioctls work */
1227 if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1230 if (d->size <= 0 || d->size == (uint64_t) -1) {
1236 /* OK, it's a real block device with a size. Find a way to
1237 * identify the device. */
1238 for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
1239 if (disk_identify_device(d) >= 0)
1242 /* Check if driver can do SMART, and enable if necessary */
1243 if (disk_smart_is_available(d)) {
1245 if (!disk_smart_is_enabled(d)) {
1246 if ((ret = disk_smart_enable(d, TRUE)) < 0)
1249 if ((ret = disk_identify_device(d)) < 0)
1252 if (!disk_smart_is_enabled(d)) {
1259 disk_smart_read_thresholds(d);
1274 void sk_disk_free(SkDisk *d) {