From e01248efcd622211c0451e6dc4ed47cc4b758cfa Mon Sep 17 00:00:00 2001 From: Amaury Pouly Date: Fri, 30 Nov 2018 15:14:30 +0100 Subject: rbscsi: add experimental API to list connected SCSI devices For now it is only implemented on linux using /sys scanning Change-Id: Ifdfe7564e6e8d0307ae6ddc53e49bb9aaf5a8268 --- utils/scsi/rbscsi.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++- utils/scsi/rbscsi.h | 25 +++++++ 2 files changed, 236 insertions(+), 1 deletion(-) diff --git a/utils/scsi/rbscsi.c b/utils/scsi/rbscsi.c index a43608a08b..8e15f1fd6a 100644 --- a/utils/scsi/rbscsi.c +++ b/utils/scsi/rbscsi.c @@ -18,6 +18,8 @@ * KIND, either express or implied. * ****************************************************************************/ +#define _XOPEN_SOURCE 500 +#define _DEFAULT_SOURCE #include #include #include @@ -47,6 +49,10 @@ typedef HANDLE rb_scsi_handle_t; #include #include #include +#include +#include +#include +#include #define RB_SCSI_LINUX typedef int rb_scsi_handle_t; #else @@ -151,7 +157,175 @@ void rb_scsi_close(rb_scsi_device_t dev) free(dev); } -/* Windpws */ +static int is_hctl(const char *name) +{ + char *end; + strtoul(name, &end, 0); /* h */ + if(*end != ':') + return 0; + strtoul(end + 1, &end, 0); /* c */ + if(*end != ':') + return 0; + strtoul(end + 1, &end, 0); /* t */ + if(*end != ':') + return 0; + strtoul(end + 1, &end, 0); /* l */ + return *end == 0; +} + +static int _resolve_link_dev_path(char *path, size_t pathsz) +{ + /* make sure it is a directory */ + struct stat st; + if(stat(path, &st) < 0) + return 0; + if(!S_ISDIR(st.st_mode)) + return 0; + if(chdir(path) < 0) + return 0; + if(getcwd(path, pathsz) == NULL) + return 0; + return 1; +} + +static int resolve_link_dev_path(char *path, size_t pathsz) +{ + /* change directory, ask the current path and resolve current directory */ + char curdir[PATH_MAX]; + if(getcwd(curdir, sizeof(curdir)) == NULL) + return 0; + int ret = _resolve_link_dev_path(path, pathsz); + chdir(curdir); + return ret; +} + +static int scan_resolve_first_dev_path(char *path, size_t pathsz) +{ + size_t pathlen = strlen(path); + char *pathend = path + pathlen; + DIR *dir = opendir(path); + if(dir == NULL) + return 0; + struct dirent *d; + int ret = 0; + while((d = readdir(dir))) + { + if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + /* we found the entry, if it is a symlink, resolve it, otherwise it must be a directory */ + if(d->d_type == DT_LNK) + { + snprintf(pathend, pathsz - pathlen, "/%s", d->d_name); + ret = resolve_link_dev_path(path, pathsz); + } + else if(d->d_type == DT_DIR) + { + snprintf(path, pathsz, "/dev/%s", d->d_name); + ret = 1; + } + break; + } + closedir(dir); + return ret; +} + +static int scan_resolve_dev_path(char *path, size_t pathsz, const char *match) +{ + size_t pathlen = strlen(path); + char *pathend = path + pathlen; + DIR *dir = opendir(path); + if(dir == NULL) + return 0; + struct dirent *d; + int ret = 0; + while((d = readdir(dir))) + { + if(strcmp(d->d_name, match)) + continue; + /* we found the entry, there are two case: + * - directory: we need to scan it and find the first entry + * - symlink: we need to see where it goes and extract the basename */ + snprintf(pathend, pathsz - pathlen, "/%s", d->d_name); + if(d->d_type == DT_DIR) + ret = scan_resolve_first_dev_path(path, pathsz); + else if(d->d_type == DT_LNK) + ret = resolve_link_dev_path(path, pathsz); + break; + } + closedir(dir); + return ret; +} + +static char *read_file_or_null(const char *path) +{ + FILE *f = fopen(path, "r"); + if(f == NULL) + return NULL; + char buffer[1024]; + if(fgets(buffer, sizeof(buffer), f) == NULL) + { + fclose(f); + return NULL; + } + fclose(f); + /* the kernel appends a '\n' at the end, remove it */ + size_t len = strlen(buffer); + if(len > 0 && buffer[len - 1] == '\n') + buffer[len - 1] = 0; + return strdup(buffer); +} + +struct rb_scsi_devent_t *rb_scsi_list(void) +{ + /* list devices in /sys/bus/scsi/devices + * we only keep entries of the form h:c:t:l */ +#define SYS_SCSI_DEV_PATH "/sys/bus/scsi/devices" + DIR *dir = opendir(SYS_SCSI_DEV_PATH); + if(dir == NULL) + return NULL; + struct dirent *d; + struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t)); + dev[0].scsi_path = NULL; + dev[0].block_path = NULL; + int nr_dev = 0; + while((d = readdir(dir))) + { + if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) + continue; + /* make sure the name is of the form h:c:t:l, we do not want targets or hosts */ + if(!is_hctl(d->d_name)) + continue; + /* we now need to scan the directory to find the block and scsi generic device path: + * block: there should be a 'block' entry + * scsi: there should be a 'scsi_generic' entry + * Both entries can either be a symlink to the devide, or a directory with a single entry */ + char scsi_path[PATH_MAX]; + snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s", d->d_name); + if(!scan_resolve_dev_path(scsi_path, sizeof(scsi_path), "scsi_generic")) + continue; + char block_path[PATH_MAX]; + snprintf(block_path, sizeof(block_path), SYS_SCSI_DEV_PATH "/%s", d->d_name); + if(!scan_resolve_dev_path(block_path, sizeof(block_path), "block")) + block_path[0] = 0; + dev = realloc(dev, (2 + nr_dev) * sizeof(struct rb_scsi_devent_t)); + dev[nr_dev].scsi_path = strdup(scsi_path); + dev[nr_dev].block_path = block_path[0] == 0 ? NULL : strdup(block_path); + /* fill vendor/model/rev */ + snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/vendor", d->d_name); + dev[nr_dev].vendor = read_file_or_null(scsi_path); + snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/model", d->d_name); + dev[nr_dev].model = read_file_or_null(scsi_path); + snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/rev", d->d_name); + dev[nr_dev].rev = read_file_or_null(scsi_path); + + /* sentinel */ + dev[++nr_dev].scsi_path = NULL; + dev[nr_dev].block_path = NULL; + } + closedir(dir); + return dev; +} +/* Windows */ #elif defined(RB_SCSI_WINDOWS) /* return either path or something allocated with malloc() */ static const char *map_to_physical_drive(const char *path, unsigned flags, void *user, @@ -275,6 +449,14 @@ void rb_scsi_close(rb_scsi_device_t dev) free(dev); } +struct rb_scsi_devent_t *rb_scsi_list(void) +{ + /* unimplemented */ + struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t)); + dev[0].scsi_path = NULL; + dev[0].block_path = NULL; + return dev; +} /* other targets */ #else rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user, @@ -297,6 +479,15 @@ void rb_scsi_close(rb_scsi_device_t dev) { free(dev); } + +struct rb_scsi_devent_t *rb_scsi_list(void) +{ + /* unimplemented */ + struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t)); + dev[0].scsi_path = NULL; + dev[0].block_path = NULL; + return dev; +} #endif void rb_scsi_decode_sense(rb_scsi_device_t dev, void *_sense, int sense_len) @@ -333,3 +524,22 @@ void rb_scsi_decode_sense(rb_scsi_device_t dev, void *_sense, int sense_len) #undef rb_printf } + +void rb_scsi_free_list(struct rb_scsi_devent_t *list) +{ + if(list == NULL) + return; + for(struct rb_scsi_devent_t *p = list; p->scsi_path; p++) + { + free(p->scsi_path); + if(p->block_path) + free(p->block_path); + if(p->vendor) + free(p->vendor); + if(p->model) + free(p->model); + if(p->rev) + free(p->rev); + } + free(list); +} diff --git a/utils/scsi/rbscsi.h b/utils/scsi/rbscsi.h index c7345a6cdf..322d94ec53 100644 --- a/utils/scsi/rbscsi.h +++ b/utils/scsi/rbscsi.h @@ -89,6 +89,31 @@ void rb_scsi_decode_sense(rb_scsi_device_t dev, void *sense, int sense_len); /* close a device */ void rb_scsi_close(rb_scsi_device_t dev); +/* SCSI device reported by rb_scsi_list() */ +struct rb_scsi_devent_t +{ + /* device path to the raw SCSI device, typically: + * - Linux: /dev/sgX + * - Windows: TODO + * This path can be used directly with scsi_rb_open(), and is guaranteed to + * be valid. */ + char *scsi_path; + /* device path to the corresponding block device, if it exists, typically: + * - Linux: /dev/sdX + * - Windows: TODO + * If this path is not-NULL, then it can used directly with scsi_rb_open() */ + char *block_path; + /* various information about the device, can be NULL on error */ + char *vendor; + char *model; + char *rev; +}; +/* try to list all SCSI devices, returns a list of devices or NULL on error + * the list is terminated by an entry with scsi_path=NULL */ +struct rb_scsi_devent_t *rb_scsi_list(void); +/* free the list returned by rb_scsi_list */ +void rb_scsi_free_list(struct rb_scsi_devent_t *list); + #ifdef __cplusplus } #endif -- cgit v1.2.3