+ uint8_t *bytes = cmd_data;
+ 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
+ */
+ 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[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;
+ 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) {
+ errno = EIO;
+ return -1;
+ }
+
+ 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 */
+ 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.
+ *
+ * Sends the IDENTIFY DEVICE command to the device represented by
+ * @fd. If successful, then the result will be copied into
+ * @out_identify.
+ *
+ * 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.
+ */
+static int disk_identify (struct udev *udev,
+ int fd,
+ uint8_t out_identify[512])
+{
+ int ret;
+ uint64_t size;
+ struct stat st;
+ uint16_t cmd[6];
+ size_t len = 512;
+ const uint8_t *p;
+
+ 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
+ */
+ if (ioctl(fd, CDROM_GET_CAPABILITY, NULL) >= 0) {
+ errno = EIO;
+ 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;
+ }
+
+ 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);
+ 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;
+ break;