0387e5a20dca14c7f5a1d18891b00eafa806e5de
[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 #ifndef STRPOOL
47 #define _P(x) x
48 #endif
49
50 #define SK_TIMEOUT 2000
51
52 typedef enum SkDirection {
53         SK_DIRECTION_NONE,
54         SK_DIRECTION_IN,
55         SK_DIRECTION_OUT,
56         _SK_DIRECTION_MAX
57 } SkDirection;
58
59 typedef enum SkDiskType {
60         SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
61         SK_DISK_TYPE_ATA,
62         SK_DISK_TYPE_UNKNOWN,
63         _SK_DISK_TYPE_MAX
64 } SkDiskType;
65
66 struct SkDisk {
67         char *name;
68         int fd;
69         SkDiskType type;
70
71         uint64_t size;
72
73         uint8_t identify[512];
74         uint8_t smart_data[512];
75         uint8_t smart_threshold_data[512];
76
77         SkBool identify_data_valid:1;
78         SkBool smart_data_valid:1;
79         SkBool smart_threshold_data_valid:1;
80
81         SkIdentifyParsedData identify_parsed_data;
82         SkSmartParsedData smart_parsed_data;
83 };
84
85 /* ATA commands */
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
91 } SkAtaCommand;
92
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
101 } SkSmartCommand;
102
103 static SkBool disk_smart_is_available(SkDisk *d) {
104         return d->identify_data_valid && !!(d->identify[164] & 1);
105 }
106
107 static SkBool disk_smart_is_enabled(SkDisk *d) {
108         return d->identify_data_valid && !!(d->identify[170] & 1);
109 }
110
111 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
112         assert(d->smart_data_valid);
113
114         return !!(d->smart_data[367] & 32);
115 }
116 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
117         assert(d->smart_data_valid);
118
119         return !!(d->smart_data[367] & 16);
120 }
121
122 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
123         assert(d->smart_data_valid);
124
125         return !!(d->smart_data[367] & 1);
126 }
127
128 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
129         assert(d->smart_data_valid);
130
131         return !!(d->smart_data[367] & 41);
132 }
133
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;
136         int ret;
137
138         assert(d->type == SK_DISK_TYPE_ATA);
139
140         switch (direction) {
141
142                 case SK_DIRECTION_OUT:
143
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. */
147
148                         errno = ENOTSUP;
149                         return -1;
150
151                 case SK_DIRECTION_IN: {
152                         uint8_t *ioctl_data;
153
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. */
156
157                         ioctl_data = alloca(4 + *len);
158                         memset(ioctl_data, 0, 4 + *len);
159
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 */
164
165                         if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
166                                 return ret;
167
168                         memset(bytes, 0, 12);
169                         bytes[11] = ioctl_data[0];
170                         bytes[1] = ioctl_data[1];
171                         bytes[3] = ioctl_data[2];
172
173                         memcpy(data, ioctl_data+4, *len);
174
175                         return ret;
176                 }
177
178                 case SK_DIRECTION_NONE: {
179                         uint8_t ioctl_data[7];
180
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 */
184
185                         memset(ioctl_data, 0, sizeof(ioctl_data));
186
187                         ioctl_data[0] = (uint8_t) command;  /* COMMAND */
188                         ioctl_data[1] = bytes[1];         /* FEATURE */
189                         ioctl_data[2] = bytes[3];         /* NSECTOR */
190
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 */
195
196                         if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
197                                 return ret;
198
199                         memset(bytes, 0, 12);
200                         bytes[11] = ioctl_data[0];
201                         bytes[1] = ioctl_data[1];
202                         bytes[3] = ioctl_data[2];
203
204                         bytes[9] = ioctl_data[3];
205                         bytes[8] = ioctl_data[4];
206                         bytes[7] = ioctl_data[5];
207
208                         bytes[10] = ioctl_data[6];
209
210                         return ret;
211                 }
212
213                 default:
214                         assert(FALSE);
215                         return -1;
216         }
217 }
218
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) {
224
225         struct sg_io_hdr io_hdr;
226
227         memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
228
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;
234         io_hdr.sbp = sense;
235         io_hdr.mx_sb_len = sense_len;
236         io_hdr.dxfer_direction = direction;
237         io_hdr.timeout = SK_TIMEOUT;
238
239         return ioctl(fd, SG_IO, &io_hdr);
240 }
241
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;
244         uint8_t cdb[16];
245         uint8_t sense[32];
246         uint8_t *desc = sense+8;
247         int ret;
248
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
253         };
254
255         assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
256
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 */
260
261         memset(cdb, 0, sizeof(cdb));
262
263         cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
264
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 */
268
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 */
272
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 */
276         }
277
278         cdb[3] = bytes[0]; /* FEATURES */
279         cdb[4] = bytes[1];
280
281         cdb[5] = bytes[2]; /* SECTORS */
282         cdb[6] = bytes[3];
283
284         cdb[8] = bytes[9]; /* LBA LOW */
285         cdb[10] = bytes[8]; /* LBA MID */
286         cdb[12] = bytes[7]; /* LBA HIGH */
287
288         cdb[13] = bytes[10] & 0x4F; /* SELECT */
289         cdb[14] = (uint8_t) command;
290
291         memset(sense, 0, sizeof(sense));
292
293         if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
294                 return ret;
295
296         if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
297                 errno = EIO;
298                 return -1;
299         }
300
301         memset(bytes, 0, 12);
302
303         bytes[1] = desc[3];
304         bytes[2] = desc[4];
305         bytes[3] = desc[5];
306         bytes[9] = desc[7];
307         bytes[8] = desc[9];
308         bytes[7] = desc[11];
309         bytes[10] = desc[12];
310         bytes[11] = desc[13];
311
312         return ret;
313 }
314
315 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
316
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,
320         };
321
322         assert(d);
323         assert(d->type <= _SK_DISK_TYPE_MAX);
324         assert(direction <= _SK_DIRECTION_MAX);
325
326         assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
327         assert(direction != SK_DIRECTION_NONE || (!data && !len));
328
329         return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
330 }
331
332 static int disk_identify_device(SkDisk *d) {
333         uint16_t cmd[6];
334         int ret;
335         size_t len = 512;
336
337         memset(cmd, 0, sizeof(cmd));
338
339         cmd[1] = htons(1);
340
341         if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
342                 return ret;
343
344         if (len != 512) {
345                 errno = EIO;
346                 return -1;
347         }
348
349         d->identify_data_valid = TRUE;
350
351         return 0;
352 }
353
354 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
355         int ret;
356         uint16_t cmd[6];
357
358         if (!d->identify_data_valid) {
359                 errno = ENOTSUP;
360                 return -1;
361         }
362
363         memset(cmd, 0, sizeof(cmd));
364
365         if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
366                 return ret;
367
368         if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
369                 errno = EIO;
370                 return -1;
371         }
372
373         *awake = ntohs(cmd[1]) == 0xFF;
374
375         return 0;
376 }
377
378 static int disk_smart_enable(SkDisk *d, SkBool b) {
379         uint16_t cmd[6];
380
381         if (!disk_smart_is_available(d)) {
382                 errno = ENOTSUP;
383                 return -1;
384         }
385
386         memset(cmd, 0, sizeof(cmd));
387
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);
392
393         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
394 }
395
396 int sk_disk_smart_read_data(SkDisk *d) {
397         uint16_t cmd[6];
398         int ret;
399         size_t len = 512;
400
401         if (!disk_smart_is_available(d)) {
402                 errno = ENOTSUP;
403                 return -1;
404         }
405
406         memset(cmd, 0, sizeof(cmd));
407
408         cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
409         cmd[1] = htons(1);
410         cmd[2] = htons(0x0000U);
411         cmd[3] = htons(0x00C2U);
412         cmd[4] = htons(0x4F00U);
413
414         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
415                 return ret;
416
417         d->smart_data_valid = TRUE;
418
419         return ret;
420 }
421
422 static int disk_smart_read_thresholds(SkDisk *d) {
423         uint16_t cmd[6];
424         int ret;
425         size_t len = 512;
426
427         if (!disk_smart_is_available(d)) {
428                 errno = ENOTSUP;
429                 return -1;
430         }
431
432         memset(cmd, 0, sizeof(cmd));
433
434         cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
435         cmd[1] = htons(1);
436         cmd[2] = htons(0x0000U);
437         cmd[3] = htons(0x00C2U);
438         cmd[4] = htons(0x4F00U);
439
440         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
441                 return ret;
442
443         d->smart_threshold_data_valid = TRUE;
444
445         return ret;
446 }
447
448 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
449         uint16_t cmd[6];
450         int ret;
451
452         if (!disk_smart_is_available(d)) {
453                 errno = ENOTSUP;
454                 return -1;
455         }
456
457         memset(cmd, 0, sizeof(cmd));
458
459         cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
460         cmd[1] = htons(0x0000U);
461         cmd[3] = htons(0x00C2U);
462         cmd[4] = htons(0x4F00U);
463
464         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
465                 return ret;
466
467         if (cmd[3] == htons(0x00C2U) &&
468             cmd[4] == htons(0x4F00U))
469                 *good = TRUE;
470         else if (cmd[3] == htons(0x002CU) &&
471             cmd[4] == htons(0xF400U))
472                 *good = FALSE;
473         else {
474                 errno = EIO;
475                 return -1;
476         }
477
478         return ret;
479 }
480
481 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
482         uint16_t cmd[6];
483         int ret;
484
485         if (!disk_smart_is_available(d)) {
486                 errno = ENOTSUP;
487                 return -1;
488         }
489
490         if (!d->smart_data_valid)
491                 if ((ret = sk_disk_smart_read_data(d)) < 0)
492                         return -1;
493
494         assert(d->smart_data_valid);
495
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) {
500                 errno = EINVAL;
501                 return -1;
502         }
503
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))) {
508                 errno = ENOTSUP;
509                 return -1;
510         }
511
512         if (test == SK_SMART_SELF_TEST_ABORT &&
513             !disk_smart_is_abort_test_available(d)) {
514                 errno = ENOTSUP;
515                 return -1;
516         }
517
518         memset(cmd, 0, sizeof(cmd));
519
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);
524
525         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
526 }
527
528 static void swap_strings(char *s, size_t len) {
529         assert((len & 1) == 0);
530
531         for (; len > 0; s += 2, len -= 2) {
532                 char t;
533                 t = s[0];
534                 s[0] = s[1];
535                 s[1] = t;
536         }
537 }
538
539 static void clean_strings(char *s) {
540         char *e;
541
542         for (e = s; *e; e++)
543                 if (*e < ' ' || *e >= 127)
544                         *e = ' ';
545 }
546
547 static void drop_spaces(char *s) {
548         char *d = s;
549         SkBool prev_space = FALSE;
550
551         s += strspn(s, " ");
552
553         for (;*s; s++) {
554
555                 if (prev_space) {
556                         if (*s != ' ') {
557                                 prev_space = FALSE;
558                                 *(d++) = ' ';
559                         }
560                 } else {
561                         if (*s == ' ')
562                                 prev_space = TRUE;
563                         else
564                                 *(d++) = *s;
565                 }
566         }
567
568         *d = 0;
569 }
570
571 static void read_string(char *d, uint8_t *s, size_t len) {
572         memcpy(d, s, len);
573         d[len] = 0;
574         swap_strings(d, len);
575         clean_strings(d);
576         drop_spaces(d);
577 }
578
579 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
580
581         if (!d->identify_data_valid) {
582                 errno = ENOENT;
583                 return -1;
584         }
585
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);
589
590         *ipd = &d->identify_parsed_data;
591
592         return 0;
593 }
594
595 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
596
597         if (!d->identify_data_valid) {
598                 errno = ENOTSUP;
599                 return -1;
600         }
601
602         *b = disk_smart_is_available(d);
603         return 0;
604 }
605
606 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
607
608         *b = d->identify_data_valid;
609         return 0;
610 }
611
612 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
613
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"
623         };
624         /* %STRINGPOOLSTOP% */
625
626         if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
627                 return NULL;
628
629         return _P(map[status]);
630 }
631
632 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
633
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"
646         };
647         /* %STRINGPOOLSTOP% */
648
649         if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
650                 return NULL;
651
652         return _P(map[status]);
653 }
654
655 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
656
657         switch (test) {
658                 case SK_SMART_SELF_TEST_SHORT:
659                         return "short";
660                 case SK_SMART_SELF_TEST_EXTENDED:
661                         return "extended";
662                 case SK_SMART_SELF_TEST_CONVEYANCE:
663                         return "conveyance";
664                 case SK_SMART_SELF_TEST_ABORT:
665                         return "abort";
666         }
667
668         return NULL;
669 }
670
671 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
672
673         if (!d->start_test_available)
674                 return FALSE;
675
676         switch (test) {
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;
684                 default:
685                         return FALSE;
686         }
687 }
688
689 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
690
691         if (!sk_smart_self_test_available(d, test))
692                 return 0;
693
694         switch (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;
701                 default:
702                         return 0;
703         }
704 }
705
706 typedef struct SkSmartAttributeInfo {
707         const char *name;
708         SkSmartAttributeUnit unit;
709 } SkSmartAttributeInfo;
710
711 /* This data is stolen from smartmontools */
712
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 }
762 };
763 /* %STRINGPOOLSTOP% */
764
765 static void make_pretty(SkSmartAttributeParsedData *a) {
766         uint64_t fourtyeight;
767
768         if (!a->name)
769                 return;
770
771         if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
772                 return;
773
774         fourtyeight =
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);
781
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;
796         else
797                 a->pretty_value = fourtyeight;
798 }
799
800 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
801         const SkIdentifyParsedData *ipd;
802
803         /* These are the simple cases */
804         if (attribute_info[id].name)
805                 return &attribute_info[id];
806
807         /* These are the complex ones */
808         if (sk_disk_identify_parse(d, &ipd) < 0)
809                 return NULL;
810
811         switch (id) {
812                 /* We might want to add further special cases/quirks
813                  * here eventually. */
814
815                 case 9: {
816
817                         /* %STRINGPOOLSTART% */
818                         static const SkSmartAttributeInfo maxtor = {
819                                 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
820                         };
821                         static const SkSmartAttributeInfo fujitsu = {
822                                 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
823                         };
824                         static const SkSmartAttributeInfo others = {
825                                 "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
826                         };
827                         /* %STRINGPOOLSTOP% */
828
829                         if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
830                                 return &maxtor;
831                         else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
832                                 return &fujitsu;
833
834                         return &others;
835                 }
836         }
837
838         return NULL;
839 }
840
841 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
842
843         if (!d->smart_data_valid) {
844                 errno = ENOENT;
845                 return -1;
846         }
847
848         switch (d->smart_data[362]) {
849                 case 0x00:
850                 case 0x80:
851                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
852                         break;
853
854                 case 0x02:
855                 case 0x82:
856                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
857                         break;
858
859                 case 0x03:
860                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
861                         break;
862
863                 case 0x04:
864                 case 0x84:
865                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
866                         break;
867
868                 case 0x05:
869                 case 0x85:
870                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
871                         break;
872
873                 case 0x06:
874                 case 0x86:
875                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
876                         break;
877
878                 default:
879                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
880                         break;
881         }
882
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;
885
886         d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
887
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);
892
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];
896
897         *spd = &d->smart_parsed_data;
898
899         return 0;
900 }
901
902 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
903         uint8_t *p;
904         unsigned n;
905
906         if (!d->smart_threshold_data_valid) {
907                 a->threshold_valid = FALSE;
908                 return;
909         }
910
911         for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
912                 if (p[0] == a->id)
913                         break;
914
915         if (n >= 30) {
916                 a->threshold_valid = FALSE;
917                 a->good_valid = FALSE;
918                 return;
919         }
920
921         a->threshold = p[1];
922         a->threshold_valid = p[1] != 0xFE;
923
924         a->good_valid = FALSE;
925         a->good = TRUE;
926
927         /* Always-Fail and Always-Pssing thresholds are not relevant
928          * for our assessment. */
929         if (p[1] >= 1 && p[1] <= 0xFD) {
930
931                 if (a->worst_value_valid) {
932                         a->good = a->good && (a->worst_value > a->threshold);
933                         a->good_valid = TRUE;
934                 }
935
936                 if (a->current_value_valid) {
937                         a->good = a->good && (a->current_value > a->threshold);
938                         a->good_valid = TRUE;
939                 }
940         }
941 }
942
943 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
944         uint8_t *p;
945         unsigned n;
946
947         if (!d->smart_data_valid) {
948                 errno = ENOENT;
949                 return -1;
950         }
951
952         for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
953                 SkSmartAttributeParsedData a;
954                 const SkSmartAttributeInfo *i;
955                 char *an = NULL;
956
957                 if (p[0] == 0)
958                         continue;
959
960                 memset(&a, 0, sizeof(a));
961                 a.id = p[0];
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;
966
967                 a.flags = ((uint16_t) p[2] << 8) | p[1];
968                 a.prefailure = !!(p[1] & 1);
969                 a.online = !!(p[1] & 2);
970
971                 memcpy(a.raw, p+5, 6);
972
973                 if ((i = lookup_attribute(d, p[0]))) {
974                         a.name = _P(i->name);
975                         a.pretty_unit = i->unit;
976                 } else {
977                         if (asprintf(&an, "attribute-%u", a.id) < 0) {
978                                 errno = ENOMEM;
979                                 return -1;
980                         }
981
982                         a.name = an;
983                         a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
984                 }
985
986                 make_pretty(&a);
987
988                 find_threshold(d, &a);
989
990                 cb(d, &a, userdata);
991
992                 free(an);
993         }
994
995         return 0;
996 }
997
998 static const char *yes_no(SkBool b) {
999         return  b ? "yes" : "no";
1000 }
1001
1002 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
1003
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"
1011         };
1012         /* %STRINGPOOLSTOP% */
1013
1014         if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
1015                 return NULL;
1016
1017         return _P(map[unit]);
1018 }
1019
1020 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
1021
1022         if (k)
1023                 strncpy(s, k, len);
1024         else
1025                 snprintf(s, len, "%u", id);
1026
1027         s[len-1] = 0;
1028
1029         return s;
1030 }
1031
1032 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1033
1034         switch (a->pretty_unit) {
1035                 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1036
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));
1049                         else
1050                                 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1051
1052                         break;
1053
1054                 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1055                         snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1056                         break;
1057
1058                 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1059                         snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1060                         break;
1061
1062                 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1063                         snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1064                         break;
1065
1066                 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1067                         snprintf(s, len, "n/a");
1068                         break;
1069
1070                 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1071                         assert(FALSE);
1072         }
1073
1074         s[len-1] = 0;
1075
1076         return s;
1077 }
1078
1079 #define HIGHLIGHT "\x1B[1m"
1080 #define ENDHIGHLIGHT "\x1B[0m"
1081
1082 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1083         char name[32];
1084         char pretty[32];
1085         char tt[32], tw[32], tc[32];
1086         SkBool highlight;
1087
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;
1094
1095         highlight = a->good_valid && !a->good && isatty(1);
1096
1097         if (highlight)
1098                 fprintf(stderr, HIGHLIGHT);
1099
1100         printf("%3u %-27s %-3s   %-3s   %-3s   %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-3s\n",
1101                a->id,
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");
1111
1112         if (highlight)
1113                 fprintf(stderr, ENDHIGHLIGHT);
1114 }
1115
1116 int sk_disk_dump(SkDisk *d) {
1117         int ret;
1118         SkBool awake = FALSE;
1119
1120         printf("Device: %s\n"
1121                "Size: %lu MiB\n",
1122                d->name,
1123                (unsigned long) (d->size/1024/1024));
1124
1125         if (d->identify_data_valid) {
1126                 const SkIdentifyParsedData *ipd;
1127
1128                 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1129                         return ret;
1130
1131                 printf("Model: [%s]\n"
1132                        "Serial: [%s]\n"
1133                        "Firmware: [%s]\n"
1134                        "SMART Available: %s\n",
1135                        ipd->model,
1136                        ipd->serial,
1137                        ipd->firmware,
1138                        yes_no(disk_smart_is_available(d)));
1139         }
1140
1141         ret = sk_disk_check_sleep_mode(d, &awake);
1142         printf("Awake: %s\n",
1143                ret >= 0 ? yes_no(awake) : "unknown");
1144
1145         if (disk_smart_is_available(d)) {
1146                 const SkSmartParsedData *spd;
1147                 SkBool good;
1148
1149                 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1150                         return ret;
1151
1152                 printf("Disk Health Good: %s\n",
1153                         yes_no(good));
1154
1155                 if ((ret = sk_disk_smart_read_data(d)) < 0)
1156                         return ret;
1157
1158                 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1159                         return ret;
1160
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);
1183
1184                 printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-3s\n",
1185                        "ID#",
1186                        "Name",
1187                        "Value",
1188                        "Worst",
1189                        "Thres",
1190                        "Pretty",
1191                        "Raw",
1192                        "Type",
1193                        "Updates",
1194                        "Good");
1195
1196                 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1197                         return ret;
1198         }
1199
1200         return 0;
1201 }
1202
1203 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1204
1205         *bytes = d->size;
1206         return 0;
1207 }
1208
1209 int sk_disk_open(const char *name, SkDisk **_d) {
1210         SkDisk *d;
1211         int ret = -1;
1212         struct stat st;
1213
1214         assert(name);
1215         assert(_d);
1216
1217         if (!(d = calloc(1, sizeof(SkDisk)))) {
1218                 errno = ENOMEM;
1219                 goto fail;
1220         }
1221
1222         if (!(d->name = strdup(name))) {
1223                 errno = ENOMEM;
1224                 goto fail;
1225         }
1226
1227         if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1228                 ret = d->fd;
1229                 goto fail;
1230         }
1231
1232         if ((ret = fstat(d->fd, &st)) < 0)
1233                 goto fail;
1234
1235         if (!S_ISBLK(st.st_mode)) {
1236                 errno = ENODEV;
1237                 ret = -1;
1238                 goto fail;
1239         }
1240
1241         /* So, it's a block device. Let's make sure the ioctls work */
1242
1243         if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1244                 goto fail;
1245
1246         if (d->size <= 0 || d->size == (uint64_t) -1) {
1247                 errno = EIO;
1248                 ret = -1;
1249                 goto fail;
1250         }
1251
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)
1256                         break;
1257
1258         /* Check if driver can do SMART, and enable if necessary */
1259         if (disk_smart_is_available(d)) {
1260
1261                 if (!disk_smart_is_enabled(d)) {
1262                         if ((ret = disk_smart_enable(d, TRUE)) < 0)
1263                                 goto fail;
1264
1265                         if ((ret = disk_identify_device(d)) < 0)
1266                                 goto fail;
1267
1268                         if (!disk_smart_is_enabled(d)) {
1269                                 errno = EIO;
1270                                 ret = -1;
1271                                 goto fail;
1272                         }
1273                 }
1274
1275                 disk_smart_read_thresholds(d);
1276         }
1277
1278         *_d = d;
1279
1280         return 0;
1281
1282 fail:
1283
1284         if (d)
1285                 sk_disk_free(d);
1286
1287         return ret;
1288 }
1289
1290 void sk_disk_free(SkDisk *d) {
1291         assert(d);
1292
1293         if (d->fd >= 0)
1294                 close(d->fd);
1295
1296         free(d->name);
1297         free(d);
1298 }