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/>.
29 #include <arpa/inet.h>
39 #include <sys/ioctl.h>
40 #include <scsi/scsi.h>
42 #include <scsi/scsi_ioctl.h>
43 #include <linux/hdreg.h>
48 #define SK_TIMEOUT 2000
50 typedef enum SkDirection {
57 typedef enum SkDiskType {
58 SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
71 uint8_t identify[512];
72 uint8_t smart_data[512];
73 uint8_t smart_threshold_data[512];
75 SkBool identify_data_valid:1;
76 SkBool smart_data_valid:1;
77 SkBool smart_threshold_data_valid:1;
79 SkIdentifyParsedData identify_parsed_data;
80 SkSmartParsedData smart_parsed_data;
84 typedef enum SkAtaCommand {
85 SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
86 SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
87 SK_ATA_COMMAND_SMART = 0xB0,
88 SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
91 /* ATA SMART subcommands (ATA8 7.52.1) */
92 typedef enum SkSmartCommand {
93 SK_SMART_COMMAND_READ_DATA = 0xD0,
94 SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
95 SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
96 SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
97 SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
98 SK_SMART_COMMAND_RETURN_STATUS = 0xDA
101 static SkBool disk_smart_is_available(SkDisk *d) {
102 return d->identify_data_valid && !!(d->identify[164] & 1);
105 static SkBool disk_smart_is_enabled(SkDisk *d) {
106 return d->identify_data_valid && !!(d->identify[170] & 1);
109 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
110 assert(d->smart_data_valid);
112 return !!(d->smart_data[367] & 32);
114 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
115 assert(d->smart_data_valid);
117 return !!(d->smart_data[367] & 16);
120 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
121 assert(d->smart_data_valid);
123 return !!(d->smart_data[367] & 1);
126 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
127 assert(d->smart_data_valid);
129 return !!(d->smart_data[367] & 41);
132 static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
133 uint8_t *bytes = cmd_data;
136 assert(d->type == SK_DISK_TYPE_ATA);
140 case SK_DIRECTION_OUT:
142 /* We could use HDIO_DRIVE_TASKFILE here, but
143 * that's a deprecated ioctl(), hence we don't
144 * do it. And we don't need writing anyway. */
149 case SK_DIRECTION_IN: {
152 /* We have HDIO_DRIVE_CMD which can only read, but not write,
153 * and cannot do LBA. We use it for all read commands. */
155 ioctl_data = alloca(4 + *len);
156 memset(ioctl_data, 0, 4 + *len);
158 ioctl_data[0] = (uint8_t) command; /* COMMAND */
159 ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3]; /* SECTOR/NSECTOR */
160 ioctl_data[2] = bytes[1]; /* FEATURE */
161 ioctl_data[3] = bytes[3]; /* NSECTOR */
163 if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
166 memset(bytes, 0, 12);
167 bytes[11] = ioctl_data[0];
168 bytes[1] = ioctl_data[1];
169 bytes[3] = ioctl_data[2];
171 memcpy(data, ioctl_data+4, *len);
176 case SK_DIRECTION_NONE: {
177 uint8_t ioctl_data[7];
179 /* We have HDIO_DRIVE_TASK which can neither read nor
180 * write, but can do LBA. We use it for all commands that
181 * do neither read nor write */
183 memset(ioctl_data, 0, sizeof(ioctl_data));
185 ioctl_data[0] = (uint8_t) command; /* COMMAND */
186 ioctl_data[1] = bytes[1]; /* FEATURE */
187 ioctl_data[2] = bytes[3]; /* NSECTOR */
189 ioctl_data[3] = bytes[9]; /* LBA LOW */
190 ioctl_data[4] = bytes[8]; /* LBA MID */
191 ioctl_data[5] = bytes[7]; /* LBA HIGH */
192 ioctl_data[6] = bytes[10]; /* SELECT */
194 if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
197 memset(bytes, 0, 12);
198 bytes[11] = ioctl_data[0];
199 bytes[1] = ioctl_data[1];
200 bytes[3] = ioctl_data[2];
202 bytes[9] = ioctl_data[3];
203 bytes[8] = ioctl_data[4];
204 bytes[7] = ioctl_data[5];
206 bytes[10] = ioctl_data[6];
217 /* Sends a SCSI command block */
218 static int sg_io(int fd, int direction,
219 const void *cdb, size_t cdb_len,
220 void *data, size_t data_len,
221 void *sense, size_t sense_len) {
223 struct sg_io_hdr io_hdr;
225 memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
227 io_hdr.interface_id = 'S';
228 io_hdr.cmdp = (unsigned char*) cdb;
229 io_hdr.cmd_len = cdb_len;
230 io_hdr.dxferp = data;
231 io_hdr.dxfer_len = data_len;
233 io_hdr.mx_sb_len = sense_len;
234 io_hdr.dxfer_direction = direction;
235 io_hdr.timeout = SK_TIMEOUT;
237 return ioctl(fd, SG_IO, &io_hdr);
240 static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
241 uint8_t *bytes = cmd_data;
244 uint8_t *desc = sense+8;
247 static const int direction_map[] = {
248 [SK_DIRECTION_NONE] = SG_DXFER_NONE,
249 [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
250 [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
253 assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
255 /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
256 * ATA Command Pass-Through":
257 * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
259 memset(cdb, 0, sizeof(cdb));
261 cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
263 if (direction == SK_DIRECTION_NONE) {
264 cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */
265 cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
267 } else if (direction == SK_DIRECTION_IN) {
268 cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */
269 cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
271 } else if (direction == SK_DIRECTION_OUT) {
272 cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */
273 cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
276 cdb[3] = bytes[0]; /* FEATURES */
279 cdb[5] = bytes[2]; /* SECTORS */
282 cdb[8] = bytes[9]; /* LBA LOW */
283 cdb[10] = bytes[8]; /* LBA MID */
284 cdb[12] = bytes[7]; /* LBA HIGH */
286 cdb[13] = bytes[10] & 0x4F; /* SELECT */
287 cdb[14] = (uint8_t) command;
289 if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
292 if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
297 memset(bytes, 0, 12);
305 bytes[10] = desc[12];
306 bytes[11] = desc[13];
311 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
313 static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = {
314 [SK_DISK_TYPE_ATA] = disk_ata_command,
315 [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
319 assert(d->type <= _SK_DISK_TYPE_MAX);
320 assert(direction <= _SK_DIRECTION_MAX);
322 assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
323 assert(direction != SK_DIRECTION_NONE || (!data && !len));
325 return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
328 static int disk_identify_device(SkDisk *d) {
333 memset(cmd, 0, sizeof(cmd));
337 if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
345 d->identify_data_valid = TRUE;
350 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
354 if (!d->identify_data_valid) {
359 memset(cmd, 0, sizeof(cmd));
361 if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
364 if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
369 *awake = ntohs(cmd[1]) == 0xFF;
374 static int disk_smart_enable(SkDisk *d, SkBool b) {
377 if (!disk_smart_is_available(d)) {
382 memset(cmd, 0, sizeof(cmd));
384 cmd[0] = htons(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
385 cmd[2] = htons(0x0000U);
386 cmd[3] = htons(0x00C2U);
387 cmd[4] = htons(0x4F00U);
389 return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
392 int sk_disk_smart_read_data(SkDisk *d) {
397 if (!disk_smart_is_available(d)) {
402 memset(cmd, 0, sizeof(cmd));
404 cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
406 cmd[2] = htons(0x0000U);
407 cmd[3] = htons(0x00C2U);
408 cmd[4] = htons(0x4F00U);
410 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
413 d->smart_data_valid = TRUE;
418 static int disk_smart_read_thresholds(SkDisk *d) {
423 if (!disk_smart_is_available(d)) {
428 memset(cmd, 0, sizeof(cmd));
430 cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
432 cmd[2] = htons(0x0000U);
433 cmd[3] = htons(0x00C2U);
434 cmd[4] = htons(0x4F00U);
436 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
439 d->smart_threshold_data_valid = TRUE;
444 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
448 if (!disk_smart_is_available(d)) {
453 memset(cmd, 0, sizeof(cmd));
455 cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
456 cmd[1] = htons(0x0000U);
457 cmd[3] = htons(0x00C2U);
458 cmd[4] = htons(0x4F00U);
460 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
463 if (cmd[3] == htons(0x00C2U) &&
464 cmd[4] == htons(0x4F00U))
466 else if (cmd[3] == htons(0x002CU) &&
467 cmd[4] == htons(0xF400U))
477 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
481 if (!disk_smart_is_available(d)) {
486 if (!d->smart_data_valid)
487 if ((ret = sk_disk_smart_read_data(d)) < 0)
490 assert(d->smart_data_valid);
492 if (test != SK_SMART_SELF_TEST_SHORT &&
493 test != SK_SMART_SELF_TEST_EXTENDED &&
494 test != SK_SMART_SELF_TEST_CONVEYANCE &&
495 test != SK_SMART_SELF_TEST_ABORT) {
500 if (!disk_smart_is_start_test_available(d)
501 || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d))
502 || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d))
503 || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) {
508 if (test == SK_SMART_SELF_TEST_ABORT &&
509 !disk_smart_is_abort_test_available(d)) {
514 memset(cmd, 0, sizeof(cmd));
516 cmd[0] = htons(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE);
517 cmd[2] = htons(0x0000U);
518 cmd[3] = htons(0x00C2U);
519 cmd[4] = htons(0x4F00U | (uint16_t) test);
521 return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
524 static void swap_strings(char *s, size_t len) {
525 assert((len & 1) == 0);
527 for (; len > 0; s += 2, len -= 2) {
535 static void clean_strings(char *s) {
539 if (*e < ' ' || *e >= 127)
543 static void drop_spaces(char *s) {
545 SkBool prev_space = FALSE;
567 static void read_string(char *d, uint8_t *s, size_t len) {
570 swap_strings(d, len);
575 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
577 if (!d->identify_data_valid) {
582 read_string(d->identify_parsed_data.serial, d->identify+20, 20);
583 read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
584 read_string(d->identify_parsed_data.model, d->identify+54, 40);
586 *ipd = &d->identify_parsed_data;
591 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
593 if (!d->identify_data_valid) {
598 *b = disk_smart_is_available(d);
602 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
604 *b = d->identify_data_valid;
608 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
610 static const char* const map[] = {
611 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
612 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
613 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
614 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
615 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
616 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
617 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
620 if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
626 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
628 static const char* const map[] = {
629 [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.",
630 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.",
631 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.",
632 [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.",
633 [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.",
634 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.",
635 [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.",
636 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.",
637 [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.",
638 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress"
641 if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
647 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
650 case SK_SMART_SELF_TEST_SHORT:
652 case SK_SMART_SELF_TEST_EXTENDED:
654 case SK_SMART_SELF_TEST_CONVEYANCE:
656 case SK_SMART_SELF_TEST_ABORT:
663 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
665 if (!d->start_test_available)
669 case SK_SMART_SELF_TEST_SHORT:
670 case SK_SMART_SELF_TEST_EXTENDED:
671 return d->short_and_extended_test_available;
672 case SK_SMART_SELF_TEST_CONVEYANCE:
673 return d->conveyance_test_available;
674 case SK_SMART_SELF_TEST_ABORT:
675 return d->abort_test_available;
681 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
683 if (!sk_smart_self_test_available(d, test))
687 case SK_SMART_SELF_TEST_SHORT:
688 return d->short_test_polling_minutes;
689 case SK_SMART_SELF_TEST_EXTENDED:
690 return d->extended_test_polling_minutes;
691 case SK_SMART_SELF_TEST_CONVEYANCE:
692 return d->conveyance_test_polling_minutes;
698 typedef struct SkSmartAttributeInfo {
700 SkSmartAttributeUnit unit;
701 } SkSmartAttributeInfo;
703 /* This data is stolen from smartmontools */
704 static const SkSmartAttributeInfo const attribute_info[255] = {
705 [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
706 [2] = { "throughput-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
707 [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
708 [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
709 [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
710 [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
711 [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
712 [8] = { "seek-time-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
713 [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
714 [11] = { "calibration-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
715 [12] = { "power-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
716 [13] = { "read-soft-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
717 [187] = { "reported-uncorrect", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
718 [189] = { "high-fly-writes", SK_SMART_ATTRIBUTE_UNIT_NONE },
719 [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
720 [191] = { "g-sense-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
721 [192] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
722 [193] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
723 [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
724 [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE },
725 [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
726 [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
727 [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
728 [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
729 [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
730 [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
731 [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
732 [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_NONE },
733 [204] = { "shock-count-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
734 [205] = { "shock-rate-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
735 [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
736 [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
737 [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
738 [209] = { "offline-seek-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
739 [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
740 [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
741 [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
742 [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
743 [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
744 [225] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
745 [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
746 [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
747 [228] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
748 [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
749 [231] = { "temperature-celsius-1", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
750 [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
751 [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }
754 static void make_pretty(SkSmartAttributeParsedData *a) {
755 uint64_t fourtyeight;
760 if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
764 ((uint64_t) a->raw[0]) |
765 (((uint64_t) a->raw[1]) << 8) |
766 (((uint64_t) a->raw[2]) << 16) |
767 (((uint64_t) a->raw[3]) << 24) |
768 (((uint64_t) a->raw[4]) << 32) |
769 (((uint64_t) a->raw[5]) << 40);
771 if (!strcmp(a->name, "spin-up-time"))
772 a->pretty_value = fourtyeight & 0xFFFF;
773 else if (!strcmp(a->name, "airflow-temperature-celsius") ||
774 !strcmp(a->name, "temperature-celsius-1") ||
775 !strcmp(a->name, "temperature-celsius-2")) {
776 a->pretty_value = (fourtyeight & 0xFFFF)*1000 + 273150;
777 } else if (!strcmp(a->name, "power-on-minutes"))
778 a->pretty_value = fourtyeight * 60 * 1000;
779 else if (!strcmp(a->name, "power-on-seconds"))
780 a->pretty_value = fourtyeight * 1000;
781 else if (!strcmp(a->name, "power-on-hours") ||
782 !strcmp(a->name, "loaded-hours") ||
783 !strcmp(a->name, "head-flying-hours"))
784 a->pretty_value = fourtyeight * 60 * 60 * 1000;
786 a->pretty_value = fourtyeight;
789 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
790 const SkIdentifyParsedData *ipd;
792 /* These are the simple cases */
793 if (attribute_info[id].name)
794 return &attribute_info[id];
796 /* These are the complex ones */
797 if (sk_disk_identify_parse(d, &ipd) < 0)
801 /* We might want to add further special cases/quirks
802 * here eventually. */
806 static const SkSmartAttributeInfo maxtor = {
807 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
809 static const SkSmartAttributeInfo fujitsu = {
810 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
812 static const SkSmartAttributeInfo others = {
813 "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
816 if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
818 else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
828 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
830 if (!d->smart_data_valid) {
835 switch (d->smart_data[362]) {
838 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
843 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
847 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
852 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
857 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
862 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
866 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
870 d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
871 d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF;
873 d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
875 d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d);
876 d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d);
877 d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d);
878 d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d);
880 d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
881 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]);
882 d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
884 *spd = &d->smart_parsed_data;
889 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
893 if (!d->smart_threshold_data_valid) {
894 a->threshold_valid = FALSE;
898 for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
903 a->threshold_valid = FALSE;
908 a->threshold_valid = TRUE;
911 a->worst_value > a->threshold &&
912 a->current_value > a->threshold;
915 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
919 if (!d->smart_data_valid) {
924 for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
925 SkSmartAttributeParsedData a;
926 const SkSmartAttributeInfo *i;
932 memset(&a, 0, sizeof(a));
934 a.current_value = p[3];
935 a.worst_value = p[4];
937 a.flags = ((uint16_t) p[2] << 8) | p[1];
938 a.prefailure = !!(p[1] & 1);
939 a.online = !!(p[1] & 2);
941 memcpy(a.raw, p+5, 6);
943 if ((i = lookup_attribute(d, p[0]))) {
945 a.pretty_unit = i->unit;
947 if (asprintf(&an, "attribute-%u", a.id) < 0) {
953 a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
958 find_threshold(d, &a);
968 static const char *yes_no(SkBool b) {
969 return b ? "yes" : "no";
972 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
974 const char * const map[] = {
975 [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
976 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
977 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
978 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
979 [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
982 if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
988 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
993 snprintf(s, len, "%u", id);
1001 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1003 switch (a->pretty_unit) {
1004 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1006 if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
1007 snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
1008 else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
1009 snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
1010 else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
1011 snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
1012 else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
1013 snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
1014 else if (a->pretty_value >= 1000LLU*60LLU)
1015 snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
1016 else if (a->pretty_value >= 1000LLU)
1017 snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
1019 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1023 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1024 snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1027 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1028 snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1031 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1032 snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1035 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1036 snprintf(s, len, "n/a");
1039 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1048 #define HIGHLIGHT "\x1B[1m"
1049 #define ENDHIGHLIGHT "\x1B[0m"
1051 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1056 snprintf(t, sizeof(t), "%3u", a->threshold);
1059 if (!a->good && isatty(1))
1060 fprintf(stderr, HIGHLIGHT);
1062 printf("%3u %-27s %3u %3u %-3s %-11s %-7s %-7s %-3s\n",
1064 print_name(name, sizeof(name), a->id, a->name),
1067 a->threshold_valid ? t : "n/a",
1068 print_value(pretty, sizeof(pretty), a),
1069 a->prefailure ? "prefail" : "old-age",
1070 a->online ? "online" : "offline",
1073 if (!a->good && isatty(1))
1074 fprintf(stderr, ENDHIGHLIGHT);
1077 int sk_disk_dump(SkDisk *d) {
1079 SkBool awake = FALSE;
1081 printf("Device: %s\n"
1084 (unsigned long) (d->size/1024/1024));
1086 if (d->identify_data_valid) {
1087 const SkIdentifyParsedData *ipd;
1089 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1092 printf("Model: [%s]\n"
1095 "SMART Available: %s\n",
1099 yes_no(disk_smart_is_available(d)));
1102 ret = sk_disk_check_sleep_mode(d, &awake);
1103 printf("Awake: %s\n",
1104 ret >= 0 ? yes_no(awake) : "unknown");
1106 if (disk_smart_is_available(d)) {
1107 const SkSmartParsedData *spd;
1110 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1113 printf("Disk Health Good: %s\n",
1116 if ((ret = sk_disk_smart_read_data(d)) < 0)
1119 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1122 printf("Off-line Data Collection Status: [%s]\n"
1123 "Total Time To Complete Off-Line Data Collection: %u s\n"
1124 "Self-Test Execution Status: [%s]\n"
1125 "Percent Self-Test Remaining: %u%%\n"
1126 "Conveyance Self-Test Available: %s\n"
1127 "Short/Extended Self-Test Available: %s\n"
1128 "Start Self-Test Available: %s\n"
1129 "Abort Self-Test Available: %s\n"
1130 "Short Self-Test Polling Time: %u min\n"
1131 "Extended Self-Test Polling Time: %u min\n"
1132 "Conveyance Self-Test Polling Time: %u min\n",
1133 sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
1134 spd->total_offline_data_collection_seconds,
1135 sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
1136 spd->self_test_execution_percent_remaining,
1137 yes_no(spd->conveyance_test_available),
1138 yes_no(spd->short_and_extended_test_available),
1139 yes_no(spd->start_test_available),
1140 yes_no(spd->abort_test_available),
1141 spd->short_test_polling_minutes,
1142 spd->extended_test_polling_minutes,
1143 spd->conveyance_test_polling_minutes);
1145 printf("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n",
1156 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1163 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1169 int sk_disk_open(const char *name, SkDisk **_d) {
1177 if (!(d = calloc(1, sizeof(SkDisk)))) {
1182 if (!(d->name = strdup(name))) {
1187 if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1192 if ((ret = fstat(d->fd, &st)) < 0)
1195 if (!S_ISBLK(st.st_mode)) {
1201 /* So, it's a block device. Let's make sure the ioctls work */
1203 if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1206 if (d->size <= 0 || d->size == (uint64_t) -1) {
1212 /* OK, it's a real block device with a size. Find a way to
1213 * identify the device. */
1214 for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
1215 if (disk_identify_device(d) >= 0)
1218 /* Check if driver can do SMART, and enable if necessary */
1219 if (disk_smart_is_available(d)) {
1221 if (!disk_smart_is_enabled(d)) {
1222 if ((ret = disk_smart_enable(d, TRUE)) < 0)
1225 if ((ret = disk_identify_device(d)) < 0)
1228 if (!disk_smart_is_enabled(d)) {
1235 disk_smart_read_thresholds(d);
1250 void sk_disk_free(SkDisk *d) {