X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=extras%2Fata_id%2Fata_id.c;h=cb4a1a1f075b4a0587f7e953ba29e57ee2fde401;hb=b466e9ab3951207a3c0c8d2ba6167be1eac1e41a;hp=25c771012710faa3fe0a5262b8013b8e55d3d1ba;hpb=7f4954d04026f28b0ebe65a514f06fefad8efaca;p=elogind.git diff --git a/extras/ata_id/ata_id.c b/extras/ata_id/ata_id.c index 25c771012..cb4a1a1f0 100644 --- a/extras/ata_id/ata_id.c +++ b/extras/ata_id/ata_id.c @@ -1,10 +1,9 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- - * +/* * ata_id - reads product/serial number from ATA drives * * Copyright (C) 2005-2008 Kay Sievers * Copyright (C) 2009 Lennart Poettering - * Copyright (C) 2009 David Zeuthen + * Copyright (C) 2009-2010 David Zeuthen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,85 +39,249 @@ #include #include #include +#include #include #include "libudev.h" #include "libudev-private.h" -#define COMMAND_TIMEOUT 2000 +#define COMMAND_TIMEOUT_MSEC (30 * 1000) -/* Sends a SCSI command block */ -static int sg_io(int fd, int direction, - const void *cdb, size_t cdb_len, - void *data, size_t data_len, - void *sense, size_t sense_len) +static int disk_scsi_inquiry_command(int fd, + void *buf, + size_t buf_len) { + struct sg_io_v4 io_v4; + uint8_t cdb[12]; + uint8_t sense[32]; + int ret; + + /* + * INQUIRY, see SPC-4 section 6.4 + */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x12; /* OPERATION CODE: INQUIRY */ + cdb[3] = (buf_len >> 8); /* ALLOCATION LENGTH */ + cdb[4] = (buf_len & 0xff); + + memset(sense, 0, sizeof(sense)); + + memset(&io_v4, 0, sizeof(struct sg_io_v4)); + io_v4.guard = 'Q'; + io_v4.protocol = BSG_PROTOCOL_SCSI; + io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + io_v4.request_len = sizeof (cdb); + io_v4.request = (uintptr_t) cdb; + io_v4.max_response_len = sizeof (sense); + io_v4.response = (uintptr_t) sense; + io_v4.din_xfer_len = buf_len; + io_v4.din_xferp = (uintptr_t) buf; + io_v4.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_v4); + if (ret != 0) { + /* could be that the driver doesn't do version 4, try version 3 */ + if (errno == EINVAL) { + struct sg_io_hdr io_hdr; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = sizeof (cdb); + io_hdr.dxferp = buf; + io_hdr.dxfer_len = buf_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sizeof (sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + goto out; + + /* even if the ioctl succeeds, we need to check the return value */ + if (!(io_hdr.status == 0 && + io_hdr.host_status == 0 && + io_hdr.driver_status == 0)) { + errno = EIO; + ret = -1; + goto out; + } + } else { + goto out; + } + } + + /* even if the ioctl succeeds, we need to check the return value */ + if (!(io_v4.device_status == 0 && + io_v4.transport_status == 0 && + io_v4.driver_status == 0)) { + errno = EIO; + ret = -1; + goto out; + } - struct sg_io_hdr io_hdr; - - memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); - io_hdr.interface_id = 'S'; - io_hdr.cmdp = (unsigned char*) cdb; - io_hdr.cmd_len = cdb_len; - io_hdr.dxferp = data; - io_hdr.dxfer_len = data_len; - io_hdr.sbp = sense; - io_hdr.mx_sb_len = sense_len; - io_hdr.dxfer_direction = direction; - io_hdr.timeout = COMMAND_TIMEOUT; - return ioctl(fd, SG_IO, &io_hdr); + out: + return ret; } -static int disk_command(int fd, int command, int direction, void *cmd_data, - void *data, size_t *len) +static int disk_identify_command(int fd, + void *buf, + size_t buf_len) { - uint8_t *bytes = cmd_data; + struct sg_io_v4 io_v4; uint8_t cdb[12]; uint8_t sense[32]; uint8_t *desc = sense+8; int ret; /* - * ATA Pass-Through 12 byte command, as described in "T10 04-262r8 - * ATA Command Pass-Through": - * http://www.t10.org/ftp/t10/document.04/04-262r8.pdf + * ATA Pass-Through 12 byte command, as described in + * + * T10 04-262r8 ATA Command Pass-Through + * + * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf */ memset(cdb, 0, sizeof(cdb)); - cdb[0] = 0xa1; /* OPERATION CODE: 12 byte pass through */ - if (direction == SG_DXFER_NONE) { - cdb[1] = 3 << 1; /* PROTOCOL: Non-Data */ - cdb[2] = 0x20; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=0, T_LENGTH=0 */ - } else if (direction == SG_DXFER_FROM_DEV) { - cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ - cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ - } else if (direction == SG_DXFER_TO_DEV) { - cdb[1] = 5 << 1; /* PROTOCOL: PIO Data-Out */ - cdb[2] = 0x26; /* OFF_LINE=0, CK_COND=1, T_DIR=0, BYT_BLOK=1, T_LENGTH=2 */ + cdb[0] = 0xa1; /* OPERATION CODE: 12 byte pass through */ + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + cdb[3] = 0; /* FEATURES */ + cdb[4] = 1; /* SECTORS */ + cdb[5] = 0; /* LBA LOW */ + cdb[6] = 0; /* LBA MID */ + cdb[7] = 0; /* LBA HIGH */ + cdb[8] = 0 & 0x4F; /* SELECT */ + cdb[9] = 0xEC; /* Command: ATA IDENTIFY DEVICE */; + memset(sense, 0, sizeof(sense)); + + memset(&io_v4, 0, sizeof(struct sg_io_v4)); + io_v4.guard = 'Q'; + io_v4.protocol = BSG_PROTOCOL_SCSI; + io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + io_v4.request_len = sizeof (cdb); + io_v4.request = (uintptr_t) cdb; + io_v4.max_response_len = sizeof (sense); + io_v4.response = (uintptr_t) sense; + io_v4.din_xfer_len = buf_len; + io_v4.din_xferp = (uintptr_t) buf; + io_v4.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_v4); + if (ret != 0) { + /* could be that the driver doesn't do version 4, try version 3 */ + if (errno == EINVAL) { + struct sg_io_hdr io_hdr; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = sizeof (cdb); + io_hdr.dxferp = buf; + io_hdr.dxfer_len = buf_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sizeof (sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + goto out; + } else { + goto out; + } } - cdb[3] = bytes[1]; /* FEATURES */ - cdb[4] = bytes[3]; /* SECTORS */ - cdb[5] = bytes[9]; /* LBA LOW */ - cdb[6] = bytes[8]; /* LBA MID */ - cdb[7] = bytes[7]; /* LBA HIGH */ - cdb[8] = bytes[10] & 0x4F; /* SELECT */ - cdb[9] = (uint8_t) command; + + if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) { + errno = EIO; + ret = -1; + goto out; + } + + out: + return ret; +} + +static int disk_identify_packet_device_command(int fd, + void *buf, + size_t buf_len) +{ + struct sg_io_v4 io_v4; + uint8_t cdb[16]; + uint8_t sense[32]; + uint8_t *desc = sense+8; + int ret; + + /* + * ATA Pass-Through 16 byte command, as described in + * + * T10 04-262r8 ATA Command Pass-Through + * + * from http://www.t10.org/ftp/t10/document.04/04-262r8.pdf + */ + memset(cdb, 0, sizeof(cdb)); + cdb[0] = 0x85; /* OPERATION CODE: 16 byte pass through */ + cdb[1] = 4 << 1; /* PROTOCOL: PIO Data-in */ + cdb[2] = 0x2e; /* OFF_LINE=0, CK_COND=1, T_DIR=1, BYT_BLOK=1, T_LENGTH=2 */ + cdb[3] = 0; /* FEATURES */ + cdb[4] = 0; /* FEATURES */ + cdb[5] = 0; /* SECTORS */ + cdb[6] = 1; /* SECTORS */ + cdb[7] = 0; /* LBA LOW */ + cdb[8] = 0; /* LBA LOW */ + cdb[9] = 0; /* LBA MID */ + cdb[10] = 0; /* LBA MID */ + cdb[11] = 0; /* LBA HIGH */ + cdb[12] = 0; /* LBA HIGH */ + cdb[13] = 0; /* DEVICE */ + cdb[14] = 0xA1; /* Command: ATA IDENTIFY PACKET DEVICE */; + cdb[15] = 0; /* CONTROL */ memset(sense, 0, sizeof(sense)); - if ((ret = sg_io(fd, direction, cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0) - return ret; - if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) { + + memset(&io_v4, 0, sizeof(struct sg_io_v4)); + io_v4.guard = 'Q'; + io_v4.protocol = BSG_PROTOCOL_SCSI; + io_v4.subprotocol = BSG_SUB_PROTOCOL_SCSI_CMD; + io_v4.request_len = sizeof (cdb); + io_v4.request = (uintptr_t) cdb; + io_v4.max_response_len = sizeof (sense); + io_v4.response = (uintptr_t) sense; + io_v4.din_xfer_len = buf_len; + io_v4.din_xferp = (uintptr_t) buf; + io_v4.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_v4); + if (ret != 0) { + /* could be that the driver doesn't do version 4, try version 3 */ + if (errno == EINVAL) { + struct sg_io_hdr io_hdr; + + memset(&io_hdr, 0, sizeof(struct sg_io_hdr)); + io_hdr.interface_id = 'S'; + io_hdr.cmdp = (unsigned char*) cdb; + io_hdr.cmd_len = sizeof (cdb); + io_hdr.dxferp = buf; + io_hdr.dxfer_len = buf_len; + io_hdr.sbp = sense; + io_hdr.mx_sb_len = sizeof (sense); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.timeout = COMMAND_TIMEOUT_MSEC; + + ret = ioctl(fd, SG_IO, &io_hdr); + if (ret != 0) + goto out; + } else { + goto out; + } + } + + if (!(sense[0] == 0x72 && desc[0] == 0x9 && desc[1] == 0x0c)) { errno = EIO; - return -1; + ret = -1; + goto out; } - memset(bytes, 0, 12); - bytes[1] = desc[3]; /* FEATURES */ - bytes[2] = desc[4]; /* STATUS */ - bytes[3] = desc[5]; /* SECTORS */ - bytes[9] = desc[7]; /* LBA LOW */ - bytes[8] = desc[9]; /* LBA MID */ - bytes[7] = desc[11]; /* LBA HIGH */ - bytes[10] = desc[12]; /* SELECT */ - bytes[11] = desc[13]; /* ERROR */ + out: return ret; } @@ -176,90 +339,100 @@ static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned int offs * @udev: The libudev context. * @fd: File descriptor for the block device. * @out_identify: Return location for IDENTIFY data. + * @out_is_packet_device: Return location for whether returned data is from a IDENTIFY PACKET DEVICE. * - * Sends the IDENTIFY DEVICE command to the device represented by - * @fd. If successful, then the result will be copied into - * @out_identify. + * Sends the IDENTIFY DEVICE or IDENTIFY PACKET DEVICE command to the + * device represented by @fd. If successful, then the result will be + * copied into @out_identify and @out_is_packet_device. * * This routine is based on code from libatasmart, Copyright 2008 * Lennart Poettering, LGPL v2.1. * - * Returns: 0 if the IDENTIFY data was successfully obtained, - * otherwise non-zero with errno set. + * Returns: 0 if the data was successfully obtained, otherwise + * non-zero with errno set. */ static int disk_identify (struct udev *udev, - int fd, - uint8_t out_identify[512]) + int fd, + uint8_t out_identify[512], + int *out_is_packet_device) { int ret; - uint64_t size; - struct stat st; - uint16_t cmd[6]; - size_t len = 512; - const uint8_t *p; + uint8_t inquiry_buf[36]; + int peripheral_device_type; + int all_nul_bytes; + int n; + int is_packet_device; assert (out_identify != NULL); /* init results */ ret = -1; memset (out_identify, '\0', 512); - - if ((ret = fstat(fd, &st)) < 0) - goto fail; - - if (!S_ISBLK(st.st_mode)) { - errno = ENODEV; - goto fail; - } - - /* - * do not confuse optical drive firmware with ATA commands - * some drives are reported to blank CD-RWs + is_packet_device = 0; + + /* If we were to use ATA PASS_THROUGH (12) on an ATAPI device + * we could accidentally blank media. This is because MMC's BLANK + * command has the same op-code (0x61). + * + * To prevent this from happening we bail out if the device + * isn't a Direct Access Block Device, e.g. SCSI type 0x00 + * (CD/DVD devices are type 0x05). So we send a SCSI INQUIRY + * command first... libata is handling this via its SCSI + * emulation layer. + * + * This also ensures that we're actually dealing with a device + * that understands SCSI commands. + * + * (Yes, it is a bit perverse that we're tunneling the ATA + * command through SCSI and relying on the ATA driver + * emulating SCSI well-enough...) + * + * (See commit 160b069c25690bfb0c785994c7c3710289179107 for + * the original bug-fix and see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=556635 + * for the original bug-report.) */ - if (ioctl(fd, CDROM_GET_CAPABILITY, NULL) >= 0) { - errno = EIO; + ret = disk_scsi_inquiry_command (fd, inquiry_buf, sizeof (inquiry_buf)); + if (ret != 0) + goto out; + + /* SPC-4, section 6.4.2: Standard INQUIRY data */ + peripheral_device_type = inquiry_buf[0] & 0x1f; + if (peripheral_device_type == 0x05) + { + is_packet_device = 1; + ret = disk_identify_packet_device_command(fd, out_identify, 512); + goto check_nul_bytes; + } + if (peripheral_device_type != 0x00) { ret = -1; - goto fail; - } - - /* So, it's a block device. Let's make sure the ioctls work */ - if ((ret = ioctl(fd, BLKGETSIZE64, &size)) < 0) - goto fail; - - if (size <= 0 || size == (uint64_t) -1) { errno = EIO; - goto fail; + goto out; } - memset(cmd, 0, sizeof(cmd)); - cmd[1] = htons(1); - ret = disk_command(fd, - 0xEC, /* IDENTIFY DEVICE command */ - SG_DXFER_FROM_DEV, cmd, - out_identify, &len); + /* OK, now issue the IDENTIFY DEVICE command */ + ret = disk_identify_command(fd, out_identify, 512); if (ret != 0) - goto fail; - - if (len != 512) { - errno = EIO; - goto fail; - } - - /* Check if IDENTIFY data is all NULs */ - for (p = out_identify; p < (const uint8_t*) out_identify + len; p++) { - if (*p) { - p = NULL; + goto out; + + check_nul_bytes: + /* Check if IDENTIFY data is all NUL bytes - if so, bail */ + all_nul_bytes = 1; + for (n = 0; n < 512; n++) { + if (out_identify[n] != '\0') { + all_nul_bytes = 0; break; } } - if (p) { + if (all_nul_bytes) { + ret = -1; errno = EIO; - goto fail; + goto out; } - ret = 0; -fail: +out: + if (out_is_packet_device != NULL) + *out_is_packet_device = is_packet_device; return ret; } @@ -274,7 +447,7 @@ int main(int argc, char *argv[]) { struct udev *udev; struct hd_driveid id; - uint8_t identify[512]; + uint8_t identify[512]; char model[41]; char model_enc[256]; char serial[21]; @@ -282,8 +455,9 @@ int main(int argc, char *argv[]) const char *node = NULL; int export = 0; int fd; - uint16_t word; + uint16_t word; int rc = 0; + int is_packet_device = 0; static const struct option options[] = { { "export", no_argument, NULL, 'x' }, { "help", no_argument, NULL, 'h' }, @@ -312,8 +486,6 @@ int main(int argc, char *argv[]) printf("Usage: ata_id [--export] [--help] \n" " --export print values as environment keys\n" " --help print this help text\n\n"); - default: - rc = 1; goto exit; } } @@ -332,7 +504,7 @@ int main(int argc, char *argv[]) goto exit; } - if (disk_identify(udev, fd, identify) == 0) { + if (disk_identify(udev, fd, identify, &is_packet_device) == 0) { /* * fix up only the fields from the IDENTIFY data that we are going to * use and copy it into the hd_driveid struct for convenience @@ -340,7 +512,7 @@ int main(int argc, char *argv[]) disk_identify_fixup_string (identify, 10, 20); /* serial */ disk_identify_fixup_string (identify, 23, 6); /* fwrev */ disk_identify_fixup_string (identify, 27, 40); /* model */ - disk_identify_fixup_uint16 (identify, 0); /* configuration */ + disk_identify_fixup_uint16 (identify, 0); /* configuration */ disk_identify_fixup_uint16 (identify, 75); /* queue depth */ disk_identify_fixup_uint16 (identify, 75); /* SATA capabilities */ disk_identify_fixup_uint16 (identify, 82); /* command set supported */ @@ -381,8 +553,8 @@ int main(int argc, char *argv[]) udev_util_replace_chars(revision, NULL); if (export) { - /* Set this to convey the disk speaks the ATA protocol */ - printf("ID_ATA=1\n"); + /* Set this to convey the disk speaks the ATA protocol */ + printf("ID_ATA=1\n"); if ((id.config >> 8) & 0x80) { /* This is an ATAPI device */ @@ -410,8 +582,12 @@ int main(int argc, char *argv[]) printf("ID_MODEL=%s\n", model); printf("ID_MODEL_ENC=%s\n", model_enc); printf("ID_REVISION=%s\n", revision); - printf("ID_SERIAL=%s_%s\n", model, serial); - printf("ID_SERIAL_SHORT=%s\n", serial); + if (serial[0] != '\0') { + printf("ID_SERIAL=%s_%s\n", model, serial); + printf("ID_SERIAL_SHORT=%s\n", serial); + } else { + printf("ID_SERIAL=%s\n", model); + } if (id.command_set_1 & (1<<5)) { printf ("ID_ATA_WRITE_CACHE=1\n");