fix copyright blurb
[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 #define _GNU_SOURCE
28
29 #include <arpa/inet.h>
30 #include <stdlib.h>
31 #include <alloca.h>
32 #include <assert.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <string.h>
37 #include <stdio.h>
38 #include <sys/stat.h>
39 #include <sys/ioctl.h>
40 #include <scsi/scsi.h>
41 #include <scsi/sg.h>
42 #include <scsi/scsi_ioctl.h>
43 #include <linux/hdreg.h>
44 #include <linux/fs.h>
45
46 #include "atasmart.h"
47
48 #define SK_TIMEOUT 2000
49
50 typedef enum SkDirection {
51         SK_DIRECTION_NONE,
52         SK_DIRECTION_IN,
53         SK_DIRECTION_OUT,
54         _SK_DIRECTION_MAX
55 } SkDirection;
56
57 typedef enum SkDiskType {
58         SK_DISK_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
59         SK_DISK_TYPE_ATA,
60         SK_DISK_TYPE_UNKNOWN,
61         _SK_DISK_TYPE_MAX
62 } SkDiskType;
63
64 struct SkDisk {
65         char *name;
66         int fd;
67         SkDiskType type;
68
69         uint64_t size;
70
71         uint8_t identify[512];
72         uint8_t smart_data[512];
73         uint8_t smart_threshold_data[512];
74
75         SkBool identify_data_valid:1;
76         SkBool smart_data_valid:1;
77         SkBool smart_threshold_data_valid:1;
78
79         SkIdentifyParsedData identify_parsed_data;
80         SkSmartParsedData smart_parsed_data;
81 };
82
83 /* ATA commands */
84 typedef enum SkAtaCommand {
85         SK_ATA_COMMAND_IDENTIFY_DEVICE = 0xEC,
86         SK_ATA_COMMAND_IDENTIFY_PACKET_DEVICE = 0xA1,
87         SK_ATA_COMMAND_SMART = 0xB0,
88         SK_ATA_COMMAND_CHECK_POWER_MODE = 0xE5
89 } SkAtaCommand;
90
91 /* ATA SMART subcommands (ATA8 7.52.1) */
92 typedef enum SkSmartCommand {
93         SK_SMART_COMMAND_READ_DATA = 0xD0,
94         SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
95         SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
96         SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
97         SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
98         SK_SMART_COMMAND_RETURN_STATUS = 0xDA
99 } SkSmartCommand;
100
101 static SkBool disk_smart_is_available(SkDisk *d) {
102         return d->identify_data_valid && !!(d->identify[164] & 1);
103 }
104
105 static SkBool disk_smart_is_enabled(SkDisk *d) {
106         return d->identify_data_valid && !!(d->identify[170] & 1);
107 }
108
109 static SkBool disk_smart_is_conveyance_test_available(SkDisk *d) {
110         assert(d->smart_data_valid);
111
112         return !!(d->smart_data[367] & 32);
113 }
114 static SkBool disk_smart_is_short_and_extended_test_available(SkDisk *d) {
115         assert(d->smart_data_valid);
116
117         return !!(d->smart_data[367] & 16);
118 }
119
120 static SkBool disk_smart_is_start_test_available(SkDisk *d) {
121         assert(d->smart_data_valid);
122
123         return !!(d->smart_data[367] & 1);
124 }
125
126 static SkBool disk_smart_is_abort_test_available(SkDisk *d) {
127         assert(d->smart_data_valid);
128
129         return !!(d->smart_data[367] & 41);
130 }
131
132 static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
133         uint8_t *bytes = cmd_data;
134         int ret;
135
136         assert(d->type == SK_DISK_TYPE_ATA);
137
138         switch (direction) {
139
140                 case SK_DIRECTION_OUT:
141
142                         /* We could use HDIO_DRIVE_TASKFILE here, but
143                          * that's a deprecated ioctl(), hence we don't
144                          * do it. And we don't need writing anyway. */
145
146                         errno = ENOTSUP;
147                         return -1;
148
149                 case SK_DIRECTION_IN: {
150                         uint8_t *ioctl_data;
151
152                         /* We have HDIO_DRIVE_CMD which can only read, but not write,
153                          * and cannot do LBA. We use it for all read commands. */
154
155                         ioctl_data = alloca(4 + *len);
156                         memset(ioctl_data, 0, 4 + *len);
157
158                         ioctl_data[0] = (uint8_t) command;  /* COMMAND */
159                         ioctl_data[1] = ioctl_data[0] == WIN_SMART ? bytes[9] : bytes[3];  /* SECTOR/NSECTOR */
160                         ioctl_data[2] = bytes[1];          /* FEATURE */
161                         ioctl_data[3] = bytes[3];          /* NSECTOR */
162
163                         if ((ret = ioctl(d->fd, HDIO_DRIVE_CMD, ioctl_data)) < 0)
164                                 return ret;
165
166                         memset(bytes, 0, 12);
167                         bytes[11] = ioctl_data[0];
168                         bytes[1] = ioctl_data[1];
169                         bytes[3] = ioctl_data[2];
170
171                         memcpy(data, ioctl_data+4, *len);
172
173                         return ret;
174                 }
175
176                 case SK_DIRECTION_NONE: {
177                         uint8_t ioctl_data[7];
178
179                         /* We have HDIO_DRIVE_TASK which can neither read nor
180                          * write, but can do LBA. We use it for all commands that
181                          * do neither read nor write */
182
183                         memset(ioctl_data, 0, sizeof(ioctl_data));
184
185                         ioctl_data[0] = (uint8_t) command;  /* COMMAND */
186                         ioctl_data[1] = bytes[1];         /* FEATURE */
187                         ioctl_data[2] = bytes[3];         /* NSECTOR */
188
189                         ioctl_data[3] = bytes[9];         /* LBA LOW */
190                         ioctl_data[4] = bytes[8];         /* LBA MID */
191                         ioctl_data[5] = bytes[7];         /* LBA HIGH */
192                         ioctl_data[6] = bytes[10];        /* SELECT */
193
194                         if ((ret = ioctl(d->fd, HDIO_DRIVE_TASK, ioctl_data)))
195                                 return ret;
196
197                         memset(bytes, 0, 12);
198                         bytes[11] = ioctl_data[0];
199                         bytes[1] = ioctl_data[1];
200                         bytes[3] = ioctl_data[2];
201
202                         bytes[9] = ioctl_data[3];
203                         bytes[8] = ioctl_data[4];
204                         bytes[7] = ioctl_data[5];
205
206                         bytes[10] = ioctl_data[6];
207
208                         return ret;
209                 }
210
211                 default:
212                         assert(FALSE);
213                         return -1;
214         }
215 }
216
217 /* Sends a SCSI command block */
218 static int sg_io(int fd, int direction,
219                  const void *cdb, size_t cdb_len,
220                  void *data, size_t data_len,
221                  void *sense, size_t sense_len) {
222
223         struct sg_io_hdr io_hdr;
224
225         memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
226
227         io_hdr.interface_id = 'S';
228         io_hdr.cmdp = (unsigned char*) cdb;
229         io_hdr.cmd_len = cdb_len;
230         io_hdr.dxferp = data;
231         io_hdr.dxfer_len = data_len;
232         io_hdr.sbp = sense;
233         io_hdr.mx_sb_len = sense_len;
234         io_hdr.dxfer_direction = direction;
235         io_hdr.timeout = SK_TIMEOUT;
236
237         return ioctl(fd, SG_IO, &io_hdr);
238 }
239
240 static int disk_passthrough_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
241         uint8_t *bytes = cmd_data;
242         uint8_t cdb[16];
243         uint8_t sense[32];
244         uint8_t *desc = sense+8;
245         int ret;
246
247         static const int direction_map[] = {
248                 [SK_DIRECTION_NONE] = SG_DXFER_NONE,
249                 [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
250                 [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
251         };
252
253         assert(d->type == SK_DISK_TYPE_ATA_PASSTHROUGH);
254
255         /* ATA Pass-Through 16 byte command, as described in "T10 04-262r8
256          * ATA Command Pass-Through":
257          * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */
258
259         memset(cdb, 0, sizeof(cdb));
260
261         cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */
262
263         if (direction == SK_DIRECTION_NONE) {
264                 cdb[1] = 3 << 1;   /* PROTOCOL: Non-Data */
265                 cdb[2] = 0x20;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
266
267         } else if (direction == SK_DIRECTION_IN) {
268                 cdb[1] = 4 << 1;   /* PROTOCOL: PIO Data-in */
269                 cdb[2] = 0x2e;     /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
270
271         } else if (direction == SK_DIRECTION_OUT) {
272                 cdb[1] = 5 << 1;   /* PROTOCOL: PIO Data-Out */
273                 cdb[2] = 0x26;     /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
274         }
275
276         cdb[3] = bytes[0]; /* FEATURES */
277         cdb[4] = bytes[1];
278
279         cdb[5] = bytes[2]; /* SECTORS */
280         cdb[6] = bytes[3];
281
282         cdb[8] = bytes[9]; /* LBA LOW */
283         cdb[10] = bytes[8]; /* LBA MID */
284         cdb[12] = bytes[7]; /* LBA HIGH */
285
286         cdb[13] = bytes[10] & 0x4F; /* SELECT */
287         cdb[14] = (uint8_t) command;
288
289         if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
290                 return ret;
291
292         if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
293                 errno = EIO;
294                 return -1;
295         }
296
297         memset(bytes, 0, 12);
298
299         bytes[1] = desc[3];
300         bytes[2] = desc[4];
301         bytes[3] = desc[5];
302         bytes[9] = desc[7];
303         bytes[8] = desc[9];
304         bytes[7] = desc[11];
305         bytes[10] = desc[12];
306         bytes[11] = desc[13];
307
308         return ret;
309 }
310
311 static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
312
313         static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = {
314                 [SK_DISK_TYPE_ATA] = disk_ata_command,
315                 [SK_DISK_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
316         };
317
318         assert(d);
319         assert(d->type <= _SK_DISK_TYPE_MAX);
320         assert(direction <= _SK_DIRECTION_MAX);
321
322         assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
323         assert(direction != SK_DIRECTION_NONE || (!data && !len));
324
325         return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
326 }
327
328 static int disk_identify_device(SkDisk *d) {
329         uint16_t cmd[6];
330         int ret;
331         size_t len = 512;
332
333         memset(cmd, 0, sizeof(cmd));
334
335         cmd[1] = htons(1);
336
337         if ((ret = disk_command(d, SK_ATA_COMMAND_IDENTIFY_DEVICE, SK_DIRECTION_IN, cmd, d->identify, &len)) < 0)
338                 return ret;
339
340         if (len != 512) {
341                 errno = EIO;
342                 return -1;
343         }
344
345         d->identify_data_valid = TRUE;
346
347         return 0;
348 }
349
350 int sk_disk_check_sleep_mode(SkDisk *d, SkBool *awake) {
351         int ret;
352         uint16_t cmd[6];
353
354         if (!d->identify_data_valid) {
355                 errno = ENOTSUP;
356                 return -1;
357         }
358
359         memset(cmd, 0, sizeof(cmd));
360
361         if ((ret = disk_command(d, SK_ATA_COMMAND_CHECK_POWER_MODE, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
362                 return ret;
363
364         if (cmd[0] != 0 || (ntohs(cmd[5]) & 1) != 0) {
365                 errno = EIO;
366                 return -1;
367         }
368
369         *awake = ntohs(cmd[1]) == 0xFF;
370
371         return 0;
372 }
373
374 static int disk_smart_enable(SkDisk *d, SkBool b) {
375         uint16_t cmd[6];
376
377         if (!disk_smart_is_available(d)) {
378                 errno = ENOTSUP;
379                 return -1;
380         }
381
382         memset(cmd, 0, sizeof(cmd));
383
384         cmd[0] = htons(b ? SK_SMART_COMMAND_ENABLE_OPERATIONS : SK_SMART_COMMAND_DISABLE_OPERATIONS);
385         cmd[2] = htons(0x0000U);
386         cmd[3] = htons(0x00C2U);
387         cmd[4] = htons(0x4F00U);
388
389         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0);
390 }
391
392 int sk_disk_smart_read_data(SkDisk *d) {
393         uint16_t cmd[6];
394         int ret;
395         size_t len = 512;
396
397         if (!disk_smart_is_available(d)) {
398                 errno = ENOTSUP;
399                 return -1;
400         }
401
402         memset(cmd, 0, sizeof(cmd));
403
404         cmd[0] = htons(SK_SMART_COMMAND_READ_DATA);
405         cmd[1] = htons(1);
406         cmd[2] = htons(0x0000U);
407         cmd[3] = htons(0x00C2U);
408         cmd[4] = htons(0x4F00U);
409
410         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_data, &len)) < 0)
411                 return ret;
412
413         d->smart_data_valid = TRUE;
414
415         return ret;
416 }
417
418 static int disk_smart_read_thresholds(SkDisk *d) {
419         uint16_t cmd[6];
420         int ret;
421         size_t len = 512;
422
423         if (!disk_smart_is_available(d)) {
424                 errno = ENOTSUP;
425                 return -1;
426         }
427
428         memset(cmd, 0, sizeof(cmd));
429
430         cmd[0] = htons(SK_SMART_COMMAND_READ_THRESHOLDS);
431         cmd[1] = htons(1);
432         cmd[2] = htons(0x0000U);
433         cmd[3] = htons(0x00C2U);
434         cmd[4] = htons(0x4F00U);
435
436         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
437                 return ret;
438
439         d->smart_threshold_data_valid = TRUE;
440
441         return ret;
442 }
443
444 int sk_disk_smart_status(SkDisk *d, SkBool *good) {
445         uint16_t cmd[6];
446         int ret;
447
448         if (!disk_smart_is_available(d)) {
449                 errno = ENOTSUP;
450                 return -1;
451         }
452
453         memset(cmd, 0, sizeof(cmd));
454
455         cmd[0] = htons(SK_SMART_COMMAND_RETURN_STATUS);
456         cmd[1] = htons(0x0000U);
457         cmd[3] = htons(0x00C2U);
458         cmd[4] = htons(0x4F00U);
459
460         if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, 0)) < 0)
461                 return ret;
462
463         if (cmd[3] == htons(0x00C2U) &&
464             cmd[4] == htons(0x4F00U))
465                 *good = TRUE;
466         else if (cmd[3] == htons(0x002CU) &&
467             cmd[4] == htons(0xF400U))
468                 *good = FALSE;
469         else {
470                 errno = EIO;
471                 return -1;
472         }
473
474         return ret;
475 }
476
477 int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
478         uint16_t cmd[6];
479         int ret;
480
481         if (!disk_smart_is_available(d)) {
482                 errno = ENOTSUP;
483                 return -1;
484         }
485
486         if (!d->smart_data_valid)
487                 if ((ret = sk_disk_smart_read_data(d)) < 0)
488                         return -1;
489
490         assert(d->smart_data_valid);
491
492         if (test != SK_SMART_SELF_TEST_SHORT &&
493             test != SK_SMART_SELF_TEST_EXTENDED &&
494             test != SK_SMART_SELF_TEST_CONVEYANCE &&
495             test != SK_SMART_SELF_TEST_ABORT) {
496                 errno = EINVAL;
497                 return -1;
498         }
499
500         if (!disk_smart_is_start_test_available(d)
501             || (test == SK_SMART_SELF_TEST_ABORT && !disk_smart_is_abort_test_available(d))
502             || ((test == SK_SMART_SELF_TEST_SHORT || test == SK_SMART_SELF_TEST_EXTENDED) && !disk_smart_is_short_and_extended_test_available(d))
503             || (test == SK_SMART_SELF_TEST_CONVEYANCE && !disk_smart_is_conveyance_test_available(d))) {
504                 errno = ENOTSUP;
505                 return -1;
506         }
507
508         if (test == SK_SMART_SELF_TEST_ABORT &&
509             !disk_smart_is_abort_test_available(d)) {
510                 errno = ENOTSUP;
511                 return -1;
512         }
513
514         memset(cmd, 0, sizeof(cmd));
515
516         cmd[0] = htons(SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE);
517         cmd[2] = htons(0x0000U);
518         cmd[3] = htons(0x00C2U);
519         cmd[4] = htons(0x4F00U | (uint16_t) test);
520
521         return disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_NONE, cmd, NULL, NULL);
522 }
523
524 static void swap_strings(char *s, size_t len) {
525         assert((len & 1) == 0);
526
527         for (; len > 0; s += 2, len -= 2) {
528                 char t;
529                 t = s[0];
530                 s[0] = s[1];
531                 s[1] = t;
532         }
533 }
534
535 static void clean_strings(char *s) {
536         char *e;
537
538         for (e = s; *e; e++)
539                 if (*e < ' ' || *e >= 127)
540                         *e = ' ';
541 }
542
543 static void drop_spaces(char *s) {
544         char *d = s;
545         SkBool prev_space = FALSE;
546
547         s += strspn(s, " ");
548
549         for (;*s; s++) {
550
551                 if (prev_space) {
552                         if (*s != ' ') {
553                                 prev_space = FALSE;
554                                 *(d++) = ' ';
555                         }
556                 } else {
557                         if (*s == ' ')
558                                 prev_space = TRUE;
559                         else
560                                 *(d++) = *s;
561                 }
562         }
563
564         *d = 0;
565 }
566
567 static void read_string(char *d, uint8_t *s, size_t len) {
568         memcpy(d, s, len);
569         d[len] = 0;
570         swap_strings(d, len);
571         clean_strings(d);
572         drop_spaces(d);
573 }
574
575 int sk_disk_identify_parse(SkDisk *d, const SkIdentifyParsedData **ipd) {
576
577         if (!d->identify_data_valid) {
578                 errno = ENOENT;
579                 return -1;
580         }
581
582         read_string(d->identify_parsed_data.serial, d->identify+20, 20);
583         read_string(d->identify_parsed_data.firmware, d->identify+46, 8);
584         read_string(d->identify_parsed_data.model, d->identify+54, 40);
585
586         *ipd = &d->identify_parsed_data;
587
588         return 0;
589 }
590
591 int sk_disk_smart_is_available(SkDisk *d, SkBool *b) {
592
593         if (!d->identify_data_valid) {
594                 errno = ENOTSUP;
595                 return -1;
596         }
597
598         *b = disk_smart_is_available(d);
599         return 0;
600 }
601
602 int sk_disk_identify_is_available(SkDisk *d, SkBool *b) {
603
604         *b = d->identify_data_valid;
605         return 0;
606 }
607
608 const char *sk_smart_offline_data_collection_status_to_string(SkSmartOfflineDataCollectionStatus status) {
609
610         static const char* const map[] = {
611                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER] = "Off-line data collection activity was never started.",
612                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS] = "Off-line data collection activity was completed without error.",
613                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS] = "Off-line activity in progress.",
614                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED] = "Off-line data collection activity was suspended by an interrupting command from host.",
615                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED] = "Off-line data collection activity was aborted by an interrupting command from host.",
616                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL] = "Off-line data collection activity was aborted by the device with a fatal error.",
617                 [SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN] = "Unknown status"
618         };
619
620         if (status >= _SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_MAX)
621                 return NULL;
622
623         return map[status];
624 }
625
626 const char *sk_smart_self_test_execution_status_to_string(SkSmartSelfTestExecutionStatus status) {
627
628         static const char* const map[] = {
629                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_SUCCESS_OR_NEVER] = "The previous self-test routine completed without error or no self-test has ever been run.",
630                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ABORTED] = "The self-test routine was aborted by the host.",
631                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INTERRUPTED] = "The self-test routine was interrupted by the host with a hardware or software reset.",
632                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_FATAL] = "A fatal error or unknown test error occurred while the device was executing its self-test routine and the device was unable to complete the self-test routine.",
633                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_UNKNOWN] = "The previous self-test completed having a test element that failed and the test element that failed.",
634                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_ELECTRICAL] = "The previous self-test completed having the electrical element of the test failed.",
635                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_SERVO] = "The previous self-test completed having the servo (and/or seek) test element of the test failed.",
636                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_READ] = "The previous self-test completed having the read element of the test failed.",
637                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_ERROR_HANDLING] = "The previous self-test completed having a test element that failed and the device is suspected of having handling damage.",
638                 [SK_SMART_SELF_TEST_EXECUTION_STATUS_INPROGRESS] = "Self-test routine in progress"
639         };
640
641         if (status >= _SK_SMART_SELF_TEST_EXECUTION_STATUS_MAX)
642                 return NULL;
643
644         return map[status];
645 }
646
647 const char* sk_smart_self_test_to_string(SkSmartSelfTest test) {
648
649         switch (test) {
650                 case SK_SMART_SELF_TEST_SHORT:
651                         return "short";
652                 case SK_SMART_SELF_TEST_EXTENDED:
653                         return "extended";
654                 case SK_SMART_SELF_TEST_CONVEYANCE:
655                         return "conveyance";
656                 case SK_SMART_SELF_TEST_ABORT:
657                         return "abort";
658         }
659
660         return NULL;
661 }
662
663 SkBool sk_smart_self_test_available(const SkSmartParsedData *d, SkSmartSelfTest test) {
664
665         if (!d->start_test_available)
666                 return FALSE;
667
668         switch (test) {
669                 case SK_SMART_SELF_TEST_SHORT:
670                 case SK_SMART_SELF_TEST_EXTENDED:
671                         return d->short_and_extended_test_available;
672                 case SK_SMART_SELF_TEST_CONVEYANCE:
673                         return d->conveyance_test_available;
674                 case SK_SMART_SELF_TEST_ABORT:
675                         return d->abort_test_available;
676                 default:
677                         return FALSE;
678         }
679 }
680
681 unsigned sk_smart_self_test_polling_minutes(const SkSmartParsedData *d, SkSmartSelfTest test) {
682
683         if (!sk_smart_self_test_available(d, test))
684                 return 0;
685
686         switch (test) {
687                 case SK_SMART_SELF_TEST_SHORT:
688                         return d->short_test_polling_minutes;
689                 case SK_SMART_SELF_TEST_EXTENDED:
690                         return d->extended_test_polling_minutes;
691                 case SK_SMART_SELF_TEST_CONVEYANCE:
692                         return d->conveyance_test_polling_minutes;
693                 default:
694                         return 0;
695         }
696 }
697
698 typedef struct SkSmartAttributeInfo {
699         const char *name;
700         SkSmartAttributeUnit unit;
701 } SkSmartAttributeInfo;
702
703 /* This data is stolen from smartmontools */
704 static const SkSmartAttributeInfo const attribute_info[255] = {
705         [1]   = { "raw-read-error-rate",         SK_SMART_ATTRIBUTE_UNIT_NONE },
706         [2]   = { "throughput-perfomance",       SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
707         [3]   = { "spin-up-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
708         [4]   = { "start-stop-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
709         [5]   = { "reallocated-sector-count",    SK_SMART_ATTRIBUTE_UNIT_NONE },
710         [6]   = { "read-channel-margin",         SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
711         [7]   = { "seek-error-rate",             SK_SMART_ATTRIBUTE_UNIT_NONE },
712         [8]   = { "seek-time-perfomance",        SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
713         [10]  = { "spin-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
714         [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
715         [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
716         [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
717         [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS },
718         [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE },
719         [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
720         [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE },
721         [192] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
722         [193] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
723         [194] = { "temperature-celsius-2",       SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
724         [195] = { "hardware-ecc-recovered",      SK_SMART_ATTRIBUTE_UNIT_NONE },
725         [196] = { "reallocated-event-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
726         [197] = { "current-pending-sector",      SK_SMART_ATTRIBUTE_UNIT_SECTORS },
727         [198] = { "offline-uncorrectable",       SK_SMART_ATTRIBUTE_UNIT_SECTORS },
728         [199] = { "udma-crc-error-count",        SK_SMART_ATTRIBUTE_UNIT_NONE },
729         [200] = { "multi-zone-error-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE },
730         [201] = { "soft-read-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE },
731         [202] = { "ta-increase-count",           SK_SMART_ATTRIBUTE_UNIT_NONE },
732         [203] = { "run-out-cancel",              SK_SMART_ATTRIBUTE_UNIT_NONE },
733         [204] = { "shock-count-write-opern",     SK_SMART_ATTRIBUTE_UNIT_NONE },
734         [205] = { "shock-rate-write-opern",      SK_SMART_ATTRIBUTE_UNIT_NONE },
735         [206] = { "flying-height",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
736         [207] = { "spin-high-current",           SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
737         [208] = { "spin-buzz",                   SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
738         [209] = { "offline-seek-perfomance",     SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
739         [220] = { "disk-shift",                  SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
740         [221] = { "g-sense-error-rate-2",        SK_SMART_ATTRIBUTE_UNIT_NONE },
741         [222] = { "loaded-hours",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
742         [223] = { "load-retry-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
743         [224] = { "load-friction",               SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
744         [225] = { "load-cycle-count",            SK_SMART_ATTRIBUTE_UNIT_NONE },
745         [226] = { "load-in-time",                SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
746         [227] = { "torq-amp-count",              SK_SMART_ATTRIBUTE_UNIT_NONE },
747         [228] = { "power-off-retract-count",     SK_SMART_ATTRIBUTE_UNIT_NONE },
748         [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
749         [231] = { "temperature-celsius-1",       SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
750         [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
751         [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE }
752 };
753
754 static void make_pretty(SkSmartAttributeParsedData *a) {
755         uint64_t fourtyeight;
756
757         if (!a->name)
758                 return;
759
760         if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
761                 return;
762
763         fourtyeight =
764                 ((uint64_t) a->raw[0]) |
765                 (((uint64_t) a->raw[1]) << 8) |
766                 (((uint64_t) a->raw[2]) << 16) |
767                 (((uint64_t) a->raw[3]) << 24) |
768                 (((uint64_t) a->raw[4]) << 32) |
769                 (((uint64_t) a->raw[5]) << 40);
770
771         if (!strcmp(a->name, "spin-up-time"))
772                 a->pretty_value = fourtyeight & 0xFFFF;
773         else if (!strcmp(a->name, "airflow-temperature-celsius") ||
774                  !strcmp(a->name, "temperature-celsius-1") ||
775                  !strcmp(a->name, "temperature-celsius-2")) {
776                 a->pretty_value = (fourtyeight & 0xFFFF)*1000 + 273150;
777         } else if (!strcmp(a->name, "power-on-minutes"))
778                 a->pretty_value = fourtyeight * 60 * 1000;
779         else if (!strcmp(a->name, "power-on-seconds"))
780                 a->pretty_value = fourtyeight * 1000;
781         else if (!strcmp(a->name, "power-on-hours") ||
782                  !strcmp(a->name, "loaded-hours") ||
783                  !strcmp(a->name, "head-flying-hours"))
784                 a->pretty_value = fourtyeight * 60 * 60 * 1000;
785         else
786                 a->pretty_value = fourtyeight;
787 }
788
789 static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
790         const SkIdentifyParsedData *ipd;
791
792         /* These are the simple cases */
793         if (attribute_info[id].name)
794                 return &attribute_info[id];
795
796         /* These are the complex ones */
797         if (sk_disk_identify_parse(d, &ipd) < 0)
798                 return NULL;
799
800         switch (id) {
801                 /* We might want to add further special cases/quirks
802                  * here eventually. */
803
804                 case 9: {
805
806                         static const SkSmartAttributeInfo maxtor = {
807                                 "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
808                         };
809                         static const SkSmartAttributeInfo fujitsu = {
810                                 "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
811                         };
812                         static const SkSmartAttributeInfo others = {
813                                 "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
814                         };
815
816                         if (strstr(ipd->model, "Maxtor") || strstr(ipd->model, "MAXTOR"))
817                                 return &maxtor;
818                         else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
819                                 return &fujitsu;
820
821                         return &others;
822                 }
823         }
824
825         return NULL;
826 }
827
828 int sk_disk_smart_parse(SkDisk *d, const SkSmartParsedData **spd) {
829
830         if (!d->smart_data_valid) {
831                 errno = ENOENT;
832                 return -1;
833         }
834
835         switch (d->smart_data[362]) {
836                 case 0x00:
837                 case 0x80:
838                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_NEVER;
839                         break;
840
841                 case 0x02:
842                 case 0x82:
843                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUCCESS;
844                         break;
845
846                 case 0x03:
847                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_INPROGRESS;
848                         break;
849
850                 case 0x04:
851                 case 0x84:
852                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_SUSPENDED;
853                         break;
854
855                 case 0x05:
856                 case 0x85:
857                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_ABORTED;
858                         break;
859
860                 case 0x06:
861                 case 0x86:
862                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_FATAL;
863                         break;
864
865                 default:
866                         d->smart_parsed_data.offline_data_collection_status = SK_SMART_OFFLINE_DATA_COLLECTION_STATUS_UNKNOWN;
867                         break;
868         }
869
870         d->smart_parsed_data.self_test_execution_percent_remaining = 10*(d->smart_data[363] & 0xF);
871         d->smart_parsed_data.self_test_execution_status = (d->smart_data[363] >> 4) & 0xF;
872
873         d->smart_parsed_data.total_offline_data_collection_seconds = (uint16_t) d->smart_data[364] | ((uint16_t) d->smart_data[365] << 8);
874
875         d->smart_parsed_data.conveyance_test_available = disk_smart_is_conveyance_test_available(d);
876         d->smart_parsed_data.short_and_extended_test_available = disk_smart_is_short_and_extended_test_available(d);
877         d->smart_parsed_data.start_test_available = disk_smart_is_start_test_available(d);
878         d->smart_parsed_data.abort_test_available = disk_smart_is_abort_test_available(d);
879
880         d->smart_parsed_data.short_test_polling_minutes = d->smart_data[372];
881         d->smart_parsed_data.extended_test_polling_minutes = d->smart_data[373] != 0xFF ? d->smart_data[373] : ((uint16_t) d->smart_data[376] << 8 | (uint16_t) d->smart_data[375]);
882         d->smart_parsed_data.conveyance_test_polling_minutes = d->smart_data[374];
883
884         *spd = &d->smart_parsed_data;
885
886         return 0;
887 }
888
889 static void find_threshold(SkDisk *d, SkSmartAttributeParsedData *a) {
890         uint8_t *p;
891         unsigned n;
892
893         if (!d->smart_threshold_data_valid) {
894                 a->threshold_valid = FALSE;
895                 return;
896         }
897
898         for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
899                 if (p[0] == a->id)
900                         break;
901
902         if (n >= 30) {
903                 a->threshold_valid = FALSE;
904                 return;
905         }
906
907         a->threshold = p[1];
908         a->threshold_valid = TRUE;
909
910         a->good =
911                 a->worst_value > a->threshold &&
912                 a->current_value > a->threshold;
913 }
914
915 int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
916         uint8_t *p;
917         unsigned n;
918
919         if (!d->smart_data_valid) {
920                 errno = ENOENT;
921                 return -1;
922         }
923
924         for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
925                 SkSmartAttributeParsedData a;
926                 const SkSmartAttributeInfo *i;
927                 char *an = NULL;
928
929                 if (p[0] == 0)
930                         continue;
931
932                 memset(&a, 0, sizeof(a));
933                 a.id = p[0];
934                 a.current_value = p[3];
935                 a.worst_value = p[4];
936
937                 a.flags = ((uint16_t) p[2] << 8) | p[1];
938                 a.prefailure = !!(p[1] & 1);
939                 a.online = !!(p[1] & 2);
940
941                 memcpy(a.raw, p+5, 6);
942
943                 if ((i = lookup_attribute(d, p[0]))) {
944                         a.name = i->name;
945                         a.pretty_unit = i->unit;
946                 } else {
947                         if (asprintf(&an, "attribute-%u", a.id) < 0) {
948                                 errno = ENOMEM;
949                                 return -1;
950                         }
951
952                         a.name = an;
953                         a.pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
954                 }
955
956                 make_pretty(&a);
957
958                 find_threshold(d, &a);
959
960                 cb(d, &a, userdata);
961
962                 free(an);
963         }
964
965         return 0;
966 }
967
968 static const char *yes_no(SkBool b) {
969         return  b ? "yes" : "no";
970 }
971
972 const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
973
974         const char * const map[] = {
975                 [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
976                 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
977                 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
978                 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
979                 [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
980         };
981
982         if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
983                 return NULL;
984
985         return map[unit];
986 }
987
988 static char* print_name(char *s, size_t len, uint8_t id, const char *k) {
989
990         if (k)
991                 strncpy(s, k, len);
992         else
993                 snprintf(s, len, "%u", id);
994
995         s[len-1] = 0;
996
997         return s;
998
999 }
1000
1001 static char *print_value(char *s, size_t len, const SkSmartAttributeParsedData *a) {
1002
1003         switch (a->pretty_unit) {
1004                 case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
1005
1006                         if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
1007                                 snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
1008                         else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
1009                                 snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
1010                         else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
1011                                 snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
1012                         else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
1013                                 snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
1014                         else if (a->pretty_value >= 1000LLU*60LLU)
1015                                 snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
1016                         else if (a->pretty_value >= 1000LLU)
1017                                 snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
1018                         else
1019                                 snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
1020
1021                         break;
1022
1023                 case SK_SMART_ATTRIBUTE_UNIT_MKELVIN:
1024                         snprintf(s, len, "%0.1f C", ((double) a->pretty_value - 273150) / 1000);
1025                         break;
1026
1027                 case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
1028                         snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
1029                         break;
1030
1031                 case SK_SMART_ATTRIBUTE_UNIT_NONE:
1032                         snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
1033                         break;
1034
1035                 case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
1036                         snprintf(s, len, "n/a");
1037                         break;
1038
1039                 case _SK_SMART_ATTRIBUTE_UNIT_MAX:
1040                         assert(FALSE);
1041         }
1042
1043         s[len-1] = 0;
1044
1045         return s;
1046 };
1047
1048 #define HIGHLIGHT "\x1B[1m"
1049 #define ENDHIGHLIGHT "\x1B[0m"
1050
1051 static void disk_dump_attributes(SkDisk *d, const SkSmartAttributeParsedData *a, void* userdata) {
1052         char name[32];
1053         char pretty[32];
1054         char t[32];
1055
1056         snprintf(t, sizeof(t), "%3u", a->threshold);
1057         t[sizeof(t)-1] = 0;
1058
1059         if (!a->good  && isatty(1))
1060                 fprintf(stderr, HIGHLIGHT);
1061
1062         printf("%3u %-27s %3u   %3u   %-3s   %-11s %-7s %-7s %-3s\n",
1063                a->id,
1064                print_name(name, sizeof(name), a->id, a->name),
1065                a->current_value,
1066                a->worst_value,
1067                a->threshold_valid ? t : "n/a",
1068                print_value(pretty, sizeof(pretty), a),
1069                a->prefailure ? "prefail" : "old-age",
1070                a->online ? "online" : "offline",
1071                yes_no(a->good));
1072
1073         if (!a->good && isatty(1))
1074                 fprintf(stderr, ENDHIGHLIGHT);
1075 }
1076
1077 int sk_disk_dump(SkDisk *d) {
1078         int ret;
1079         SkBool awake = FALSE;
1080
1081         printf("Device: %s\n"
1082                "Size: %lu MiB\n",
1083                d->name,
1084                (unsigned long) (d->size/1024/1024));
1085
1086         if (d->identify_data_valid) {
1087                 const SkIdentifyParsedData *ipd;
1088
1089                 if ((ret = sk_disk_identify_parse(d, &ipd)) < 0)
1090                         return ret;
1091
1092                 printf("Model: [%s]\n"
1093                        "Serial: [%s]\n"
1094                        "Firmware: [%s]\n"
1095                        "SMART Available: %s\n",
1096                        ipd->model,
1097                        ipd->serial,
1098                        ipd->firmware,
1099                        yes_no(disk_smart_is_available(d)));
1100         }
1101
1102         ret = sk_disk_check_sleep_mode(d, &awake);
1103         printf("Awake: %s\n",
1104                ret >= 0 ? yes_no(awake) : "unknown");
1105
1106         if (disk_smart_is_available(d)) {
1107                 const SkSmartParsedData *spd;
1108                 SkBool good;
1109
1110                 if ((ret = sk_disk_smart_status(d, &good)) < 0)
1111                         return ret;
1112
1113                 printf("Disk Health Good: %s\n",
1114                         yes_no(good));
1115
1116                 if ((ret = sk_disk_smart_read_data(d)) < 0)
1117                         return ret;
1118
1119                 if ((ret = sk_disk_smart_parse(d, &spd)) < 0)
1120                         return ret;
1121
1122                 printf("Off-line Data Collection Status: [%s]\n"
1123                        "Total Time To Complete Off-Line Data Collection: %u s\n"
1124                        "Self-Test Execution Status: [%s]\n"
1125                        "Percent Self-Test Remaining: %u%%\n"
1126                        "Conveyance Self-Test Available: %s\n"
1127                        "Short/Extended Self-Test Available: %s\n"
1128                        "Start Self-Test Available: %s\n"
1129                        "Abort Self-Test Available: %s\n"
1130                        "Short Self-Test Polling Time: %u min\n"
1131                        "Extended Self-Test Polling Time: %u min\n"
1132                        "Conveyance Self-Test Polling Time: %u min\n",
1133                        sk_smart_offline_data_collection_status_to_string(spd->offline_data_collection_status),
1134                        spd->total_offline_data_collection_seconds,
1135                        sk_smart_self_test_execution_status_to_string(spd->self_test_execution_status),
1136                        spd->self_test_execution_percent_remaining,
1137                        yes_no(spd->conveyance_test_available),
1138                        yes_no(spd->short_and_extended_test_available),
1139                        yes_no(spd->start_test_available),
1140                        yes_no(spd->abort_test_available),
1141                         spd->short_test_polling_minutes,
1142                        spd->extended_test_polling_minutes,
1143                        spd->conveyance_test_polling_minutes);
1144
1145                 printf("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n",
1146                        "ID#",
1147                        "Name",
1148                        "Value",
1149                        "Worst",
1150                        "Thres",
1151                        "Pretty",
1152                        "Type",
1153                        "Updates",
1154                        "Good");
1155
1156                 if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
1157                         return ret;
1158         }
1159
1160         return 0;
1161 }
1162
1163 int sk_disk_get_size(SkDisk *d, uint64_t *bytes) {
1164
1165         *bytes = d->size;
1166         return 0;
1167 }
1168
1169 int sk_disk_open(const char *name, SkDisk **_d) {
1170         SkDisk *d;
1171         int ret = -1;
1172         struct stat st;
1173
1174         assert(name);
1175         assert(_d);
1176
1177         if (!(d = calloc(1, sizeof(SkDisk)))) {
1178                 errno = ENOMEM;
1179                 goto fail;
1180         }
1181
1182         if (!(d->name = strdup(name))) {
1183                 errno = ENOMEM;
1184                 goto fail;
1185         }
1186
1187         if ((d->fd = open(name, O_RDWR|O_NOCTTY)) < 0) {
1188                 ret = d->fd;
1189                 goto fail;
1190         }
1191
1192         if ((ret = fstat(d->fd, &st)) < 0)
1193                 goto fail;
1194
1195         if (!S_ISBLK(st.st_mode)) {
1196                 errno = ENODEV;
1197                 ret = -1;
1198                 goto fail;
1199         }
1200
1201         /* So, it's a block device. Let's make sure the ioctls work */
1202
1203         if ((ret = ioctl(d->fd, BLKGETSIZE64, &d->size)) < 0)
1204                 goto fail;
1205
1206         if (d->size <= 0 || d->size == (uint64_t) -1) {
1207                 errno = EIO;
1208                 ret = -1;
1209                 goto fail;
1210         }
1211
1212         /* OK, it's a real block device with a size. Find a way to
1213          * identify the device. */
1214         for (d->type = 0; d->type != SK_DISK_TYPE_UNKNOWN; d->type++)
1215                 if (disk_identify_device(d) >= 0)
1216                         break;
1217
1218         /* Check if driver can do SMART, and enable if necessary */
1219         if (disk_smart_is_available(d)) {
1220
1221                 if (!disk_smart_is_enabled(d)) {
1222                         if ((ret = disk_smart_enable(d, TRUE)) < 0)
1223                                 goto fail;
1224
1225                         if ((ret = disk_identify_device(d)) < 0)
1226                                 goto fail;
1227
1228                         if (!disk_smart_is_enabled(d)) {
1229                                 errno = EIO;
1230                                 ret = -1;
1231                                 goto fail;
1232                         }
1233                 }
1234
1235                 disk_smart_read_thresholds(d);
1236         }
1237
1238         *_d = d;
1239
1240         return 0;
1241
1242 fail:
1243
1244         if (d)
1245                 sk_disk_free(d);
1246
1247         return ret;
1248 }
1249
1250 void sk_disk_free(SkDisk *d) {
1251         assert(d);
1252
1253         if (d->fd >= 0)
1254                 close(d->fd);
1255
1256         free(d->name);
1257         free(d);
1258 }