set version info from configure script
[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 #include <sys/types.h>
44 #include <regex.h>
45
46 #include "atasmart.h"
47
48 #ifndef STRPOOL
49 #define _P(x) x
50 #endif
51
52 #define SK_TIMEOUT 2000
53
54 typedef enum SkDirection {
55         SK_DIRECTION_NONE,
56         SK_DIRECTION_IN,
57         SK_DIRECTION_OUT,
58         _SK_DIRECTION_MAX
59 } SkDirection;
60
61 typedef enum SkDiskType {
62         SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
63         SK_DISK_TYPE_ATA,
64         SK_DISK_TYPE_UNKNOWN,
65         _SK_DISK_TYPE_MAX
66 } SkDiskType;
67
68 struct SkDisk {
69         char *name;
70         int fd;
71         SkDiskType type;
72
73         uint64_t size;
74
75         uint8_t identify[512];
76         uint8_t smart_data[512];
77         uint8_t smart_threshold_data[512];
78
79         SkBool identify_data_valid:1;
80         SkBool smart_data_valid:1;
81         SkBool smart_threshold_data_valid:1;
82
83         SkIdentifyParsedData identify_parsed_data;
84         SkSmartParsedData smart_parsed_data;
85 };
86
87 /* ATA commands */
88 typedef enum SkAtaCommand {
89         SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
90         SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
91         SK_ATA_COMMAND_SMART = 0xB0,
92         SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
93 } SkAtaCommand;
94
95 /* ATA SMART subcommands (ATA8 7.52.1) */
96 typedef enum SkSmartCommand {
97         SK_SMART_COMMAND_READ_DATA = 0xD0,
98         SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
99         SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
100         SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
101         SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
102         SK_SMART_COMMAND_RETURN_STATUS = 0xDA
103 } SkSmartCommand;
104
105 static SkBool disk_smart_is_available(SkDisk *d) {
106         return d->identify_data_valid && !!(d->identify[164] & 1);
107 }
108
109 static SkBool disk_smart_is_enabled(SkDisk *d) {
110         return d->identify_data_valid && !!(d->identify[170] & 1);
111 }
112
113 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
114         assert(d->smart_data_valid);
115
116         return !!(d->smart_data[367] & 32);
117 }
118 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
119         assert(d->smart_data_valid);
120
121         return !!(d->smart_data[367] & 16);
122 }
123
124 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
125         assert(d->smart_data_valid);
126
127         return !!(d->smart_data[367] & 1);
128 }
129
130 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
131         assert(d->smart_data_valid);
132
133         return !!(d->smart_data[367] & 41);
134 }
135
136 static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
137         uint8_t *bytes = cmd_data;
138         int ret;
139
140         assert(d->type == SK_DISK_TYPE_ATA);
141
142         switch (direction) {
143
144                 case SK_DIRECTION_OUT:
145
146                         /* We could use HDIO_DRIVE_TASKFILE here, but
147                          * that's a deprecated ioctl(), hence we don't
148                          * do it. And we don't need writing anyway. */
149
150                         errno = ENOTSUP;
151                         return -1;
152
153                 case SK_DIRECTION_IN: {
154                         uint8_t *ioctl_data;
155
156                         /* We have HDIO_DRIVE_CMD which can only read, but not write,
157                          * and cannot do LBA. We use it for all read commands. */
158
159                         ioctl_data = alloca(4 + *len);
160                         memset(ioctl_data, 0, 4 + *len);
161
162                         ioctl_data[0] = (uint8_t) command;  /* COMMAND */
163                         ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3];  /* SECTOR/NSECTOR */
164                         ioctl_data[2] = bytes[1];          /* FEATURE */
165                         ioctl_data[3] = bytes[3];          /* NSECTOR */
166
167                         if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
168                                 return ret;
169
170                         memset(bytes, 0, 12);
171                         bytes[11] = ioctl_data[0];
172                         bytes[1] = ioctl_data[1];
173                         bytes[3] = ioctl_data[2];
174
175                         memcpy(data, ioctl_data+4, *len);
176
177                         return ret;
178                 }
179
180                 case SK_DIRECTION_NONE: {
181                         uint8_t ioctl_data[7];
182
183                         /* We have HDIO_DRIVE_TASK which can neither read nor
184                          * write, but can do LBA. We use it for all commands that
185                          * do neither read nor write */
186
187                         memset(ioctl_data, 0, sizeof(ioctl_data));
188
189                         ioctl_data[0] = (uint8_t) command;  /* COMMAND */
190                         ioctl_data[1] = bytes[1];         /* FEATURE */
191                         ioctl_data[2] = bytes[3];         /* NSECTOR */
192
193                         ioctl_data[3] = bytes[9];         /* LBA LOW */
194                         ioctl_data[4] = bytes[8];         /* LBA MID */
195                         ioctl_data[5] = bytes[7];         /* LBA HIGH */
196                         ioctl_data[6] = bytes[10];        /* SELECT */
197
198                         if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
199                                 return ret;
200
201                         memset(bytes, 0, 12);
202                         bytes[11] = ioctl_data[0];
203                         bytes[1] = ioctl_data[1];
204                         bytes[3] = ioctl_data[2];
205
206                         bytes[9] = ioctl_data[3];
207                         bytes[8] = ioctl_data[4];
208                         bytes[7] = ioctl_data[5];
209
210                         bytes[10] = ioctl_data[6];
211
212                         return ret;
213                 }
214
215                 default:
216                         assert(FALSE);
217                         return -1;
218         }
219 }
220
221 /* Sends a SCSI command block */
222 static int sg_io(int fd, int direction,
223                  const void *cdb, size_t cdb_len,
224                  void *data, size_t data_len,
225                  void *sense, size_t sense_len) {
226
227         struct sg_io_hdr io_hdr;
228
229         memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
230
231         io_hdr.interface_id = 'S';
232         io_hdr.cmdp = (unsigned char*) cdb;
233         io_hdr.cmd_len = cdb_len;
234         io_hdr.dxferp = data;
235         io_hdr.dxfer_len = data_len;
236         io_hdr.sbp = sense;
237         io_hdr.mx_sb_len = sense_len;
238         io_hdr.dxfer_direction = direction;
239         io_hdr.timeout = SK_TIMEOUT;
240
241         return ioctl(fd, SG_IO, &io_hdr);
242 }
243
244 static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
245         uint8_t *bytes = cmd_data;
246         uint8_t cdb[16];
247         uint8_t sense[32];
248         uint8_t *desc = sense+8;
249         int ret;
250
251         static const int direction_map[] = {
252                 [SK_DIRECTION_NONE] = SG_DXFER_NONE,
253                 [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
254                 [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
255         };
256
257         assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
258
259         /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
260          * ATA Command Pass-Through":
261          * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
262
263         memset(cdb, 0, sizeof(cdb));
264
265         cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
266
267         if (direction == SK_DIRECTION_NONE) {
268                 cdb[1] = 3 << 1;   /* PROTOCOL: Non-Data */
269                 cdb[2] = 0x20;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
270
271         } else if (direction == SK_DIRECTION_IN) {
272                 cdb[1] = 4 << 1;   /* PROTOCOL: PIO Data-in */
273                 cdb[2] = 0x2e;     /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
274
275         } else if (direction == SK_DIRECTION_OUT) {
276                 cdb[1] = 5 << 1;   /* PROTOCOL: PIO Data-Out */
277                 cdb[2] = 0x26;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
278         }
279
280         cdb[3] = bytes[0]; /* FEATURES */
281         cdb[4] = bytes[1];
282
283         cdb[5] = bytes[2]; /* SECTORS */
284         cdb[6] = bytes[3];
285
286         cdb[8] = bytes[9]; /* LBA LOW */
287         cdb[10] = bytes[8]; /* LBA MID */
288         cdb[12] = bytes[7]; /* LBA HIGH */
289
290         cdb[13] = bytes[10] & 0x4F; /* SELECT */
291         cdb[14] = (uint8_t) command;
292
293         memset(sense, 0, sizeof(sense));
294
295         if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
296                 return ret;
297
298         if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
299                 errno = EIO;
300                 return -1;
301         }
302
303         memset(bytes, 0, 12);
304
305         bytes[1] = desc[3];
306         bytes[2] = desc[4];
307         bytes[3] = desc[5];
308         bytes[9] = desc[7];
309         bytes[8] = desc[9];
310         bytes[7] = desc[11];
311         bytes[10] = desc[12];
312         bytes[11] = desc[13];
313
314         return ret;
315 }
316
317 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
318
319         static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = {
320                 [SK_DISK_TYPE_ATA] = disk_ata_command,
321                 [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
322         };
323
324         assert(d);
325         assert(d->type <= _SK_DISK_TYPE_MAX);
326         assert(direction <= _SK_DIRECTION_MAX);
327
328         assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
329         assert(direction != SK_DIRECTION_NONE || (!data && !len));
330
331         return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
332 }
333
334 static int disk_identify_device(SkDisk *d) {
335         uint16_t cmd[6];
336         int ret;
337         size_t len = 512;
338
339         memset(cmd, 0, sizeof(cmd));
340
341         cmd[1] = htons(1);
342
343         if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
344                 return ret;
345
346         if (len != 512) {
347                 errno = EIO;
348                 return -1;
349         }
350
351         d->identify_data_valid = TRUE;
352
353         return 0;
354 }
355
356 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
357         int ret;
358         uint16_t cmd[6];
359
360         if (!d->identify_data_valid) {
361                 errno = ENOTSUP;
362                 return -1;
363         }
364
365         memset(cmd, 0, sizeof(cmd));
366
367         if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
368                 return ret;
369
370         if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
371                 errno = EIO;
372                 return -1;
373         }
374
375         *awake = ntohs(cmd[1]) == 0xFF;
376
377         return 0;
378 }
379
380 static int disk_smart_enable(SkDisk *d, SkBool b) {
381         uint16_t cmd[6];
382
383         if (!disk_smart_is_available(d)) {
384                 errno = ENOTSUP;
385                 return -1;
386         }
387
388         memset(cmd, 0, sizeof(cmd));
389
390         cmd[0] = htons(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
391         cmd[2] = htons(0x0000U);
392         cmd[3] = htons(0x00C2U);
393         cmd[4] = htons(0x4F00U);
394
395         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
396 }
397
398 int sk_disk_smart_read_data(SkDisk *d) {
399         uint16_t cmd[6];
400         int ret;
401         size_t len = 512;
402
403         if (!disk_smart_is_available(d)) {
404                 errno = ENOTSUP;
405                 return -1;
406         }
407
408         memset(cmd, 0, sizeof(cmd));
409
410         cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
411         cmd[1] = htons(1);
412         cmd[2] = htons(0x0000U);
413         cmd[3] = htons(0x00C2U);
414         cmd[4] = htons(0x4F00U);
415
416         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
417                 return ret;
418
419         d->smart_data_valid = TRUE;
420
421         return ret;
422 }
423
424 static int disk_smart_read_thresholds(SkDisk *d) {
425         uint16_t cmd[6];
426         int ret;
427         size_t len = 512;
428
429         if (!disk_smart_is_available(d)) {
430                 errno = ENOTSUP;
431                 return -1;
432         }
433
434         memset(cmd, 0, sizeof(cmd));
435
436         cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
437         cmd[1] = htons(1);
438         cmd[2] = htons(0x0000U);
439         cmd[3] = htons(0x00C2U);
440         cmd[4] = htons(0x4F00U);
441
442         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
443                 return ret;
444
445         d->smart_threshold_data_valid = TRUE;
446
447         return ret;
448 }
449
450 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
451         uint16_t cmd[6];
452         int ret;
453
454         if (!disk_smart_is_available(d)) {
455                 errno = ENOTSUP;
456                 return -1;
457         }
458
459         memset(cmd, 0, sizeof(cmd));
460
461         cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
462         cmd[1] = htons(0x0000U);
463         cmd[3] = htons(0x00C2U);
464         cmd[4] = htons(0x4F00U);
465
466         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
467                 return ret;
468
469         if (cmd[3] == htons(0x00C2U) &&
470             cmd[4] == htons(0x4F00U))
471                 *good = TRUE;
472         else if (cmd[3] == htons(0x002CU) &&
473             cmd[4] == htons(0xF400U))
474                 *good = FALSE;
475         else {
476                 errno = EIO;
477                 return -1;
478         }
479
480         return ret;
481 }
482
483 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
484         uint16_t cmd[6];
485         int ret;
486
487         if (!disk_smart_is_available(d)) {
488                 errno = ENOTSUP;
489                 return -1;
490         }
491
492         if (!d->smart_data_valid)
493                 if ((ret = sk_disk_smart_read_data(d)) < 0)
494                         return -1;
495
496         assert(d->smart_data_valid);
497
498         if (test != SK_SMART_SELF_TEST_SHORT &&
499             test != SK_SMART_SELF_TEST_EXTENDED &&
500             test != SK_SMART_SELF_TEST_CONVEYANCE &&
501             test != SK_SMART_SELF_TEST_ABORT) {
502                 errno = EINVAL;
503                 return -1;
504         }
505
506         if (!disk_smart_is_start_test_available(d)
507             || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d))
508             || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d))
509             || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) {
510                 errno = ENOTSUP;
511                 return -1;
512         }
513
514         if (test == SK_SMART_SELF_TEST_ABORT &&
515             !disk_smart_is_abort_test_available(d)) {
516                 errno = ENOTSUP;
517                 return -1;
518         }
519
520         memset(cmd, 0, sizeof(cmd));
521
522         cmd[0] = htons(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE);
523         cmd[2] = htons(0x0000U);
524         cmd[3] = htons(0x00C2U);
525         cmd[4] = htons(0x4F00U | (uint16_t) test);
526
527         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
528 }
529
530 static void swap_strings(char *s, size_t len) {
531         assert((len & 1) == 0);
532
533         for (; len > 0; s += 2, len -= 2) {
534                 char t;
535                 t = s[0];
536                 s[0] = s[1];
537                 s[1] = t;
538         }
539 }
540
541 static void clean_strings(char *s) {
542         char *e;
543
544         for (e = s; *e; e++)
545                 if (*e < ' ' || *e >= 127)
546                         *e = ' ';
547 }
548
549 static void drop_spaces(char *s) {
550         char *d = s;
551         SkBool prev_space = FALSE;
552
553         s += strspn(s, " ");
554
555         for (;*s; s++) {
556
557                 if (prev_space) {
558                         if (*s != ' ') {
559                                 prev_space = FALSE;
560                                 *(d++) = ' ';
561                                 *(d++) = *s;
562                         }
563                 } else {
564                         if (*s == ' ')
565                                 prev_space = TRUE;
566                         else
567                                 *(d++) = *s;
568                 }
569         }
570
571         *d = 0;
572 }
573
574 static void read_string(char *d, uint8_t *s, size_t len) {
575         memcpy(d, s, len);
576         d[len] = 0;
577         swap_strings(d, len);
578         clean_strings(d);
579         drop_spaces(d);
580 }
581
582 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
583
584         if (!d->identify_data_valid) {
585                 errno = ENOENT;
586                 return -1;
587         }
588
589         read_string(d->identify_parsed_data.serial, d->identify+20, 20);
590         read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
591         read_string(d->identify_parsed_data.model, d->identify+54, 40);
592
593         *ipd = &d->identify_parsed_data;
594
595         return 0;
596 }
597
598 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
599
600         if (!d->identify_data_valid) {
601                 errno = ENOTSUP;
602                 return -1;
603         }
604
605         *b = disk_smart_is_available(d);
606         return 0;
607 }
608
609 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
610
611         *b = d->identify_data_valid;
612         return 0;
613 }
614
615 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
616
617         /* %STRINGPOOLSTART% */
618         static const char* const map[] = {
619                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
620                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
621                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
622                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
623                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
624                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
625                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
626         };
627         /* %STRINGPOOLSTOP% */
628
629         if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
630                 return NULL;
631
632         return _P(map[status]);
633 }
634
635 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
636
637         /* %STRINGPOOLSTART% */
638         static const char* const map[] = {
639                 [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.",
640                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.",
641                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.",
642                 [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.",
643                 [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.",
644                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.",
645                 [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.",
646                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.",
647                 [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.",
648                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress"
649         };
650         /* %STRINGPOOLSTOP% */
651
652         if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
653                 return NULL;
654
655         return _P(map[status]);
656 }
657
658 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
659
660         switch (test) {
661                 case SK_SMART_SELF_TEST_SHORT:
662                         return "short";
663                 case SK_SMART_SELF_TEST_EXTENDED:
664                         return "extended";
665                 case SK_SMART_SELF_TEST_CONVEYANCE:
666                         return "conveyance";
667                 case SK_SMART_SELF_TEST_ABORT:
668                         return "abort";
669         }
670
671         return NULL;
672 }
673
674 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
675
676         if (!d->start_test_available)
677                 return FALSE;
678
679         switch (test) {
680                 case SK_SMART_SELF_TEST_SHORT:
681                 case SK_SMART_SELF_TEST_EXTENDED:
682                         return d->short_and_extended_test_available;
683                 case SK_SMART_SELF_TEST_CONVEYANCE:
684                         return d->conveyance_test_available;
685                 case SK_SMART_SELF_TEST_ABORT:
686                         return d->abort_test_available;
687                 default:
688                         return FALSE;
689         }
690 }
691
692 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
693
694         if (!sk_smart_self_test_available(d, test))
695                 return 0;
696
697         switch (test) {
698                 case SK_SMART_SELF_TEST_SHORT:
699                         return d->short_test_polling_minutes;
700                 case SK_SMART_SELF_TEST_EXTENDED:
701                         return d->extended_test_polling_minutes;
702                 case SK_SMART_SELF_TEST_CONVEYANCE:
703                         return d->conveyance_test_polling_minutes;
704                 default:
705                         return 0;
706         }
707 }
708
709 static void make_pretty(SkSmartAttributeParsedData *a) {
710         uint64_t fourtyeight;
711
712         if (!a->name)
713                 return;
714
715         if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
716                 return;
717
718         fourtyeight =
719                 ((uint64_t) a->raw[0]) |
720                 (((uint64_t) a->raw[1]) << 8) |
721                 (((uint64_t) a->raw[2]) << 16) |
722                 (((uint64_t) a->raw[3]) << 24) |
723                 (((uint64_t) a->raw[4]) << 32) |
724                 (((uint64_t) a->raw[5]) << 40);
725
726         if (!strcmp(a->name, "spin-up-time"))
727                 a->pretty_value = fourtyeight & 0xFFFF;
728         else if (!strcmp(a->name, "airflow-temperature-celsius") ||
729                  !strcmp(a->name, "temperature-celsius-1") ||
730                  !strcmp(a->name, "temperature-celsius-2"))
731                 a->pretty_value = (fourtyeight & 0xFFFF)*1000 + 273150;
732         else if (!strcmp(a->name, "temperature-centi-celsius"))
733                 a->pretty_value = (fourtyeight & 0xFFFF)*100 + 273150;
734         else if (!strcmp(a->name, "power-on-minutes"))
735                 a->pretty_value = fourtyeight * 60 * 1000;
736         else if (!strcmp(a->name, "power-on-seconds"))
737                 a->pretty_value = fourtyeight * 1000;
738         else if (!strcmp(a->name, "power-on-half-minutes"))
739                 a->pretty_value = fourtyeight * 30 * 1000;
740         else if (!strcmp(a->name, "power-on-hours") ||
741                  !strcmp(a->name, "loaded-hours") ||
742                  !strcmp(a->name, "head-flying-hours"))
743                 a->pretty_value = fourtyeight * 60 * 60 * 1000;
744         else
745                 a->pretty_value = fourtyeight;
746 }
747
748 typedef struct SkSmartAttributeInfo {
749         const char *name;
750         SkSmartAttributeUnit unit;
751 } SkSmartAttributeInfo;
752
753 /* This data is stolen from smartmontools */
754
755 /* %STRINGPOOLSTART% */
756 static const SkSmartAttributeInfo const attribute_info[255] = {
757         [1]   = { "raw-read-error-rate",         SK_SMART_ATTRIBUTE_UNIT_NONE },
758         [2]   = { "throughput-perfomance",       SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
759         [3]   = { "spin-up-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
760         [4]   = { "start-stop-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
761         [5]   = { "reallocated-sector-count",    SK_SMART_ATTRIBUTE_UNIT_NONE },
762         [6]   = { "read-channel-margin",         SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
763         [7]   = { "seek-error-rate",             SK_SMART_ATTRIBUTE_UNIT_NONE },
764         [8]   = { "seek-time-perfomance",        SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
765         [9]   = { "power-on-hours",              SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
766         [10]  = { "spin-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
767         [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
768         [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
769         [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
770         [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS },
771         [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE },
772         [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
773         [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE },
774         [192] = { "power-off-retract-count-1",   SK_SMART_ATTRIBUTE_UNIT_NONE },
775         [193] = { "load-cycle-count-1",          SK_SMART_ATTRIBUTE_UNIT_NONE },
776         [194] = { "temperature-celsius-2",       SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
777         [195] = { "hardware-ecc-recovered",      SK_SMART_ATTRIBUTE_UNIT_NONE },
778         [196] = { "reallocated-event-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
779         [197] = { "current-pending-sector",      SK_SMART_ATTRIBUTE_UNIT_SECTORS },
780         [198] = { "offline-uncorrectable",       SK_SMART_ATTRIBUTE_UNIT_SECTORS },
781         [199] = { "udma-crc-error-count",        SK_SMART_ATTRIBUTE_UNIT_NONE },
782         [200] = { "multi-zone-error-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE },
783         [201] = { "soft-read-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
784         [202] = { "ta-increase-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
785         [203] = { "run-out-cancel",              SK_SMART_ATTRIBUTE_UNIT_NONE },
786         [204] = { "shock-count-write-opern",     SK_SMART_ATTRIBUTE_UNIT_NONE },
787         [205] = { "shock-rate-write-opern",      SK_SMART_ATTRIBUTE_UNIT_NONE },
788         [206] = { "flying-height",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
789         [207] = { "spin-high-current",           SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
790         [208] = { "spin-buzz",                   SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
791         [209] = { "offline-seek-perfomance",     SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
792         [220] = { "disk-shift",                  SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
793         [221] = { "g-sense-error-rate-2",        SK_SMART_ATTRIBUTE_UNIT_NONE },
794         [222] = { "loaded-hours",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
795         [223] = { "load-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
796         [224] = { "load-friction",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
797         [225] = { "load-cycle-count-2",          SK_SMART_ATTRIBUTE_UNIT_NONE },
798         [226] = { "load-in-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
799         [227] = { "torq-amp-count",              SK_SMART_ATTRIBUTE_UNIT_NONE },
800         [228] = { "power-off-retract-count-2",   SK_SMART_ATTRIBUTE_UNIT_NONE },
801         [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
802         [231] = { "temperature-celsius-1",       SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
803         [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
804         [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE }
805 };
806 /* %STRINGPOOLSTOP% */
807
808 typedef enum SkSmartQuirk {
809         SK_SMART_QUIRK_9_POWERONMINUTES = 1,
810         SK_SMART_QUIRK_9_POWERONSECONDS = 2,
811         SK_SMART_QUIRK_9_POWERONHALFMINUTES = 4,
812         SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 8,
813         SK_SMART_QUIRK_193_LOADUNLOAD = 16,
814         SK_SMART_QUIRK_194_10XCELSIUS = 32,
815         SK_SMART_QUIRK_194_UNKNOWN = 64,
816         SK_SMART_QUIRK_200_WRITEERRORCOUNT = 128,
817         SK_SMART_QUIRK_201_DETECTEDTACOUNT = 256,
818 } SkSmartQuirk;
819
820 /* %STRINGPOOLSTART% */
821 static const char *quirk_name[] = {
822         "9_POWERONMINUTES",
823         "9_POWERONSECONDS",
824         "9_POWERONHALFMINUTES",
825         "192_EMERGENCYRETRACTCYCLECT",
826         "193_LOADUNLOAD",
827         "194_10XCELSIUS",
828         "194_UNKNOWN",
829         "200_WRITEERRORCOUNT",
830         "201_DETECTEDTACOUNT",
831         NULL
832 };
833 /* %STRINGPOOLSTOP% */
834
835 typedef struct SkSmartQuirkDatabase {
836         const char *model;
837         const char *firmware;
838         SkSmartQuirk quirk;
839 } SkSmartQuirkDatabase;
840
841 /* %STRINGPOOLSTART% */
842 static const SkSmartQuirkDatabase quirk_database[] = { {
843                 "^FUJITSU MHR2040AT$",
844                 NULL,
845                 SK_SMART_QUIRK_9_POWERONSECONDS|
846                 SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT|
847                 SK_SMART_QUIRK_200_WRITEERRORCOUNT
848         }, {
849                 "^FUJITSU MHS20[6432]0AT(  .)?$",
850                 NULL,
851                 SK_SMART_QUIRK_9_POWERONSECONDS|
852                 SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT|
853                 SK_SMART_QUIRK_200_WRITEERRORCOUNT|
854                 SK_SMART_QUIRK_201_DETECTEDTACOUNT
855
856         }, {
857                 "^SAMSUNG SV4012H$",
858                 NULL,
859                 SK_SMART_QUIRK_9_POWERONHALFMINUTES
860         }, {
861                 "^SAMSUNG SV0412H$",
862                 NULL,
863                 SK_SMART_QUIRK_9_POWERONHALFMINUTES|
864                 SK_SMART_QUIRK_194_10XCELSIUS
865         }, {
866                 "^SAMSUNG SV1204H$",
867                 NULL,
868                 SK_SMART_QUIRK_9_POWERONHALFMINUTES|
869                 SK_SMART_QUIRK_194_10XCELSIUS
870         }, {
871                 "^SAMSUNG SP40A2H$",
872                 "^RR100-07$",
873                 SK_SMART_QUIRK_9_POWERONHALFMINUTES
874         }, {
875                 "^SAMSUNG SP8004H$",
876                 "^QW100-61$",
877                 SK_SMART_QUIRK_9_POWERONHALFMINUTES
878         }, {
879                 "^SAMSUNG",
880                 ".*-(2[3-9]|3[0-9])$",
881                 SK_SMART_QUIRK_9_POWERONHALFMINUTES
882
883         }, {
884                 "^Maxtor 2B0(0[468]|1[05]|20)H1$",
885                 NULL,
886                 SK_SMART_QUIRK_9_POWERONMINUTES|
887                 SK_SMART_QUIRK_194_UNKNOWN
888         }, {
889                 "^Maxtor 4G(120J6|160J[68])$",
890                 NULL,
891                 SK_SMART_QUIRK_9_POWERONMINUTES|
892                 SK_SMART_QUIRK_194_UNKNOWN
893         }, {
894                 "^Maxtor 4D0(20H1|40H2|60H3|80H4)$",
895                 NULL,
896                 SK_SMART_QUIRK_9_POWERONMINUTES|
897                 SK_SMART_QUIRK_194_UNKNOWN
898
899         }, {
900                 "^HITACHI_DK14FA-20B$",
901                 NULL,
902                 SK_SMART_QUIRK_9_POWERONMINUTES|
903                 SK_SMART_QUIRK_193_LOADUNLOAD
904         }, {
905                 "^HITACHI_DK23..-..B?$",
906                 NULL,
907                 SK_SMART_QUIRK_9_POWERONMINUTES|
908                 SK_SMART_QUIRK_193_LOADUNLOAD
909         }, {
910                 "^(HITACHI_DK23FA-20J|HTA422020F9AT[JN]0)$",
911                 NULL,
912                 SK_SMART_QUIRK_9_POWERONMINUTES|
913                 SK_SMART_QUIRK_193_LOADUNLOAD
914
915         }, {
916                 "Maxtor",
917                 NULL,
918                 SK_SMART_QUIRK_9_POWERONMINUTES
919         }, {
920                 "MAXTOR",
921                 NULL,
922                 SK_SMART_QUIRK_9_POWERONMINUTES
923         }, {
924                 "Fujitsu",
925                 NULL,
926                 SK_SMART_QUIRK_9_POWERONSECONDS
927         }, {
928                 "FUJITSU",
929                 NULL,
930                 SK_SMART_QUIRK_9_POWERONSECONDS
931         }, {
932                 NULL,
933                 NULL,
934                 0
935         }
936 };
937 /* %STRINGPOOLSTOP% */
938
939 static int match(const char*regex, const char *s, SkBool *result) {
940         int k;
941         regex_t re;
942
943         *result = FALSE;
944
945         if (regcomp(&re, regex, REG_EXTENDED|REG_NOSUB) != 0) {
946                 errno = EINVAL;
947                 return -1;
948         }
949
950         if ((k = regexec(&re, s, 0, NULL, 0)) != 0) {
951
952                 if (k != REG_NOMATCH) {
953                         regfree(&re);
954                         errno = EINVAL;
955                         return -1;
956                 }
957
958         } else
959                 *result = TRUE;
960
961         regfree(&re);
962
963         return 0;
964 }
965
966 static int lookup_quirks(const char *model, const char *firmware, SkSmartQuirk *quirk) {
967         int k;
968         const SkSmartQuirkDatabase *db;
969
970         *quirk = 0;
971
972         for (db = quirk_database; db->model || db->firmware; db++) {
973
974                 if (db->model) {
975                         SkBool matching = FALSE;
976
977                         if ((k = match(_P(db->model), model, &matching)) < 0)
978                                 return k;
979
980                         if (!matching)
981                                 continue;
982                 }
983
984                 if (db->firmware) {
985                         SkBool matching = FALSE;
986
987                         if ((k = match(_P(db->firmware), firmware, &matching)) < 0)
988                                 return k;
989
990                         if (!matching)
991                                 continue;
992                 }
993
994                 *quirk = db->quirk;
995                 return 0;
996         }
997
998         return 0;
999 }
1000
1001 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
1002         const SkIdentifyParsedData *ipd;
1003         SkSmartQuirk quirk = 0;
1004
1005         /* These are the complex ones */
1006         if (sk_disk_identify_parse(d, &ipd) < 0)
1007                 return NULL;
1008
1009         if (lookup_quirks(ipd->model, ipd->firmware, &quirk) < 0)
1010                 return NULL;
1011
1012         if (quirk) {
1013                 switch (id) {
1014
1015                         case 9:
1016                                 /* %STRINGPOOLSTART% */
1017                                 if (quirk & SK_SMART_QUIRK_9_POWERONMINUTES) {
1018                                         static const SkSmartAttributeInfo a = {
1019                                                 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
1020                                         };
1021                                         return &a;
1022
1023                                 } else if (quirk & SK_SMART_QUIRK_9_POWERONSECONDS) {
1024                                         static const SkSmartAttributeInfo a = {
1025                                                 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
1026                                         };
1027                                         return &a;
1028
1029                                 } else if (quirk & SK_SMART_QUIRK_9_POWERONHALFMINUTES) {
1030                                         static const SkSmartAttributeInfo a = {
1031                                                 "power-on-half-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
1032                                         };
1033                                         return &a;
1034                                 }
1035                                 /* %STRINGPOOLSTOP% */
1036
1037                                 break;
1038
1039                         case 192:
1040                                 /* %STRINGPOOLSTART% */
1041                                 if (quirk & SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT) {
1042                                         static const SkSmartAttributeInfo a = {
1043                                                 "emergency-retract-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE
1044                                         };
1045                                         return &a;
1046                                 }
1047                                 /* %STRINGPOOLSTOP% */
1048
1049                                 break;
1050
1051                         case 194:
1052                                 /* %STRINGPOOLSTART% */
1053                                 if (quirk & SK_SMART_QUIRK_194_10XCELSIUS) {
1054                                         static const SkSmartAttributeInfo a = {
1055                                                 "temperature-centi-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN
1056                                         };
1057                                         return &a;
1058                                 } else if (quirk & SK_SMART_QUIRK_194_UNKNOWN)
1059                                         return NULL;
1060                                 /* %STRINGPOOLSTOP% */
1061
1062                                 break;
1063
1064                         case 200:
1065                                 /* %STRINGPOOLSTART% */
1066                                 if (quirk & SK_SMART_QUIRK_200_WRITEERRORCOUNT) {
1067                                         static const SkSmartAttributeInfo a = {
1068                                                 "write-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE
1069                                         };
1070                                         return &a;
1071                                 }
1072                                 /* %STRINGPOOLSTOP% */
1073
1074                                 break;
1075
1076                         case 201:
1077                                 /* %STRINGPOOLSTART% */
1078                                 if (quirk & SK_SMART_QUIRK_201_DETECTEDTACOUNT) {
1079                                         static const SkSmartAttributeInfo a = {
1080                                                 "detected-ta-count", SK_SMART_ATTRIBUTE_UNIT_NONE
1081                                         };
1082                                         return &a;
1083                                 }
1084                                 /* %STRINGPOOLSTOP% */
1085
1086                                 break;
1087                 }
1088         }
1089
1090         /* These are the simple cases */
1091         if (attribute_info[id].name)
1092                 return &attribute_info[id];
1093
1094         return NULL;
1095 }
1096
1097 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
1098
1099         if (!d->smart_data_valid) {
1100                 errno = ENOENT;
1101                 return -1;
1102         }
1103
1104         switch (d->smart_data[362]) {
1105                 case 0x00:
1106                 case 0x80:
1107                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
1108                         break;
1109
1110                 case 0x02:
1111                 case 0x82:
1112                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
1113                         break;
1114
1115                 case 0x03:
1116                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
1117                         break;
1118
1119                 case 0x04:
1120                 case 0x84:
1121                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
1122                         break;
1123
1124                 case 0x05:
1125                 case 0x85:
1126                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
1127                         break;
1128
1129                 case 0x06:
1130                 case 0x86:
1131                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
1132                         break;
1133
1134                 default:
1135                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
1136                         break;
1137         }
1138
1139         d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
1140         d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF;
1141
1142         d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
1143
1144         d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d);
1145         d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d);
1146         d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d);
1147         d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d);
1148
1149         d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
1150         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]);
1151         d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
1152
1153         *spd = &d->smart_parsed_data;
1154
1155         return 0;
1156 }
1157
1158 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
1159         uint8_t *p;
1160         unsigned n;
1161
1162         if (!d->smart_threshold_data_valid) {
1163                 a->threshold_valid = FALSE;
1164                 return;
1165         }
1166
1167         for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
1168                 if (p[0] == a->id)
1169                         break;
1170
1171         if (n >= 30) {
1172                 a->threshold_valid = FALSE;
1173                 a->good_valid = FALSE;
1174                 return;
1175         }
1176
1177         a->threshold = p[1];
1178         a->threshold_valid = p[1] != 0xFE;
1179
1180         a->good_valid = FALSE;
1181         a->good = TRUE;
1182
1183         /* Always-Fail and Always-Pssing thresholds are not relevant
1184          * for our assessment. */
1185         if (p[1] >= 1 && p[1] <= 0xFD) {
1186
1187                 if (a->worst_value_valid) {
1188                         a->good = a->good && (a->worst_value > a->threshold);
1189                         a->good_valid = TRUE;
1190                 }
1191
1192                 if (a->current_value_valid) {
1193                         a->good = a->good && (a->current_value > a->threshold);
1194                         a->good_valid = TRUE;
1195                 }
1196         }
1197 }
1198
1199 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
1200         uint8_t *p;
1201         unsigned n;
1202
1203         if (!d->smart_data_valid) {
1204                 errno = ENOENT;
1205                 return -1;
1206         }
1207
1208         for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
1209                 SkSmartAttributeParsedData a;
1210                 const SkSmartAttributeInfo *i;
1211                 char *an = NULL;
1212
1213                 if (p[0] == 0)
1214                         continue;
1215
1216                 memset(&a, 0, sizeof(a));
1217                 a.id = p[0];
1218                 a.current_value = p[3];
1219                 a.current_value_valid = p[3] >= 1 && p[3] <= 0xFD;
1220                 a.worst_value = p[4];
1221                 a.worst_value_valid = p[4] >= 1 && p[4] <= 0xFD;
1222
1223                 a.flags = ((uint16_t) p[2] << 8) | p[1];
1224                 a.prefailure = !!(p[1] & 1);
1225                 a.online = !!(p[1] & 2);
1226
1227                 memcpy(a.raw, p+5, 6);
1228
1229                 if ((i = lookup_attribute(d, p[0]))) {
1230                         a.name = _P(i->name);
1231                         a.pretty_unit = i->unit;
1232                 } else {
1233                         if (asprintf(&an, "attribute-%u", a.id) < 0) {
1234                                 errno = ENOMEM;
1235                                 return -1;
1236                         }
1237
1238                         a.name = an;
1239                         a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
1240                 }
1241
1242                 make_pretty(&a);
1243
1244                 find_threshold(d, &a);
1245
1246                 cb(d, &a, userdata);
1247
1248                 free(an);
1249         }
1250
1251         return 0;
1252 }
1253
1254 static const char *yes_no(SkBool b) {
1255         return  b ? "yes" : "no";
1256 }
1257
1258 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
1259
1260         /* %STRINGPOOLSTART% */
1261         const char * const map[] = {
1262                 [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
1263                 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
1264                 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
1265                 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
1266                 [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
1267         };
1268         /* %STRINGPOOLSTOP% */
1269
1270         if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
1271                 return NULL;
1272
1273         return _P(map[unit]);
1274 }
1275
1276 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
1277
1278         if (k)
1279                 strncpy(s, k, len);
1280         else
1281                 snprintf(s, len, "%u", id);
1282
1283         s[len-1] = 0;
1284
1285         return s;
1286 }
1287
1288 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1289
1290         switch (a->pretty_unit) {
1291                 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1292
1293                         if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
1294                                 snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
1295                         else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
1296                                 snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
1297                         else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
1298                                 snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
1299                         else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
1300                                 snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
1301                         else if (a->pretty_value >= 1000LLU*60LLU)
1302                                 snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
1303                         else if (a->pretty_value >= 1000LLU)
1304                                 snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
1305                         else
1306                                 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1307
1308                         break;
1309
1310                 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1311                         snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1312                         break;
1313
1314                 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1315                         snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1316                         break;
1317
1318                 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1319                         snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1320                         break;
1321
1322                 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1323                         snprintf(s, len, "n/a");
1324                         break;
1325
1326                 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1327                         assert(FALSE);
1328         }
1329
1330         s[len-1] = 0;
1331
1332         return s;
1333 }
1334
1335 #define HIGHLIGHT "\x1B[1m"
1336 #define ENDHIGHLIGHT "\x1B[0m"
1337
1338 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1339         char name[32];
1340         char pretty[32];
1341         char tt[32], tw[32], tc[32];
1342         SkBool highlight;
1343
1344         snprintf(tt, sizeof(tt), "%3u", a->threshold);
1345         tt[sizeof(tt)-1] = 0;
1346         snprintf(tw, sizeof(tw), "%3u", a->worst_value);
1347         tw[sizeof(tw)-1] = 0;
1348         snprintf(tc, sizeof(tc), "%3u", a->current_value);
1349         tc[sizeof(tc)-1] = 0;
1350
1351         highlight = a->good_valid && !a->good && isatty(1);
1352
1353         if (highlight)
1354                 fprintf(stderr, HIGHLIGHT);
1355
1356         printf("%3u %-27s %-3s   %-3s   %-3s   %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-3s\n",
1357                a->id,
1358                print_name(name, sizeof(name), a->id, a->name),
1359                a->current_value_valid ? tc : "n/a",
1360                a->worst_value_valid ? tw : "n/a",
1361                a->threshold_valid ? tt : "n/a",
1362                print_value(pretty, sizeof(pretty), a),
1363                a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5],
1364                a->prefailure ? "prefail" : "old-age",
1365                a->online ? "online" : "offline",
1366                a->good_valid ? yes_no(a->good) : "n/a");
1367
1368         if (highlight)
1369                 fprintf(stderr, ENDHIGHLIGHT);
1370 }
1371
1372 int sk_disk_dump(SkDisk *d) {
1373         int ret;
1374         SkBool awake = FALSE;
1375
1376         printf("Device: %s\n"
1377                "Size: %lu MiB\n",
1378                d->name,
1379                (unsigned long) (d->size/1024/1024));
1380
1381         if (d->identify_data_valid) {
1382                 const SkIdentifyParsedData *ipd;
1383                 SkSmartQuirk quirk = 0;
1384                 unsigned i;
1385
1386                 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1387                         return ret;
1388
1389                 printf("Model: [%s]\n"
1390                        "Serial: [%s]\n"
1391                        "Firmware: [%s]\n"
1392                        "SMART Available: %s\n",
1393                        ipd->model,
1394                        ipd->serial,
1395                        ipd->firmware,
1396                        yes_no(disk_smart_is_available(d)));
1397
1398                 if ((ret = lookup_quirks(ipd->model, ipd->firmware, &quirk)))
1399                         return ret;
1400
1401                 printf("Quirks:");
1402
1403                 for (i = 0; quirk_name[i]; i++)
1404                         if (quirk & (1<<i))
1405                                 printf(" %s", _P(quirk_name[i]));
1406
1407                 printf("\n");
1408
1409         }
1410
1411         ret = sk_disk_check_sleep_mode(d, &awake);
1412         printf("Awake: %s\n",
1413                ret >= 0 ? yes_no(awake) : "unknown");
1414
1415         if (disk_smart_is_available(d)) {
1416                 const SkSmartParsedData *spd;
1417                 SkBool good;
1418
1419                 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1420                         return ret;
1421
1422                 printf("Disk Health Good: %s\n",
1423                         yes_no(good));
1424
1425                 if ((ret = sk_disk_smart_read_data(d)) < 0)
1426                         return ret;
1427
1428                 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1429                         return ret;
1430
1431                 printf("Off-line Data Collection Status: [%s]\n"
1432                        "Total Time To Complete Off-Line Data Collection: %u s\n"
1433                        "Self-Test Execution Status: [%s]\n"
1434                        "Percent Self-Test Remaining: %u%%\n"
1435                        "Conveyance Self-Test Available: %s\n"
1436                        "Short/Extended Self-Test Available: %s\n"
1437                        "Start Self-Test Available: %s\n"
1438                        "Abort Self-Test Available: %s\n"
1439                        "Short Self-Test Polling Time: %u min\n"
1440                        "Extended Self-Test Polling Time: %u min\n"
1441                        "Conveyance Self-Test Polling Time: %u min\n",
1442                        sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
1443                        spd->total_offline_data_collection_seconds,
1444                        sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
1445                        spd->self_test_execution_percent_remaining,
1446                        yes_no(spd->conveyance_test_available),
1447                        yes_no(spd->short_and_extended_test_available),
1448                        yes_no(spd->start_test_available),
1449                        yes_no(spd->abort_test_available),
1450                         spd->short_test_polling_minutes,
1451                        spd->extended_test_polling_minutes,
1452                        spd->conveyance_test_polling_minutes);
1453
1454                 printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-3s\n",
1455                        "ID#",
1456                        "Name",
1457                        "Value",
1458                        "Worst",
1459                        "Thres",
1460                        "Pretty",
1461                        "Raw",
1462                        "Type",
1463                        "Updates",
1464                        "Good");
1465
1466                 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1467                         return ret;
1468         }
1469
1470         return 0;
1471 }
1472
1473 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1474
1475         *bytes = d->size;
1476         return 0;
1477 }
1478
1479 int sk_disk_open(const char *name, SkDisk **_d) {
1480         SkDisk *d;
1481         int ret = -1;
1482         struct stat st;
1483
1484         assert(name);
1485         assert(_d);
1486
1487         if (!(d = calloc(1, sizeof(SkDisk)))) {
1488                 errno = ENOMEM;
1489                 goto fail;
1490         }
1491
1492         if (!(d->name = strdup(name))) {
1493                 errno = ENOMEM;
1494                 goto fail;
1495         }
1496
1497         if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1498                 ret = d->fd;
1499                 goto fail;
1500         }
1501
1502         if ((ret = fstat(d->fd, &st)) < 0)
1503                 goto fail;
1504
1505         if (!S_ISBLK(st.st_mode)) {
1506                 errno = ENODEV;
1507                 ret = -1;
1508                 goto fail;
1509         }
1510
1511         /* So, it's a block device. Let's make sure the ioctls work */
1512
1513         if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1514                 goto fail;
1515
1516         if (d->size <= 0 || d->size == (uint64_t) -1) {
1517                 errno = EIO;
1518                 ret = -1;
1519                 goto fail;
1520         }
1521
1522         /* OK, it's a real block device with a size. Find a way to
1523          * identify the device. */
1524         for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
1525                 if (disk_identify_device(d) >= 0)
1526                         break;
1527
1528         /* Check if driver can do SMART, and enable if necessary */
1529         if (disk_smart_is_available(d)) {
1530
1531                 if (!disk_smart_is_enabled(d)) {
1532                         if ((ret = disk_smart_enable(d, TRUE)) < 0)
1533                                 goto fail;
1534
1535                         if ((ret = disk_identify_device(d)) < 0)
1536                                 goto fail;
1537
1538                         if (!disk_smart_is_enabled(d)) {
1539                                 errno = EIO;
1540                                 ret = -1;
1541                                 goto fail;
1542                         }
1543                 }
1544
1545                 disk_smart_read_thresholds(d);
1546         }
1547
1548         *_d = d;
1549
1550         return 0;
1551
1552 fail:
1553
1554         if (d)
1555                 sk_disk_free(d);
1556
1557         return ret;
1558 }
1559
1560 void sk_disk_free(SkDisk *d) {
1561         assert(d);
1562
1563         if (d->fd >= 0)
1564                 close(d->fd);
1565
1566         free(d->name);
1567         free(d);
1568 }