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>
50 #define SK_TIMEOUT 2000
52 typedef enum SkDirection {
59 typedef enum SkDiskType {
60 SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
73 uint8_t identify[512];
74 uint8_t smart_data[512];
75 uint8_t smart_threshold_data[512];
77 SkBool identify_data_valid:1;
78 SkBool smart_data_valid:1;
79 SkBool smart_threshold_data_valid:1;
81 SkIdentifyParsedData identify_parsed_data;
82 SkSmartParsedData smart_parsed_data;
86 typedef enum SkAtaCommand {
87 SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
88 SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
89 SK_ATA_COMMAND_SMART = 0xB0,
90 SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
93 /* ATA SMART subcommands (ATA8 7.52.1) */
94 typedef enum SkSmartCommand {
95 SK_SMART_COMMAND_READ_DATA = 0xD0,
96 SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
97 SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
98 SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
99 SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
100 SK_SMART_COMMAND_RETURN_STATUS = 0xDA
103 static SkBool disk_smart_is_available(SkDisk *d) {
104 return d->identify_data_valid && !!(d->identify[164] & 1);
107 static SkBool disk_smart_is_enabled(SkDisk *d) {
108 return d->identify_data_valid && !!(d->identify[170] & 1);
111 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
112 assert(d->smart_data_valid);
114 return !!(d->smart_data[367] & 32);
116 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
117 assert(d->smart_data_valid);
119 return !!(d->smart_data[367] & 16);
122 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
123 assert(d->smart_data_valid);
125 return !!(d->smart_data[367] & 1);
128 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
129 assert(d->smart_data_valid);
131 return !!(d->smart_data[367] & 41);
134 static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
135 uint8_t *bytes = cmd_data;
138 assert(d->type == SK_DISK_TYPE_ATA);
142 case SK_DIRECTION_OUT:
144 /* We could use HDIO_DRIVE_TASKFILE here, but
145 * that's a deprecated ioctl(), hence we don't
146 * do it. And we don't need writing anyway. */
151 case SK_DIRECTION_IN: {
154 /* We have HDIO_DRIVE_CMD which can only read, but not write,
155 * and cannot do LBA. We use it for all read commands. */
157 ioctl_data = alloca(4 + *len);
158 memset(ioctl_data, 0, 4 + *len);
160 ioctl_data[0] = (uint8_t) command; /* COMMAND */
161 ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3]; /* SECTOR/NSECTOR */
162 ioctl_data[2] = bytes[1]; /* FEATURE */
163 ioctl_data[3] = bytes[3]; /* NSECTOR */
165 if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
168 memset(bytes, 0, 12);
169 bytes[11] = ioctl_data[0];
170 bytes[1] = ioctl_data[1];
171 bytes[3] = ioctl_data[2];
173 memcpy(data, ioctl_data+4, *len);
178 case SK_DIRECTION_NONE: {
179 uint8_t ioctl_data[7];
181 /* We have HDIO_DRIVE_TASK which can neither read nor
182 * write, but can do LBA. We use it for all commands that
183 * do neither read nor write */
185 memset(ioctl_data, 0, sizeof(ioctl_data));
187 ioctl_data[0] = (uint8_t) command; /* COMMAND */
188 ioctl_data[1] = bytes[1]; /* FEATURE */
189 ioctl_data[2] = bytes[3]; /* NSECTOR */
191 ioctl_data[3] = bytes[9]; /* LBA LOW */
192 ioctl_data[4] = bytes[8]; /* LBA MID */
193 ioctl_data[5] = bytes[7]; /* LBA HIGH */
194 ioctl_data[6] = bytes[10]; /* SELECT */
196 if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
199 memset(bytes, 0, 12);
200 bytes[11] = ioctl_data[0];
201 bytes[1] = ioctl_data[1];
202 bytes[3] = ioctl_data[2];
204 bytes[9] = ioctl_data[3];
205 bytes[8] = ioctl_data[4];
206 bytes[7] = ioctl_data[5];
208 bytes[10] = ioctl_data[6];
219 /* Sends a SCSI command block */
220 static int sg_io(int fd, int direction,
221 const void *cdb, size_t cdb_len,
222 void *data, size_t data_len,
223 void *sense, size_t sense_len) {
225 struct sg_io_hdr io_hdr;
227 memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
229 io_hdr.interface_id = 'S';
230 io_hdr.cmdp = (unsigned char*) cdb;
231 io_hdr.cmd_len = cdb_len;
232 io_hdr.dxferp = data;
233 io_hdr.dxfer_len = data_len;
235 io_hdr.mx_sb_len = sense_len;
236 io_hdr.dxfer_direction = direction;
237 io_hdr.timeout = SK_TIMEOUT;
239 return ioctl(fd, SG_IO, &io_hdr);
242 static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
243 uint8_t *bytes = cmd_data;
246 uint8_t *desc = sense+8;
249 static const int direction_map[] = {
250 [SK_DIRECTION_NONE] = SG_DXFER_NONE,
251 [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
252 [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
255 assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
257 /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
258 * ATA Command Pass-Through":
259 * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
261 memset(cdb, 0, sizeof(cdb));
263 cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
265 if (direction == SK_DIRECTION_NONE) {
266 cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */
267 cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
269 } else if (direction == SK_DIRECTION_IN) {
270 cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */
271 cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
273 } else if (direction == SK_DIRECTION_OUT) {
274 cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */
275 cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
278 cdb[3] = bytes[0]; /* FEATURES */
281 cdb[5] = bytes[2]; /* SECTORS */
284 cdb[8] = bytes[9]; /* LBA LOW */
285 cdb[10] = bytes[8]; /* LBA MID */
286 cdb[12] = bytes[7]; /* LBA HIGH */
288 cdb[13] = bytes[10] & 0x4F; /* SELECT */
289 cdb[14] = (uint8_t) command;
291 memset(sense, 0, sizeof(sense));
293 if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
296 if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
301 memset(bytes, 0, 12);
309 bytes[10] = desc[12];
310 bytes[11] = desc[13];
315 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
317 static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = {
318 [SK_DISK_TYPE_ATA] = disk_ata_command,
319 [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
323 assert(d->type <= _SK_DISK_TYPE_MAX);
324 assert(direction <= _SK_DIRECTION_MAX);
326 assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
327 assert(direction != SK_DIRECTION_NONE || (!data && !len));
329 return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
332 static int disk_identify_device(SkDisk *d) {
337 memset(cmd, 0, sizeof(cmd));
341 if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
349 d->identify_data_valid = TRUE;
354 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
358 if (!d->identify_data_valid) {
363 memset(cmd, 0, sizeof(cmd));
365 if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
368 if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
373 *awake = ntohs(cmd[1]) == 0xFF;
378 static int disk_smart_enable(SkDisk *d, SkBool b) {
381 if (!disk_smart_is_available(d)) {
386 memset(cmd, 0, sizeof(cmd));
388 cmd[0] = htons(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
389 cmd[2] = htons(0x0000U);
390 cmd[3] = htons(0x00C2U);
391 cmd[4] = htons(0x4F00U);
393 return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
396 int sk_disk_smart_read_data(SkDisk *d) {
401 if (!disk_smart_is_available(d)) {
406 memset(cmd, 0, sizeof(cmd));
408 cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
410 cmd[2] = htons(0x0000U);
411 cmd[3] = htons(0x00C2U);
412 cmd[4] = htons(0x4F00U);
414 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
417 d->smart_data_valid = TRUE;
422 static int disk_smart_read_thresholds(SkDisk *d) {
427 if (!disk_smart_is_available(d)) {
432 memset(cmd, 0, sizeof(cmd));
434 cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
436 cmd[2] = htons(0x0000U);
437 cmd[3] = htons(0x00C2U);
438 cmd[4] = htons(0x4F00U);
440 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
443 d->smart_threshold_data_valid = TRUE;
448 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
452 if (!disk_smart_is_available(d)) {
457 memset(cmd, 0, sizeof(cmd));
459 cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
460 cmd[1] = htons(0x0000U);
461 cmd[3] = htons(0x00C2U);
462 cmd[4] = htons(0x4F00U);
464 if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
467 if (cmd[3] == htons(0x00C2U) &&
468 cmd[4] == htons(0x4F00U))
470 else if (cmd[3] == htons(0x002CU) &&
471 cmd[4] == htons(0xF400U))
481 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
485 if (!disk_smart_is_available(d)) {
490 if (!d->smart_data_valid)
491 if ((ret = sk_disk_smart_read_data(d)) < 0)
494 assert(d->smart_data_valid);
496 if (test != SK_SMART_SELF_TEST_SHORT &&
497 test != SK_SMART_SELF_TEST_EXTENDED &&
498 test != SK_SMART_SELF_TEST_CONVEYANCE &&
499 test != SK_SMART_SELF_TEST_ABORT) {
504 if (!disk_smart_is_start_test_available(d)
505 || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d))
506 || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d))
507 || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) {
512 if (test == SK_SMART_SELF_TEST_ABORT &&
513 !disk_smart_is_abort_test_available(d)) {
518 memset(cmd, 0, sizeof(cmd));
520 cmd[0] = htons(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE);
521 cmd[2] = htons(0x0000U);
522 cmd[3] = htons(0x00C2U);
523 cmd[4] = htons(0x4F00U | (uint16_t) test);
525 return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
528 static void swap_strings(char *s, size_t len) {
529 assert((len & 1) == 0);
531 for (; len > 0; s += 2, len -= 2) {
539 static void clean_strings(char *s) {
543 if (*e < ' ' || *e >= 127)
547 static void drop_spaces(char *s) {
549 SkBool prev_space = FALSE;
571 static void read_string(char *d, uint8_t *s, size_t len) {
574 swap_strings(d, len);
579 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
581 if (!d->identify_data_valid) {
586 read_string(d->identify_parsed_data.serial, d->identify+20, 20);
587 read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
588 read_string(d->identify_parsed_data.model, d->identify+54, 40);
590 *ipd = &d->identify_parsed_data;
595 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
597 if (!d->identify_data_valid) {
602 *b = disk_smart_is_available(d);
606 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
608 *b = d->identify_data_valid;
612 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
614 /* %STRINGPOOLSTART% */
615 static const char* const map[] = {
616 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
617 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
618 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
619 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
620 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
621 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
622 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
624 /* %STRINGPOOLSTOP% */
626 if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
629 return _P(map[status]);
632 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
634 /* %STRINGPOOLSTART% */
635 static const char* const map[] = {
636 [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.",
637 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.",
638 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.",
639 [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.",
640 [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.",
641 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.",
642 [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.",
643 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.",
644 [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.",
645 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress"
647 /* %STRINGPOOLSTOP% */
649 if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
652 return _P(map[status]);
655 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
658 case SK_SMART_SELF_TEST_SHORT:
660 case SK_SMART_SELF_TEST_EXTENDED:
662 case SK_SMART_SELF_TEST_CONVEYANCE:
664 case SK_SMART_SELF_TEST_ABORT:
671 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
673 if (!d->start_test_available)
677 case SK_SMART_SELF_TEST_SHORT:
678 case SK_SMART_SELF_TEST_EXTENDED:
679 return d->short_and_extended_test_available;
680 case SK_SMART_SELF_TEST_CONVEYANCE:
681 return d->conveyance_test_available;
682 case SK_SMART_SELF_TEST_ABORT:
683 return d->abort_test_available;
689 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
691 if (!sk_smart_self_test_available(d, test))
695 case SK_SMART_SELF_TEST_SHORT:
696 return d->short_test_polling_minutes;
697 case SK_SMART_SELF_TEST_EXTENDED:
698 return d->extended_test_polling_minutes;
699 case SK_SMART_SELF_TEST_CONVEYANCE:
700 return d->conveyance_test_polling_minutes;
706 typedef struct SkSmartAttributeInfo {
708 SkSmartAttributeUnit unit;
709 } SkSmartAttributeInfo;
711 /* This data is stolen from smartmontools */
713 /* %STRINGPOOLSTART% */
714 static const SkSmartAttributeInfo const attribute_info[255] = {
715 [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
716 [2] = { "throughput-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
717 [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
718 [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
719 [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
720 [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
721 [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
722 [8] = { "seek-time-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
723 [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
724 [11] = { "calibration-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
725 [12] = { "power-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
726 [13] = { "read-soft-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
727 [187] = { "reported-uncorrect", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
728 [189] = { "high-fly-writes", SK_SMART_ATTRIBUTE_UNIT_NONE },
729 [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
730 [191] = { "g-sense-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
731 [192] = { "power-off-retract-count-1", SK_SMART_ATTRIBUTE_UNIT_NONE },
732 [193] = { "load-cycle-count-1", SK_SMART_ATTRIBUTE_UNIT_NONE },
733 [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
734 [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE },
735 [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
736 [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
737 [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
738 [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
739 [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
740 [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
741 [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
742 [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_NONE },
743 [204] = { "shock-count-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
744 [205] = { "shock-rate-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
745 [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
746 [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
747 [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
748 [209] = { "offline-seek-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
749 [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
750 [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
751 [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
752 [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
753 [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
754 [225] = { "load-cycle-count-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
755 [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
756 [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
757 [228] = { "power-off-retract-count-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
758 [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
759 [231] = { "temperature-celsius-1", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
760 [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
761 [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }
763 /* %STRINGPOOLSTOP% */
765 static void make_pretty(SkSmartAttributeParsedData *a) {
766 uint64_t fourtyeight;
771 if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
775 ((uint64_t) a->raw[0]) |
776 (((uint64_t) a->raw[1]) << 8) |
777 (((uint64_t) a->raw[2]) << 16) |
778 (((uint64_t) a->raw[3]) << 24) |
779 (((uint64_t) a->raw[4]) << 32) |
780 (((uint64_t) a->raw[5]) << 40);
782 if (!strcmp(a->name, "spin-up-time"))
783 a->pretty_value = fourtyeight & 0xFFFF;
784 else if (!strcmp(a->name, "airflow-temperature-celsius") ||
785 !strcmp(a->name, "temperature-celsius-1") ||
786 !strcmp(a->name, "temperature-celsius-2")) {
787 a->pretty_value = (fourtyeight & 0xFFFF)*1000 + 273150;
788 } else if (!strcmp(a->name, "power-on-minutes"))
789 a->pretty_value = fourtyeight * 60 * 1000;
790 else if (!strcmp(a->name, "power-on-seconds"))
791 a->pretty_value = fourtyeight * 1000;
792 else if (!strcmp(a->name, "power-on-hours") ||
793 !strcmp(a->name, "loaded-hours") ||
794 !strcmp(a->name, "head-flying-hours"))
795 a->pretty_value = fourtyeight * 60 * 60 * 1000;
797 a->pretty_value = fourtyeight;
800 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
801 const SkIdentifyParsedData *ipd;
803 /* These are the simple cases */
804 if (attribute_info[id].name)
805 return &attribute_info[id];
807 /* These are the complex ones */
808 if (sk_disk_identify_parse(d, &ipd) < 0)
812 /* We might want to add further special cases/quirks
813 * here eventually. */
817 /* %STRINGPOOLSTART% */
818 static const SkSmartAttributeInfo maxtor = {
819 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
821 static const SkSmartAttributeInfo fujitsu = {
822 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
824 static const SkSmartAttributeInfo others = {
825 "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
827 /* %STRINGPOOLSTOP% */
829 if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
831 else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
841 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
843 if (!d->smart_data_valid) {
848 switch (d->smart_data[362]) {
851 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
856 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
860 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
865 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
870 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
875 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
879 d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
883 d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
884 d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF;
886 d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
888 d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d);
889 d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d);
890 d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d);
891 d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d);
893 d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
894 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]);
895 d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
897 *spd = &d->smart_parsed_data;
902 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
906 if (!d->smart_threshold_data_valid) {
907 a->threshold_valid = FALSE;
911 for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
916 a->threshold_valid = FALSE;
917 a->good_valid = FALSE;
922 a->threshold_valid = p[1] != 0xFE;
924 a->good_valid = FALSE;
927 /* Always-Fail and Always-Pssing thresholds are not relevant
928 * for our assessment. */
929 if (p[1] >= 1 && p[1] <= 0xFD) {
931 if (a->worst_value_valid) {
932 a->good = a->good && (a->worst_value > a->threshold);
933 a->good_valid = TRUE;
936 if (a->current_value_valid) {
937 a->good = a->good && (a->current_value > a->threshold);
938 a->good_valid = TRUE;
943 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
947 if (!d->smart_data_valid) {
952 for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
953 SkSmartAttributeParsedData a;
954 const SkSmartAttributeInfo *i;
960 memset(&a, 0, sizeof(a));
962 a.current_value = p[3];
963 a.current_value_valid = p[3] >= 1 && p[3] <= 0xFD;
964 a.worst_value = p[4];
965 a.worst_value_valid = p[4] >= 1 && p[4] <= 0xFD;
967 a.flags = ((uint16_t) p[2] << 8) | p[1];
968 a.prefailure = !!(p[1] & 1);
969 a.online = !!(p[1] & 2);
971 memcpy(a.raw, p+5, 6);
973 if ((i = lookup_attribute(d, p[0]))) {
974 a.name = _P(i->name);
975 a.pretty_unit = i->unit;
977 if (asprintf(&an, "attribute-%u", a.id) < 0) {
983 a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
988 find_threshold(d, &a);
998 static const char *yes_no(SkBool b) {
999 return b ? "yes" : "no";
1002 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
1004 /* %STRINGPOOLSTART% */
1005 const char * const map[] = {
1006 [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
1007 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
1008 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
1009 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
1010 [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
1012 /* %STRINGPOOLSTOP% */
1014 if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
1017 return _P(map[unit]);
1020 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
1025 snprintf(s, len, "%u", id);
1032 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1034 switch (a->pretty_unit) {
1035 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1037 if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
1038 snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
1039 else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
1040 snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
1041 else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
1042 snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
1043 else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
1044 snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
1045 else if (a->pretty_value >= 1000LLU*60LLU)
1046 snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
1047 else if (a->pretty_value >= 1000LLU)
1048 snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
1050 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1054 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1055 snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1058 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1059 snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1062 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1063 snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1066 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1067 snprintf(s, len, "n/a");
1070 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1079 #define HIGHLIGHT "\x1B[1m"
1080 #define ENDHIGHLIGHT "\x1B[0m"
1082 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1085 char tt[32], tw[32], tc[32];
1088 snprintf(tt, sizeof(tt), "%3u", a->threshold);
1089 tt[sizeof(tt)-1] = 0;
1090 snprintf(tw, sizeof(tw), "%3u", a->worst_value);
1091 tw[sizeof(tw)-1] = 0;
1092 snprintf(tc, sizeof(tc), "%3u", a->current_value);
1093 tc[sizeof(tc)-1] = 0;
1095 highlight = a->good_valid && !a->good && isatty(1);
1098 fprintf(stderr, HIGHLIGHT);
1100 printf("%3u %-27s %-3s %-3s %-3s %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-3s\n",
1102 print_name(name, sizeof(name), a->id, a->name),
1103 a->current_value_valid ? tc : "n/a",
1104 a->worst_value_valid ? tw : "n/a",
1105 a->threshold_valid ? tt : "n/a",
1106 print_value(pretty, sizeof(pretty), a),
1107 a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5],
1108 a->prefailure ? "prefail" : "old-age",
1109 a->online ? "online" : "offline",
1110 a->good_valid ? yes_no(a->good) : "n/a");
1113 fprintf(stderr, ENDHIGHLIGHT);
1116 int sk_disk_dump(SkDisk *d) {
1118 SkBool awake = FALSE;
1120 printf("Device: %s\n"
1123 (unsigned long) (d->size/1024/1024));
1125 if (d->identify_data_valid) {
1126 const SkIdentifyParsedData *ipd;
1128 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1131 printf("Model: [%s]\n"
1134 "SMART Available: %s\n",
1138 yes_no(disk_smart_is_available(d)));
1141 ret = sk_disk_check_sleep_mode(d, &awake);
1142 printf("Awake: %s\n",
1143 ret >= 0 ? yes_no(awake) : "unknown");
1145 if (disk_smart_is_available(d)) {
1146 const SkSmartParsedData *spd;
1149 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1152 printf("Disk Health Good: %s\n",
1155 if ((ret = sk_disk_smart_read_data(d)) < 0)
1158 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1161 printf("Off-line Data Collection Status: [%s]\n"
1162 "Total Time To Complete Off-Line Data Collection: %u s\n"
1163 "Self-Test Execution Status: [%s]\n"
1164 "Percent Self-Test Remaining: %u%%\n"
1165 "Conveyance Self-Test Available: %s\n"
1166 "Short/Extended Self-Test Available: %s\n"
1167 "Start Self-Test Available: %s\n"
1168 "Abort Self-Test Available: %s\n"
1169 "Short Self-Test Polling Time: %u min\n"
1170 "Extended Self-Test Polling Time: %u min\n"
1171 "Conveyance Self-Test Polling Time: %u min\n",
1172 sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
1173 spd->total_offline_data_collection_seconds,
1174 sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
1175 spd->self_test_execution_percent_remaining,
1176 yes_no(spd->conveyance_test_available),
1177 yes_no(spd->short_and_extended_test_available),
1178 yes_no(spd->start_test_available),
1179 yes_no(spd->abort_test_available),
1180 spd->short_test_polling_minutes,
1181 spd->extended_test_polling_minutes,
1182 spd->conveyance_test_polling_minutes);
1184 printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-3s\n",
1196 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1203 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1209 int sk_disk_open(const char *name, SkDisk **_d) {
1217 if (!(d = calloc(1, sizeof(SkDisk)))) {
1222 if (!(d->name = strdup(name))) {
1227 if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1232 if ((ret = fstat(d->fd, &st)) < 0)
1235 if (!S_ISBLK(st.st_mode)) {
1241 /* So, it's a block device. Let's make sure the ioctls work */
1243 if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1246 if (d->size <= 0 || d->size == (uint64_t) -1) {
1252 /* OK, it's a real block device with a size. Find a way to
1253 * identify the device. */
1254 for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
1255 if (disk_identify_device(d) >= 0)
1258 /* Check if driver can do SMART, and enable if necessary */
1259 if (disk_smart_is_available(d)) {
1261 if (!disk_smart_is_enabled(d)) {
1262 if ((ret = disk_smart_enable(d, TRUE)) < 0)
1265 if ((ret = disk_identify_device(d)) < 0)
1268 if (!disk_smart_is_enabled(d)) {
1275 disk_smart_read_thresholds(d);
1290 void sk_disk_free(SkDisk *d) {