check for threshold/value validity
[platform/upstream/libatasmart.git] / atasmart.c
1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 /***
4     This file is part of libatasmart.
5
6     Copyright 2008 Lennart Poettering
7
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.
12
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.
17
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/>.
21 ***/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <arpa/inet.h>
28 #include <stdlib.h>
29 #include <alloca.h>
30 #include <assert.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <sys/stat.h>
37 #include <sys/ioctl.h>
38 #include <scsi/scsi.h>
39 #include <scsi/sg.h>
40 #include <scsi/scsi_ioctl.h>
41 #include <linux/hdreg.h>
42 #include <linux/fs.h>
43
44 #include "atasmart.h"
45
46 #define SK_TIMEOUT 2000
47
48 typedef enum SkDirection {
49         SK_DIRECTION_NONE,
50         SK_DIRECTION_IN,
51         SK_DIRECTION_OUT,
52         _SK_DIRECTION_MAX
53 } SkDirection;
54
55 typedef enum SkDiskType {
56         SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
57         SK_DISK_TYPE_ATA,
58         SK_DISK_TYPE_UNKNOWN,
59         _SK_DISK_TYPE_MAX
60 } SkDiskType;
61
62 struct SkDisk {
63         char *name;
64         int fd;
65         SkDiskType type;
66
67         uint64_t size;
68
69         uint8_t identify[512];
70         uint8_t smart_data[512];
71         uint8_t smart_threshold_data[512];
72
73         SkBool identify_data_valid:1;
74         SkBool smart_data_valid:1;
75         SkBool smart_threshold_data_valid:1;
76
77         SkIdentifyParsedData identify_parsed_data;
78         SkSmartParsedData smart_parsed_data;
79 };
80
81 /* ATA commands */
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
87 } SkAtaCommand;
88
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
97 } SkSmartCommand;
98
99 static SkBool disk_smart_is_available(SkDisk *d) {
100         return d->identify_data_valid && !!(d->identify[164] & 1);
101 }
102
103 static SkBool disk_smart_is_enabled(SkDisk *d) {
104         return d->identify_data_valid && !!(d->identify[170] & 1);
105 }
106
107 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
108         assert(d->smart_data_valid);
109
110         return !!(d->smart_data[367] & 32);
111 }
112 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
113         assert(d->smart_data_valid);
114
115         return !!(d->smart_data[367] & 16);
116 }
117
118 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
119         assert(d->smart_data_valid);
120
121         return !!(d->smart_data[367] & 1);
122 }
123
124 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
125         assert(d->smart_data_valid);
126
127         return !!(d->smart_data[367] & 41);
128 }
129
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;
132         int ret;
133
134         assert(d->type == SK_DISK_TYPE_ATA);
135
136         switch (direction) {
137
138                 case SK_DIRECTION_OUT:
139
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. */
143
144                         errno = ENOTSUP;
145                         return -1;
146
147                 case SK_DIRECTION_IN: {
148                         uint8_t *ioctl_data;
149
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. */
152
153                         ioctl_data = alloca(4 + *len);
154                         memset(ioctl_data, 0, 4 + *len);
155
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 */
160
161                         if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
162                                 return ret;
163
164                         memset(bytes, 0, 12);
165                         bytes[11] = ioctl_data[0];
166                         bytes[1] = ioctl_data[1];
167                         bytes[3] = ioctl_data[2];
168
169                         memcpy(data, ioctl_data+4, *len);
170
171                         return ret;
172                 }
173
174                 case SK_DIRECTION_NONE: {
175                         uint8_t ioctl_data[7];
176
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 */
180
181                         memset(ioctl_data, 0, sizeof(ioctl_data));
182
183                         ioctl_data[0] = (uint8_t) command;  /* COMMAND */
184                         ioctl_data[1] = bytes[1];         /* FEATURE */
185                         ioctl_data[2] = bytes[3];         /* NSECTOR */
186
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 */
191
192                         if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
193                                 return ret;
194
195                         memset(bytes, 0, 12);
196                         bytes[11] = ioctl_data[0];
197                         bytes[1] = ioctl_data[1];
198                         bytes[3] = ioctl_data[2];
199
200                         bytes[9] = ioctl_data[3];
201                         bytes[8] = ioctl_data[4];
202                         bytes[7] = ioctl_data[5];
203
204                         bytes[10] = ioctl_data[6];
205
206                         return ret;
207                 }
208
209                 default:
210                         assert(FALSE);
211                         return -1;
212         }
213 }
214
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) {
220
221         struct sg_io_hdr io_hdr;
222
223         memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
224
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;
230         io_hdr.sbp = sense;
231         io_hdr.mx_sb_len = sense_len;
232         io_hdr.dxfer_direction = direction;
233         io_hdr.timeout = SK_TIMEOUT;
234
235         return ioctl(fd, SG_IO, &io_hdr);
236 }
237
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;
240         uint8_t cdb[16];
241         uint8_t sense[32];
242         uint8_t *desc = sense+8;
243         int ret;
244
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
249         };
250
251         assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
252
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 */
256
257         memset(cdb, 0, sizeof(cdb));
258
259         cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
260
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 */
264
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 */
268
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 */
272         }
273
274         cdb[3] = bytes[0]; /* FEATURES */
275         cdb[4] = bytes[1];
276
277         cdb[5] = bytes[2]; /* SECTORS */
278         cdb[6] = bytes[3];
279
280         cdb[8] = bytes[9]; /* LBA LOW */
281         cdb[10] = bytes[8]; /* LBA MID */
282         cdb[12] = bytes[7]; /* LBA HIGH */
283
284         cdb[13] = bytes[10] & 0x4F; /* SELECT */
285         cdb[14] = (uint8_t) command;
286
287         if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
288                 return ret;
289
290         if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
291                 errno = EIO;
292                 return -1;
293         }
294
295         memset(bytes, 0, 12);
296
297         bytes[1] = desc[3];
298         bytes[2] = desc[4];
299         bytes[3] = desc[5];
300         bytes[9] = desc[7];
301         bytes[8] = desc[9];
302         bytes[7] = desc[11];
303         bytes[10] = desc[12];
304         bytes[11] = desc[13];
305
306         return ret;
307 }
308
309 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
310
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,
314         };
315
316         assert(d);
317         assert(d->type <= _SK_DISK_TYPE_MAX);
318         assert(direction <= _SK_DIRECTION_MAX);
319
320         assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
321         assert(direction != SK_DIRECTION_NONE || (!data && !len));
322
323         return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
324 }
325
326 static int disk_identify_device(SkDisk *d) {
327         uint16_t cmd[6];
328         int ret;
329         size_t len = 512;
330
331         memset(cmd, 0, sizeof(cmd));
332
333         cmd[1] = htons(1);
334
335         if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
336                 return ret;
337
338         if (len != 512) {
339                 errno = EIO;
340                 return -1;
341         }
342
343         d->identify_data_valid = TRUE;
344
345         return 0;
346 }
347
348 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
349         int ret;
350         uint16_t cmd[6];
351
352         if (!d->identify_data_valid) {
353                 errno = ENOTSUP;
354                 return -1;
355         }
356
357         memset(cmd, 0, sizeof(cmd));
358
359         if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
360                 return ret;
361
362         if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
363                 errno = EIO;
364                 return -1;
365         }
366
367         *awake = ntohs(cmd[1]) == 0xFF;
368
369         return 0;
370 }
371
372 static int disk_smart_enable(SkDisk *d, SkBool b) {
373         uint16_t cmd[6];
374
375         if (!disk_smart_is_available(d)) {
376                 errno = ENOTSUP;
377                 return -1;
378         }
379
380         memset(cmd, 0, sizeof(cmd));
381
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);
386
387         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
388 }
389
390 int sk_disk_smart_read_data(SkDisk *d) {
391         uint16_t cmd[6];
392         int ret;
393         size_t len = 512;
394
395         if (!disk_smart_is_available(d)) {
396                 errno = ENOTSUP;
397                 return -1;
398         }
399
400         memset(cmd, 0, sizeof(cmd));
401
402         cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
403         cmd[1] = htons(1);
404         cmd[2] = htons(0x0000U);
405         cmd[3] = htons(0x00C2U);
406         cmd[4] = htons(0x4F00U);
407
408         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
409                 return ret;
410
411         d->smart_data_valid = TRUE;
412
413         return ret;
414 }
415
416 static int disk_smart_read_thresholds(SkDisk *d) {
417         uint16_t cmd[6];
418         int ret;
419         size_t len = 512;
420
421         if (!disk_smart_is_available(d)) {
422                 errno = ENOTSUP;
423                 return -1;
424         }
425
426         memset(cmd, 0, sizeof(cmd));
427
428         cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
429         cmd[1] = htons(1);
430         cmd[2] = htons(0x0000U);
431         cmd[3] = htons(0x00C2U);
432         cmd[4] = htons(0x4F00U);
433
434         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
435                 return ret;
436
437         d->smart_threshold_data_valid = TRUE;
438
439         return ret;
440 }
441
442 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
443         uint16_t cmd[6];
444         int ret;
445
446         if (!disk_smart_is_available(d)) {
447                 errno = ENOTSUP;
448                 return -1;
449         }
450
451         memset(cmd, 0, sizeof(cmd));
452
453         cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
454         cmd[1] = htons(0x0000U);
455         cmd[3] = htons(0x00C2U);
456         cmd[4] = htons(0x4F00U);
457
458         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
459                 return ret;
460
461         if (cmd[3] == htons(0x00C2U) &&
462             cmd[4] == htons(0x4F00U))
463                 *good = TRUE;
464         else if (cmd[3] == htons(0x002CU) &&
465             cmd[4] == htons(0xF400U))
466                 *good = FALSE;
467         else {
468                 errno = EIO;
469                 return -1;
470         }
471
472         return ret;
473 }
474
475 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
476         uint16_t cmd[6];
477         int ret;
478
479         if (!disk_smart_is_available(d)) {
480                 errno = ENOTSUP;
481                 return -1;
482         }
483
484         if (!d->smart_data_valid)
485                 if ((ret = sk_disk_smart_read_data(d)) < 0)
486                         return -1;
487
488         assert(d->smart_data_valid);
489
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) {
494                 errno = EINVAL;
495                 return -1;
496         }
497
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))) {
502                 errno = ENOTSUP;
503                 return -1;
504         }
505
506         if (test == SK_SMART_SELF_TEST_ABORT &&
507             !disk_smart_is_abort_test_available(d)) {
508                 errno = ENOTSUP;
509                 return -1;
510         }
511
512         memset(cmd, 0, sizeof(cmd));
513
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);
518
519         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
520 }
521
522 static void swap_strings(char *s, size_t len) {
523         assert((len & 1) == 0);
524
525         for (; len > 0; s += 2, len -= 2) {
526                 char t;
527                 t = s[0];
528                 s[0] = s[1];
529                 s[1] = t;
530         }
531 }
532
533 static void clean_strings(char *s) {
534         char *e;
535
536         for (e = s; *e; e++)
537                 if (*e < ' ' || *e >= 127)
538                         *e = ' ';
539 }
540
541 static void drop_spaces(char *s) {
542         char *d = s;
543         SkBool prev_space = FALSE;
544
545         s += strspn(s, " ");
546
547         for (;*s; s++) {
548
549                 if (prev_space) {
550                         if (*s != ' ') {
551                                 prev_space = FALSE;
552                                 *(d++) = ' ';
553                         }
554                 } else {
555                         if (*s == ' ')
556                                 prev_space = TRUE;
557                         else
558                                 *(d++) = *s;
559                 }
560         }
561
562         *d = 0;
563 }
564
565 static void read_string(char *d, uint8_t *s, size_t len) {
566         memcpy(d, s, len);
567         d[len] = 0;
568         swap_strings(d, len);
569         clean_strings(d);
570         drop_spaces(d);
571 }
572
573 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
574
575         if (!d->identify_data_valid) {
576                 errno = ENOENT;
577                 return -1;
578         }
579
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);
583
584         *ipd = &d->identify_parsed_data;
585
586         return 0;
587 }
588
589 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
590
591         if (!d->identify_data_valid) {
592                 errno = ENOTSUP;
593                 return -1;
594         }
595
596         *b = disk_smart_is_available(d);
597         return 0;
598 }
599
600 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
601
602         *b = d->identify_data_valid;
603         return 0;
604 }
605
606 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
607
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"
616         };
617
618         if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
619                 return NULL;
620
621         return map[status];
622 }
623
624 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
625
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"
637         };
638
639         if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
640                 return NULL;
641
642         return map[status];
643 }
644
645 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
646
647         switch (test) {
648                 case SK_SMART_SELF_TEST_SHORT:
649                         return "short";
650                 case SK_SMART_SELF_TEST_EXTENDED:
651                         return "extended";
652                 case SK_SMART_SELF_TEST_CONVEYANCE:
653                         return "conveyance";
654                 case SK_SMART_SELF_TEST_ABORT:
655                         return "abort";
656         }
657
658         return NULL;
659 }
660
661 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
662
663         if (!d->start_test_available)
664                 return FALSE;
665
666         switch (test) {
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;
674                 default:
675                         return FALSE;
676         }
677 }
678
679 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
680
681         if (!sk_smart_self_test_available(d, test))
682                 return 0;
683
684         switch (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;
691                 default:
692                         return 0;
693         }
694 }
695
696 typedef struct SkSmartAttributeInfo {
697         const char *name;
698         SkSmartAttributeUnit unit;
699 } SkSmartAttributeInfo;
700
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",     SK_SMART_ATTRIBUTE_UNIT_NONE },
720         [193] = { "load-cycle-count",            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",            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",     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 }
750 };
751
752 static void make_pretty(SkSmartAttributeParsedData *a) {
753         uint64_t fourtyeight;
754
755         if (!a->name)
756                 return;
757
758         if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
759                 return;
760
761         fourtyeight =
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);
768
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;
783         else
784                 a->pretty_value = fourtyeight;
785 }
786
787 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
788         const SkIdentifyParsedData *ipd;
789
790         /* These are the simple cases */
791         if (attribute_info[id].name)
792                 return &attribute_info[id];
793
794         /* These are the complex ones */
795         if (sk_disk_identify_parse(d, &ipd) < 0)
796                 return NULL;
797
798         switch (id) {
799                 /* We might want to add further special cases/quirks
800                  * here eventually. */
801
802                 case 9: {
803
804                         static const SkSmartAttributeInfo maxtor = {
805                                 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
806                         };
807                         static const SkSmartAttributeInfo fujitsu = {
808                                 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
809                         };
810                         static const SkSmartAttributeInfo others = {
811                                 "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
812                         };
813
814                         if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
815                                 return &maxtor;
816                         else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
817                                 return &fujitsu;
818
819                         return &others;
820                 }
821         }
822
823         return NULL;
824 }
825
826 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
827
828         if (!d->smart_data_valid) {
829                 errno = ENOENT;
830                 return -1;
831         }
832
833         switch (d->smart_data[362]) {
834                 case 0x00:
835                 case 0x80:
836                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
837                         break;
838
839                 case 0x02:
840                 case 0x82:
841                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
842                         break;
843
844                 case 0x03:
845                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
846                         break;
847
848                 case 0x04:
849                 case 0x84:
850                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
851                         break;
852
853                 case 0x05:
854                 case 0x85:
855                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
856                         break;
857
858                 case 0x06:
859                 case 0x86:
860                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
861                         break;
862
863                 default:
864                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
865                         break;
866         }
867
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;
870
871         d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
872
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);
877
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];
881
882         *spd = &d->smart_parsed_data;
883
884         return 0;
885 }
886
887 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
888         uint8_t *p;
889         unsigned n;
890
891         if (!d->smart_threshold_data_valid) {
892                 a->threshold_valid = FALSE;
893                 return;
894         }
895
896         for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
897                 if (p[0] == a->id)
898                         break;
899
900         if (n >= 30) {
901                 a->threshold_valid = FALSE;
902                 a->good_valid = FALSE;
903                 return;
904         }
905
906         a->threshold = p[1];
907         a->threshold_valid = p[1] != 0xFE;
908
909         a->good_valid = FALSE;
910         a->good = TRUE;
911
912         /* Always-Fail and Always-Pssing thresholds are not relevant
913          * for our assessment. */
914         if (p[1] >= 1 && p[1] <= 0xFD) {
915
916                 if (a->worst_value_valid) {
917                         a->good = a->good && (a->worst_value > a->threshold);
918                         a->good_valid = TRUE;
919                 }
920
921                 if (a->current_value_valid) {
922                         a->good = a->good && (a->current_value > a->threshold);
923                         a->good_valid = TRUE;
924                 }
925         }
926 }
927
928 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
929         uint8_t *p;
930         unsigned n;
931
932         if (!d->smart_data_valid) {
933                 errno = ENOENT;
934                 return -1;
935         }
936
937         for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
938                 SkSmartAttributeParsedData a;
939                 const SkSmartAttributeInfo *i;
940                 char *an = NULL;
941
942                 if (p[0] == 0)
943                         continue;
944
945                 memset(&a, 0, sizeof(a));
946                 a.id = p[0];
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;
951
952                 a.flags = ((uint16_t) p[2] << 8) | p[1];
953                 a.prefailure = !!(p[1] & 1);
954                 a.online = !!(p[1] & 2);
955
956                 memcpy(a.raw, p+5, 6);
957
958                 if ((i = lookup_attribute(d, p[0]))) {
959                         a.name = i->name;
960                         a.pretty_unit = i->unit;
961                 } else {
962                         if (asprintf(&an, "attribute-%u", a.id) < 0) {
963                                 errno = ENOMEM;
964                                 return -1;
965                         }
966
967                         a.name = an;
968                         a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
969                 }
970
971                 make_pretty(&a);
972
973                 find_threshold(d, &a);
974
975                 cb(d, &a, userdata);
976
977                 free(an);
978         }
979
980         return 0;
981 }
982
983 static const char *yes_no(SkBool b) {
984         return  b ? "yes" : "no";
985 }
986
987 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
988
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"
995         };
996
997         if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
998                 return NULL;
999
1000         return map[unit];
1001 }
1002
1003 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
1004
1005         if (k)
1006                 strncpy(s, k, len);
1007         else
1008                 snprintf(s, len, "%u", id);
1009
1010         s[len-1] = 0;
1011
1012         return s;
1013
1014 }
1015
1016 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1017
1018         switch (a->pretty_unit) {
1019                 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1020
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));
1033                         else
1034                                 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1035
1036                         break;
1037
1038                 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1039                         snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1040                         break;
1041
1042                 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1043                         snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1044                         break;
1045
1046                 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1047                         snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1048                         break;
1049
1050                 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1051                         snprintf(s, len, "n/a");
1052                         break;
1053
1054                 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1055                         assert(FALSE);
1056         }
1057
1058         s[len-1] = 0;
1059
1060         return s;
1061 }
1062
1063 #define HIGHLIGHT "\x1B[1m"
1064 #define ENDHIGHLIGHT "\x1B[0m"
1065
1066 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1067         char name[32];
1068         char pretty[32];
1069         char tt[32], tw[32], tc[32];
1070
1071         snprintf(tt, sizeof(tt), "%3u", a->threshold);
1072         tt[sizeof(tt)-1] = 0;
1073         snprintf(tw, sizeof(tw), "%3u", a->worst_value);
1074         tw[sizeof(tw)-1] = 0;
1075         snprintf(tc, sizeof(tc), "%3u", a->current_value);
1076         tc[sizeof(tc)-1] = 0;
1077
1078         if (!a->good  && isatty(1))
1079                 fprintf(stderr, HIGHLIGHT);
1080
1081         printf("%3u %-27s %-3s   %-3s   %-3s   %-11s %-7s %-7s %-3s\n",
1082                a->id,
1083                print_name(name, sizeof(name), a->id, a->name),
1084                a->current_value_valid ? tc : "n/a",
1085                a->worst_value_valid ? tw : "n/a",
1086                a->threshold_valid ? tt : "n/a",
1087                print_value(pretty, sizeof(pretty), a),
1088                a->prefailure ? "prefail" : "old-age",
1089                a->online ? "online" : "offline",
1090                a->good_valid ? yes_no(a->good) : "n/a");
1091
1092         if (!a->good && isatty(1))
1093                 fprintf(stderr, ENDHIGHLIGHT);
1094 }
1095
1096 int sk_disk_dump(SkDisk *d) {
1097         int ret;
1098         SkBool awake = FALSE;
1099
1100         printf("Device: %s\n"
1101                "Size: %lu MiB\n",
1102                d->name,
1103                (unsigned long) (d->size/1024/1024));
1104
1105         if (d->identify_data_valid) {
1106                 const SkIdentifyParsedData *ipd;
1107
1108                 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1109                         return ret;
1110
1111                 printf("Model: [%s]\n"
1112                        "Serial: [%s]\n"
1113                        "Firmware: [%s]\n"
1114                        "SMART Available: %s\n",
1115                        ipd->model,
1116                        ipd->serial,
1117                        ipd->firmware,
1118                        yes_no(disk_smart_is_available(d)));
1119         }
1120
1121         ret = sk_disk_check_sleep_mode(d, &awake);
1122         printf("Awake: %s\n",
1123                ret >= 0 ? yes_no(awake) : "unknown");
1124
1125         if (disk_smart_is_available(d)) {
1126                 const SkSmartParsedData *spd;
1127                 SkBool good;
1128
1129                 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1130                         return ret;
1131
1132                 printf("Disk Health Good: %s\n",
1133                         yes_no(good));
1134
1135                 if ((ret = sk_disk_smart_read_data(d)) < 0)
1136                         return ret;
1137
1138                 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1139                         return ret;
1140
1141                 printf("Off-line Data Collection Status: [%s]\n"
1142                        "Total Time To Complete Off-Line Data Collection: %u s\n"
1143                        "Self-Test Execution Status: [%s]\n"
1144                        "Percent Self-Test Remaining: %u%%\n"
1145                        "Conveyance Self-Test Available: %s\n"
1146                        "Short/Extended Self-Test Available: %s\n"
1147                        "Start Self-Test Available: %s\n"
1148                        "Abort Self-Test Available: %s\n"
1149                        "Short Self-Test Polling Time: %u min\n"
1150                        "Extended Self-Test Polling Time: %u min\n"
1151                        "Conveyance Self-Test Polling Time: %u min\n",
1152                        sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
1153                        spd->total_offline_data_collection_seconds,
1154                        sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
1155                        spd->self_test_execution_percent_remaining,
1156                        yes_no(spd->conveyance_test_available),
1157                        yes_no(spd->short_and_extended_test_available),
1158                        yes_no(spd->start_test_available),
1159                        yes_no(spd->abort_test_available),
1160                         spd->short_test_polling_minutes,
1161                        spd->extended_test_polling_minutes,
1162                        spd->conveyance_test_polling_minutes);
1163
1164                 printf("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n",
1165                        "ID#",
1166                        "Name",
1167                        "Value",
1168                        "Worst",
1169                        "Thres",
1170                        "Pretty",
1171                        "Type",
1172                        "Updates",
1173                        "Good");
1174
1175                 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1176                         return ret;
1177         }
1178
1179         return 0;
1180 }
1181
1182 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1183
1184         *bytes = d->size;
1185         return 0;
1186 }
1187
1188 int sk_disk_open(const char *name, SkDisk **_d) {
1189         SkDisk *d;
1190         int ret = -1;
1191         struct stat st;
1192
1193         assert(name);
1194         assert(_d);
1195
1196         if (!(d = calloc(1, sizeof(SkDisk)))) {
1197                 errno = ENOMEM;
1198                 goto fail;
1199         }
1200
1201         if (!(d->name = strdup(name))) {
1202                 errno = ENOMEM;
1203                 goto fail;
1204         }
1205
1206         if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1207                 ret = d->fd;
1208                 goto fail;
1209         }
1210
1211         if ((ret = fstat(d->fd, &st)) < 0)
1212                 goto fail;
1213
1214         if (!S_ISBLK(st.st_mode)) {
1215                 errno = ENODEV;
1216                 ret = -1;
1217                 goto fail;
1218         }
1219
1220         /* So, it's a block device. Let's make sure the ioctls work */
1221
1222         if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1223                 goto fail;
1224
1225         if (d->size <= 0 || d->size == (uint64_t) -1) {
1226                 errno = EIO;
1227                 ret = -1;
1228                 goto fail;
1229         }
1230
1231         /* OK, it's a real block device with a size. Find a way to
1232          * identify the device. */
1233         for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
1234                 if (disk_identify_device(d) >= 0)
1235                         break;
1236
1237         /* Check if driver can do SMART, and enable if necessary */
1238         if (disk_smart_is_available(d)) {
1239
1240                 if (!disk_smart_is_enabled(d)) {
1241                         if ((ret = disk_smart_enable(d, TRUE)) < 0)
1242                                 goto fail;
1243
1244                         if ((ret = disk_identify_device(d)) < 0)
1245                                 goto fail;
1246
1247                         if (!disk_smart_is_enabled(d)) {
1248                                 errno = EIO;
1249                                 ret = -1;
1250                                 goto fail;
1251                         }
1252                 }
1253
1254                 disk_smart_read_thresholds(d);
1255         }
1256
1257         *_d = d;
1258
1259         return 0;
1260
1261 fail:
1262
1263         if (d)
1264                 sk_disk_free(d);
1265
1266         return ret;
1267 }
1268
1269 void sk_disk_free(SkDisk *d) {
1270         assert(d);
1271
1272         if (d->fd >= 0)
1273                 close(d->fd);
1274
1275         free(d->name);
1276         free(d);
1277 }