martinpitt / umockdev

Mock hardware devices for creating unit tests and bug reporting
https://launchpad.net/umockdev
GNU Lesser General Public License v2.1
314 stars 58 forks source link

umockdev-record not capturing USB mass storage ioctl calls #251

Open Degoah opened 4 months ago

Degoah commented 4 months ago

Description:

I am trying to use umockdev-record to capture the behavior of my application that interacts with a USB mass storage device. My trivial application, named get_usb_stick_info, uses the ioctl system call with the SG_IO command to retrieve information from the device.

The application code is provided below:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>

#define INQUIRY_CMDLEN 6
#define INQUIRY_REPLY_LEN 96
#define SENSE_LEN 32

int main(int argc, char* argv[]) {
  const char *device = argv[1];   
  int fd = open(device, O_RDONLY);
  if (fd < 0) {
    perror("Failed to open device");
    return 1;
  }

  unsigned char inqCmdBlk[INQUIRY_CMDLEN] = {0x12, 0, 0, 0, INQUIRY_REPLY_LEN, 0};
  unsigned char inqBuff[INQUIRY_REPLY_LEN];
  unsigned char senseBuffer[SENSE_LEN];

  sg_io_hdr_t io_hdr;
  memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
  memset(inqBuff, 0, INQUIRY_REPLY_LEN);
  memset(senseBuffer, 0, SENSE_LEN);

  io_hdr.interface_id = 'S';
  io_hdr.cmd_len = sizeof(inqCmdBlk);
  io_hdr.mx_sb_len = sizeof(senseBuffer);
  io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
  io_hdr.dxfer_len = INQUIRY_REPLY_LEN;
  io_hdr.dxferp = inqBuff;
  io_hdr.cmdp = inqCmdBlk;
  io_hdr.sbp = senseBuffer;
  io_hdr.timeout = 2000;  // 2 seconds timeout

  if (ioctl(fd, SG_IO, &io_hdr) < 0) {
    perror("SG_IO ioctl failed");
    close(fd);
    return 1;
  }

  if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK) {
    fprintf(stderr, "SCSI command failed\n");
    close(fd);
    return 1;
  }

  printf("Vendor ID: %.8s\n", &inqBuff[8]);
  printf("Product ID: %.16s\n", &inqBuff[16]);
  printf("Product Revision: %.4s\n", &inqBuff[32]);

  close(fd);
  return 0;
}

However, when I record the interaction using the following command:

umockdev-record /dev/sda > usb_stick.umockdev
umockdev-record --ioctl /dev/sda=usb_stick.ioctl ./get_usb_stick_info /dev/sda

The generated file usb_stick.ioctl file only contains the line @dev /dev/sda.

It seems that umockdev-record is not capturing the actual ioctl system calls being made by my application.

Expected Behavior:

The usb_stick.ioctl file should contain the details of the ioctl system call. This would allow me to replay the interaction with the USB device in a mock environment.

System Information:

umockdev version: 018.1
martinpitt commented 4 months ago

Right, umockdev doesn't do mass storage. That's too complicated to emulate, you are better off with using scsi_debug, loop devices, etc. If you don't actually need to mount it, just get information, then it should be possible though -- contributions appreciated! (I won't work on this myself).

Degoah commented 4 months ago

I've spent several hours trying to achieve this with umockdev. Based on your response, it seems like updating the documentation to explicitly mention that mass storage devices are not currently supported would be helpful for future users.

Degoah commented 4 months ago

I see, loop device could be used...Yes, I don't need to mount the devices for my integration/unit tests. But right now I'm unsure, how to achieve this with loop devices. Am I right, that I need to create an image file, format it for example with ext4 and map it onto a loop device to get the filesystem information (label, model serial no.) for example with the "blkid" library?

benzea commented 4 months ago

What is the easiest solution probably depends a lot on what you need. One could:

  1. Add support for the ioctl to umockdev (in ioctl-tree.c), that is an option especially of the ioctl is simple
  2. Add some custom support for it (like the USB pcap support), that is possibly more complicated
  3. Implement the ioctl entirely yourself in the test infrastructure (similar to point 2, but no need for umockdev changes; see e.g. fwupd https://github.com/fwupd/fwupd/pull/6870)
  4. Or, as Martin already suggested, using some entirely separate infrastructure to test this.

Those are just a few ideas, I am sure there are other possible solutions. What makes sense for you likely depends a lot on what exactly you need to test.