rbscsi: add experimental API to list connected SCSI devices
For now it is only implemented on linux using /sys scanning Change-Id: Ifdfe7564e6e8d0307ae6ddc53e49bb9aaf5a8268
This commit is contained in:
parent
4365437159
commit
e01248efcd
2 changed files with 236 additions and 1 deletions
|
@ -18,6 +18,8 @@
|
|||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#define _XOPEN_SOURCE 500
|
||||
#define _DEFAULT_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
@ -47,6 +49,10 @@ typedef HANDLE rb_scsi_handle_t;
|
|||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <scsi/sg.h>
|
||||
#include <dirent.h>
|
||||
#include <linux/limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue