chiark / gitweb /
cbd3b5b8d2a58517bef3c8c85921cbd6568fa5f6
[elogind.git] / extras / ata_id / ata_id.c
1 /*
2  * ata_id - reads product/serial number from ATA drives
3  *
4  * Copyright (C) 2005-2008 Kay Sievers <kay.sievers@vrfy.org>
5  * Copyright (C) 2009 Lennart Poettering <lennart@poettering.net>
6  * Copyright (C) 2009 David Zeuthen <zeuthen@gmail.com>
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdint.h>
25 #include <unistd.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <assert.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <getopt.h>
32 #include <scsi/scsi.h>
33 #include <scsi/sg.h>
34 #include <scsi/scsi_ioctl.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <linux/types.h>
39 #include <linux/hdreg.h>
40 #include <linux/fs.h>
41 #include <linux/cdrom.h>
42 #include <arpa/inet.h>
43
44 #include "libudev.h"
45 #include "libudev-private.h"
46
47 #define COMMAND_TIMEOUT 2000
48
49 /* Sends a SCSI command block */
50 static int sg_io(int fd, int direction,
51                  const void *cdb, size_t cdb_len,
52                  void *data, size_t data_len,
53                  void *sense, size_t sense_len)
54 {
55
56         struct sg_io_hdr io_hdr;
57
58         memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
59         io_hdr.interface_id = 'S';
60         io_hdr.cmdp = (unsigned char*) cdb;
61         io_hdr.cmd_len = cdb_len;
62         io_hdr.dxferp = data;
63         io_hdr.dxfer_len = data_len;
64         io_hdr.sbp = sense;
65         io_hdr.mx_sb_len = sense_len;
66         io_hdr.dxfer_direction = direction;
67         io_hdr.timeout = COMMAND_TIMEOUT;
68         return ioctl(fd, SG_IO, &io_hdr);
69 }
70
71 static int disk_command(int fd, int command, int direction, void *cmd_data,
72                         void *data, size_t *len)
73 {
74         uint8_t *bytes = cmd_data;
75         uint8_t cdb[12];
76         uint8_t sense[32];
77         uint8_t *desc = sense+8;
78         int ret;
79
80         /*
81          * ATA Pass-Through 12 byte command, as described in "T10 04-262r8
82          * ATA Command Pass-Through":
83          * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf
84          */
85         memset(cdb, 0, sizeof(cdb));
86         cdb[0] = 0xa1; /* OPERATION CODE: 12 byte pass through */
87         if (direction == SG_DXFER_NONE) {
88                 cdb[1] = 3 << 1;        /* PROTOCOL: Non-Data */
89                 cdb[2] = 0x20;          /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */
90         } else if (direction == SG_DXFER_FROM_DEV) {
91                 cdb[1] = 4 << 1;        /* PROTOCOL: PIO Data-in */
92                 cdb[2] = 0x2e;          /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */
93         } else if (direction == SG_DXFER_TO_DEV) {
94                 cdb[1] = 5 << 1;        /* PROTOCOL: PIO Data-Out */
95                 cdb[2] = 0x26;          /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */
96         }
97         cdb[3] = bytes[1];              /* FEATURES */
98         cdb[4] = bytes[3];              /* SECTORS */
99         cdb[5] = bytes[9];              /* LBA LOW */
100         cdb[6] = bytes[8];              /* LBA MID */
101         cdb[7] = bytes[7];              /* LBA HIGH */
102         cdb[8] = bytes[10] & 0x4F;      /* SELECT */
103         cdb[9] = (uint8_t) command;
104         memset(sense, 0, sizeof(sense));
105         if ((ret = sg_io(fd, direction, cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0)
106                 return ret;
107         if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
108                 errno = EIO;
109                 return -1;
110         }
111
112         memset(bytes, 0, 12);
113         bytes[1] = desc[3]; /* FEATURES */
114         bytes[2] = desc[4]; /* STATUS */
115         bytes[3] = desc[5]; /* SECTORS */
116         bytes[9] = desc[7]; /* LBA LOW */
117         bytes[8] = desc[9]; /* LBA MID */
118         bytes[7] = desc[11]; /* LBA HIGH */
119         bytes[10] = desc[12]; /* SELECT */
120         bytes[11] = desc[13]; /* ERROR */
121         return ret;
122 }
123
124 /**
125  * disk_identify_get_string:
126  * @identify: A block of IDENTIFY data
127  * @offset_words: Offset of the string to get, in words.
128  * @dest: Destination buffer for the string.
129  * @dest_len: Length of destination buffer, in bytes.
130  *
131  * Copies the ATA string from @identify located at @offset_words into @dest.
132  */
133 static void disk_identify_get_string (uint8_t identify[512],
134                                       unsigned int offset_words,
135                                       char *dest,
136                                       size_t dest_len)
137 {
138         unsigned int c1;
139         unsigned int c2;
140
141         assert (identify != NULL);
142         assert (dest != NULL);
143         assert ((dest_len & 1) == 0);
144
145         while (dest_len > 0) {
146                 c1 = ((uint16_t *) identify)[offset_words] >> 8;
147                 c2 = ((uint16_t *) identify)[offset_words] & 0xff;
148                 *dest = c1;
149                 dest++;
150                 *dest = c2;
151                 dest++;
152                 offset_words++;
153                 dest_len -= 2;
154         }
155 }
156
157 static void disk_identify_fixup_string (uint8_t identify[512],
158                                         unsigned int offset_words,
159                                         size_t len)
160 {
161         disk_identify_get_string(identify, offset_words,
162                                  (char *) identify + offset_words * 2, len);
163 }
164
165 static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned int offset_words)
166 {
167         uint16_t *p;
168
169         p = (uint16_t *) identify;
170         p[offset_words] = le16toh (p[offset_words]);
171 }
172
173 /**
174  * disk_identify:
175  * @udev: The libudev context.
176  * @fd: File descriptor for the block device.
177  * @out_identify: Return location for IDENTIFY data.
178  *
179  * Sends the IDENTIFY DEVICE command to the device represented by
180  * @fd. If successful, then the result will be copied into
181  * @out_identify.
182  *
183  * This routine is based on code from libatasmart, Copyright 2008
184  * Lennart Poettering, LGPL v2.1.
185  *
186  * Returns: 0 if the IDENTIFY data was successfully obtained,
187  * otherwise non-zero with errno set.
188  */
189 static int disk_identify (struct udev *udev,
190                           int fd,
191                           uint8_t out_identify[512])
192 {
193         int ret;
194         uint64_t size;
195         struct stat st;
196         uint16_t cmd[6];
197         size_t len = 512;
198         const uint8_t *p;
199
200         assert (out_identify != NULL);
201
202         /* init results */
203         ret = -1;
204         memset (out_identify, '\0', 512);
205
206         if ((ret = fstat(fd, &st)) < 0)
207                 goto fail;
208
209         if (!S_ISBLK(st.st_mode)) {
210                 errno = ENODEV;
211                 goto fail;
212         }
213
214         /*
215          * do not confuse optical drive firmware with ATA commands
216          * some drives are reported to blank CD-RWs
217          */
218         if (ioctl(fd, CDROM_GET_CAPABILITY, NULL) >= 0) {
219                 errno = EIO;
220                 ret = -1;
221                 goto fail;
222         }
223
224         /* So, it's a block device. Let's make sure the ioctls work */
225         if ((ret = ioctl(fd, BLKGETSIZE64, &size)) < 0)
226                 goto fail;
227
228         if (size <= 0 || size == (uint64_t) -1) {
229                 errno = EIO;
230                 goto fail;
231         }
232
233         memset(cmd, 0, sizeof(cmd));
234         cmd[1] = htons(1);
235         ret = disk_command(fd,
236                            0xEC, /* IDENTIFY DEVICE command */
237                            SG_DXFER_FROM_DEV, cmd,
238                            out_identify, &len);
239         if (ret != 0)
240                 goto fail;
241
242         if (len != 512) {
243                 errno = EIO;
244                 goto fail;
245         }
246
247          /* Check if IDENTIFY data is all NULs */
248         for (p = out_identify; p < (const uint8_t*) out_identify + len; p++) {
249                 if (*p) {
250                         p = NULL;
251                         break;
252                 }
253         }
254
255         if (p) {
256                 errno = EIO;
257                 goto fail;
258         }
259
260         ret = 0;
261 fail:
262         return ret;
263 }
264
265 static void log_fn(struct udev *udev, int priority,
266                    const char *file, int line, const char *fn,
267                    const char *format, va_list args)
268 {
269         vsyslog(priority, format, args);
270 }
271
272 int main(int argc, char *argv[])
273 {
274         struct udev *udev;
275         struct hd_driveid id;
276          uint8_t identify[512];
277         char model[41];
278         char model_enc[256];
279         char serial[21];
280         char revision[9];
281         const char *node = NULL;
282         int export = 0;
283         int fd;
284          uint16_t word;
285         int rc = 0;
286         static const struct option options[] = {
287                 { "export", no_argument, NULL, 'x' },
288                 { "help", no_argument, NULL, 'h' },
289                 {}
290         };
291
292         udev = udev_new();
293         if (udev == NULL)
294                 goto exit;
295
296         udev_log_init("ata_id");
297         udev_set_log_fn(udev, log_fn);
298
299         while (1) {
300                 int option;
301
302                 option = getopt_long(argc, argv, "xh", options, NULL);
303                 if (option == -1)
304                         break;
305
306                 switch (option) {
307                 case 'x':
308                         export = 1;
309                         break;
310                 case 'h':
311                         printf("Usage: ata_id [--export] [--help] <device>\n"
312                                "  --export    print values as environment keys\n"
313                                "  --help      print this help text\n\n");
314                 default:
315                         rc = 1;
316                         goto exit;
317                 }
318         }
319
320         node = argv[optind];
321         if (node == NULL) {
322                 err(udev, "no node specified\n");
323                 rc = 1;
324                 goto exit;
325         }
326
327         fd = open(node, O_RDONLY|O_NONBLOCK);
328         if (fd < 0) {
329                 err(udev, "unable to open '%s'\n", node);
330                 rc = 1;
331                 goto exit;
332         }
333
334         if (disk_identify(udev, fd, identify) == 0) {
335                 /*
336                  * fix up only the fields from the IDENTIFY data that we are going to
337                  * use and copy it into the hd_driveid struct for convenience
338                  */
339                 disk_identify_fixup_string (identify,  10, 20); /* serial */
340                 disk_identify_fixup_string (identify,  23,  6); /* fwrev */
341                 disk_identify_fixup_string (identify,  27, 40); /* model */
342                 disk_identify_fixup_uint16 (identify,   0);     /* configuration */
343                 disk_identify_fixup_uint16 (identify,  75);     /* queue depth */
344                 disk_identify_fixup_uint16 (identify,  75);     /* SATA capabilities */
345                 disk_identify_fixup_uint16 (identify,  82);     /* command set supported */
346                 disk_identify_fixup_uint16 (identify,  83);     /* command set supported */
347                 disk_identify_fixup_uint16 (identify,  84);     /* command set supported */
348                 disk_identify_fixup_uint16 (identify,  85);     /* command set supported */
349                 disk_identify_fixup_uint16 (identify,  86);     /* command set supported */
350                 disk_identify_fixup_uint16 (identify,  87);     /* command set supported */
351                 disk_identify_fixup_uint16 (identify,  89);     /* time required for SECURITY ERASE UNIT */
352                 disk_identify_fixup_uint16 (identify,  90);     /* time required for enhanced SECURITY ERASE UNIT */
353                 disk_identify_fixup_uint16 (identify,  91);     /* current APM values */
354                 disk_identify_fixup_uint16 (identify,  94);     /* current AAM value */
355                 disk_identify_fixup_uint16 (identify, 128);     /* device lock function */
356                 disk_identify_fixup_uint16 (identify, 217);     /* nominal media rotation rate */
357                 memcpy(&id, identify, sizeof id);
358         } else {
359                 /* If this fails, then try HDIO_GET_IDENTITY */
360                 if (ioctl(fd, HDIO_GET_IDENTITY, &id) != 0) {
361                         if (errno == ENOTTY) {
362                                 info(udev, "HDIO_GET_IDENTITY unsupported for '%s'\n", node);
363                                 rc = 2;
364                         } else {
365                                 err(udev, "HDIO_GET_IDENTITY failed for '%s'\n", node);
366                                 rc = 3;
367                         }
368                         goto close;
369                 }
370         }
371
372         memcpy (model, id.model, 40);
373         model[40] = '\0';
374         udev_util_encode_string(model, model_enc, sizeof(model_enc));
375         udev_util_replace_whitespace((char *) id.model, model, 40);
376         udev_util_replace_chars(model, NULL);
377         udev_util_replace_whitespace((char *) id.serial_no, serial, 20);
378         udev_util_replace_chars(serial, NULL);
379         udev_util_replace_whitespace((char *) id.fw_rev, revision, 8);
380         udev_util_replace_chars(revision, NULL);
381
382         if (export) {
383                   /* Set this to convey the disk speaks the ATA protocol */
384                   printf("ID_ATA=1\n");
385
386                 if ((id.config >> 8) & 0x80) {
387                         /* This is an ATAPI device */
388                         switch ((id.config >> 8) & 0x1f) {
389                         case 0:
390                                 printf("ID_TYPE=cd\n");
391                                 break;
392                         case 1:
393                                 printf("ID_TYPE=tape\n");
394                                 break;
395                         case 5:
396                                 printf("ID_TYPE=cd\n");
397                                 break;
398                         case 7:
399                                 printf("ID_TYPE=optical\n");
400                                 break;
401                         default:
402                                 printf("ID_TYPE=generic\n");
403                                 break;
404                         }
405                 } else {
406                         printf("ID_TYPE=disk\n");
407                 }
408                 printf("ID_BUS=ata\n");
409                 printf("ID_MODEL=%s\n", model);
410                 printf("ID_MODEL_ENC=%s\n", model_enc);
411                 printf("ID_REVISION=%s\n", revision);
412                 printf("ID_SERIAL=%s_%s\n", model, serial);
413                 printf("ID_SERIAL_SHORT=%s\n", serial);
414
415                 if (id.command_set_1 & (1<<5)) {
416                         printf ("ID_ATA_WRITE_CACHE=1\n");
417                         printf ("ID_ATA_WRITE_CACHE_ENABLED=%d\n", (id.cfs_enable_1 & (1<<5)) ? 1 : 0);
418                 }
419                 if (id.command_set_1 & (1<<10)) {
420                         printf("ID_ATA_FEATURE_SET_HPA=1\n");
421                         printf("ID_ATA_FEATURE_SET_HPA_ENABLED=%d\n", (id.cfs_enable_1 & (1<<10)) ? 1 : 0);
422
423                         /*
424                          * TODO: use the READ NATIVE MAX ADDRESS command to get the native max address
425                          * so it is easy to check whether the protected area is in use.
426                          */
427                 }
428                 if (id.command_set_1 & (1<<3)) {
429                         printf("ID_ATA_FEATURE_SET_PM=1\n");
430                         printf("ID_ATA_FEATURE_SET_PM_ENABLED=%d\n", (id.cfs_enable_1 & (1<<3)) ? 1 : 0);
431                 }
432                 if (id.command_set_1 & (1<<1)) {
433                         printf("ID_ATA_FEATURE_SET_SECURITY=1\n");
434                         printf("ID_ATA_FEATURE_SET_SECURITY_ENABLED=%d\n", (id.cfs_enable_1 & (1<<1)) ? 1 : 0);
435                         printf("ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=%d\n", id.trseuc * 2);
436                         if ((id.cfs_enable_1 & (1<<1))) /* enabled */ {
437                                 if (id.dlf & (1<<8))
438                                         printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=maximum\n");
439                                 else
440                                         printf("ID_ATA_FEATURE_SET_SECURITY_LEVEL=high\n");
441                         }
442                         if (id.dlf & (1<<5))
443                                 printf("ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=%d\n", id.trsEuc * 2);
444                         if (id.dlf & (1<<4))
445                                 printf("ID_ATA_FEATURE_SET_SECURITY_EXPIRE=1\n");
446                         if (id.dlf & (1<<3))
447                                 printf("ID_ATA_FEATURE_SET_SECURITY_FROZEN=1\n");
448                         if (id.dlf & (1<<2))
449                                 printf("ID_ATA_FEATURE_SET_SECURITY_LOCKED=1\n");
450                 }
451                 if (id.command_set_1 & (1<<0)) {
452                         printf("ID_ATA_FEATURE_SET_SMART=1\n");
453                         printf("ID_ATA_FEATURE_SET_SMART_ENABLED=%d\n", (id.cfs_enable_1 & (1<<0)) ? 1 : 0);
454                 }
455                 if (id.command_set_2 & (1<<9)) {
456                         printf("ID_ATA_FEATURE_SET_AAM=1\n");
457                         printf("ID_ATA_FEATURE_SET_AAM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<9)) ? 1 : 0);
458                         printf("ID_ATA_FEATURE_SET_AAM_VENDOR_RECOMMENDED_VALUE=%d\n", id.acoustic >> 8);
459                         printf("ID_ATA_FEATURE_SET_AAM_CURRENT_VALUE=%d\n", id.acoustic & 0xff);
460                 }
461                 if (id.command_set_2 & (1<<5)) {
462                         printf("ID_ATA_FEATURE_SET_PUIS=1\n");
463                         printf("ID_ATA_FEATURE_SET_PUIS_ENABLED=%d\n", (id.cfs_enable_2 & (1<<5)) ? 1 : 0);
464                 }
465                 if (id.command_set_2 & (1<<3)) {
466                         printf("ID_ATA_FEATURE_SET_APM=1\n");
467                         printf("ID_ATA_FEATURE_SET_APM_ENABLED=%d\n", (id.cfs_enable_2 & (1<<3)) ? 1 : 0);
468                         if ((id.cfs_enable_2 & (1<<3)))
469                                 printf("ID_ATA_FEATURE_SET_APM_CURRENT_VALUE=%d\n", id.CurAPMvalues & 0xff);
470                 }
471                 if (id.command_set_2 & (1<<0))
472                         printf("ID_ATA_DOWNLOAD_MICROCODE=1\n");
473
474                 /*
475                  * Word 76 indicates the capabilities of a SATA device. A PATA device shall set
476                  * word 76 to 0000h or FFFFh. If word 76 is set to 0000h or FFFFh, then
477                  * the device does not claim compliance with the Serial ATA specification and words
478                  * 76 through 79 are not valid and shall be ignored.
479                  */
480                 word = *((uint16_t *) identify + 76);
481                 if (word != 0x0000 && word != 0xffff) {
482                         printf("ID_ATA_SATA=1\n");
483                         /*
484                          * If bit 2 of word 76 is set to one, then the device supports the Gen2
485                          * signaling rate of 3.0 Gb/s (see SATA 2.6).
486                          *
487                          * If bit 1 of word 76 is set to one, then the device supports the Gen1
488                          * signaling rate of 1.5 Gb/s (see SATA 2.6).
489                          */
490                         if (word & (1<<2))
491                                 printf("ID_ATA_SATA_SIGNAL_RATE_GEN2=1\n");
492                         if (word & (1<<1))
493                                 printf("ID_ATA_SATA_SIGNAL_RATE_GEN1=1\n");
494                 }
495
496                 /* Word 217 indicates the nominal media rotation rate of the device */
497                 word = *((uint16_t *) identify + 217);
498                 if (word != 0x0000) {
499                         if (word == 0x0001) {
500                                 printf ("ID_ATA_ROTATION_RATE_RPM=0\n"); /* non-rotating e.g. SSD */
501                         } else if (word >= 0x0401 && word <= 0xfffe) {
502                                 printf ("ID_ATA_ROTATION_RATE_RPM=%d\n", word);
503                         }
504                 }
505
506                 /*
507                  * Words 108-111 contain a mandatory World Wide Name (WWN) in the NAA IEEE Registered identifier
508                  * format. Word 108 bits (15:12) shall contain 5h, indicating that the naming authority is IEEE.
509                  * All other values are reserved.
510                  */
511                 word = *((uint16_t *) identify + 108);
512                 if ((word & 0xf000) == 0x5000) {
513                         uint64_t wwwn;
514
515                         wwwn   = *((uint16_t *) identify + 108);
516                         wwwn <<= 16;
517                         wwwn  |= *((uint16_t *) identify + 109);
518                         wwwn <<= 16;
519                         wwwn  |= *((uint16_t *) identify + 110);
520                         wwwn <<= 16;
521                         wwwn  |= *((uint16_t *) identify + 111);
522                         printf("ID_WWN=0x%llx\n", (unsigned long long int) wwwn);
523                 }
524         } else {
525                 if (serial[0] != '\0')
526                         printf("%s_%s\n", model, serial);
527                 else
528                         printf("%s\n", model);
529         }
530 close:
531         close(fd);
532 exit:
533         udev_unref(udev);
534         udev_log_close();
535         return rc;
536 }