2
votes

how can I programmatically enum all the USB storages using C under Linux? I want to get the strings such as '/dev/sdb4' so that I can then use mount() to mount them.

I guess there's some approach to list all USB devices, but I only want the USB storages.

In addition, can we distinguish a USB flash drive (plugged into the machine's USB port directly) and a USB disk (usually connected to the machine via a USB cable)?

Thanks.

1
Check out lsusb might help you list the USB devices.user1551592

1 Answers

3
votes

You can find all USB storage devices from directory /proc/scsi/usb-storage. By listing the contents of this directory you can find out SCSI host device numbers with which you're then ready to check the file /sys/class/scsi_disk/N:*:*:* where N is the number of SCSI host device. The files in /sys/class/scsi_disk/ are links to actual device directories.

What follows is one way of doing this, though it's not one of the best. I think you might want to rely on an automounter instead of coding this by hand.

The part where you do mixing and matching based on what particular disk device you have attached (in your example you list two) is left as an exercise for the reader.

#include <stdio.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#include <linux/limits.h>
#include <glob.h>
#include <unistd.h>

#define PROC_USB_DEV "/proc/scsi/usb-storage"
#define SYS_SCSI_HOST "/sys/class/scsi_host/host%s/device"
#define SYS_SCSI_DEV "/sys/class/scsi_disk/%s:*"
#define SYS_SCSI_TGT "%s/device/block/*"

int main(int argc, char **argv) {
  DIR *dp = NULL;
  struct dirent *dt = NULL;

  if((dp = opendir(PROC_USB_DEV)) == NULL) {
    fprintf(stderr, "Can not open %s: %s", PROC_USB_DEV, strerror(errno)); 
    return 2;
  }

  /* find usb storage device hosts which appear as scsi hosts */
  /* for the sake of example this one hasn't been done with glob(3) */
  while((dt = readdir(dp)) != NULL) {
    int scsi_dev = 0;
    /* skip '.' and '..', possibly others too */
    if((scsi_dev = atoi(basename(dt->d_name))) < 1) continue;

    char buf[PATH_MAX];
    char pat[PATH_MAX];

    snprintf(buf, PATH_MAX, SYS_SCSI_HOST, basename(dt->d_name));
    snprintf(pat, PATH_MAX, SYS_SCSI_DEV, basename(dt->d_name));

    glob_t hosts;
    size_t count;

    /* find SCSI host device paths */
    glob(pat, 0, 0, &hosts);
    if(hosts.gl_pathc > 0) {
      char **p;
      int n;
      for(p = hosts.gl_pathv, n = hosts.gl_pathc; n; p++, n--) {
        char tgtbuf[PATH_MAX + NAME_MAX];

        snprintf(tgtbuf, PATH_MAX + NAME_MAX, SYS_SCSI_TGT, *p);

        /* find SCSI disk device paths */
        glob_t devs;
        glob(tgtbuf, 0, 0, &devs);
        if(devs.gl_pathc > 0) {
          char **ptr;
          int c;
          for(ptr = devs.gl_pathv, c = devs.gl_pathc; c; ptr++, c--) {
            printf("We would now call mount(2) for /dev/%s.\n",
                   basename(*ptr));
          }
        }
        globfree(&devs);
      }
      globfree(&hosts);
    }
  }
}

As a final note: this is most likely not the best possible example to learn from when it comes to coding style in general.