+#define COMMAND_TIMEOUT_MSEC (30 * 1000)
+
+static int disk_scsi_inquiry_command(int fd,
+ void *buf,
+ size_t buf_len)
+{
+ struct sg_io_v4 io_v4;
+ uint8_t cdb[6];
+ 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;
+ }
+
+ out:
+ return ret;
+}
+
+static int disk_identify_command(int fd,
+ void *buf,
+ size_t buf_len)
+{
+ 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
+ *
+ * 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 */
+ 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;
+ }
+ }
+
+ 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));
+
+ 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;
+ ret = -1;
+ goto out;
+ }
+
+ out:
+ return ret;
+}
+
+/**
+ * disk_identify_get_string:
+ * @identify: A block of IDENTIFY data
+ * @offset_words: Offset of the string to get, in words.
+ * @dest: Destination buffer for the string.
+ * @dest_len: Length of destination buffer, in bytes.
+ *
+ * Copies the ATA string from @identify located at @offset_words into @dest.
+ */
+static void disk_identify_get_string (uint8_t identify[512],
+ unsigned int offset_words,
+ char *dest,
+ size_t dest_len)
+{
+ unsigned int c1;
+ unsigned int c2;
+
+ assert (identify != NULL);
+ assert (dest != NULL);
+ assert ((dest_len & 1) == 0);
+
+ while (dest_len > 0) {
+ c1 = ((uint16_t *) identify)[offset_words] >> 8;
+ c2 = ((uint16_t *) identify)[offset_words] & 0xff;
+ *dest = c1;
+ dest++;
+ *dest = c2;
+ dest++;
+ offset_words++;
+ dest_len -= 2;
+ }
+}
+
+static void disk_identify_fixup_string (uint8_t identify[512],
+ unsigned int offset_words,
+ size_t len)
+{
+ disk_identify_get_string(identify, offset_words,
+ (char *) identify + offset_words * 2, len);
+}
+
+static void disk_identify_fixup_uint16 (uint8_t identify[512], unsigned int offset_words)
+{
+ uint16_t *p;
+
+ p = (uint16_t *) identify;
+ p[offset_words] = le16toh (p[offset_words]);
+}
+
+/**
+ * disk_identify:
+ * @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 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 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 *out_is_packet_device)
+{
+ int ret;
+ 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);
+ 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.)
+ */
+ 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;
+ errno = EIO;
+ goto out;
+ }
+
+ /* OK, now issue the IDENTIFY DEVICE command */
+ ret = disk_identify_command(fd, out_identify, 512);
+ if (ret != 0)
+ 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 (all_nul_bytes) {
+ ret = -1;
+ errno = EIO;
+ goto out;
+ }
+
+out:
+ if (out_is_packet_device != NULL)
+ *out_is_packet_device = is_packet_device;
+ return ret;
+}
+