diff options
author | Amaury Pouly <amaury.pouly@gmail.com> | 2018-11-30 15:14:30 +0100 |
---|---|---|
committer | Amaury Pouly <amaury.pouly@gmail.com> | 2018-11-30 15:37:10 +0100 |
commit | e01248efcd622211c0451e6dc4ed47cc4b758cfa (patch) | |
tree | ba0d979d82579f67b75b074e38eeaf34b3599cfe /utils | |
parent | 43654371591feab23ce0a52af42bb88de0abfbca (diff) | |
download | rockbox-e01248efcd622211c0451e6dc4ed47cc4b758cfa.tar.gz rockbox-e01248efcd622211c0451e6dc4ed47cc4b758cfa.zip |
rbscsi: add experimental API to list connected SCSI devices
For now it is only implemented on linux using /sys scanning
Change-Id: Ifdfe7564e6e8d0307ae6ddc53e49bb9aaf5a8268
Diffstat (limited to 'utils')
-rw-r--r-- | utils/scsi/rbscsi.c | 212 | ||||
-rw-r--r-- | utils/scsi/rbscsi.h | 25 |
2 files changed, 236 insertions, 1 deletions
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 @@ | |||
18 | * KIND, either express or implied. | 18 | * KIND, either express or implied. |
19 | * | 19 | * |
20 | ****************************************************************************/ | 20 | ****************************************************************************/ |
21 | #define _XOPEN_SOURCE 500 | ||
22 | #define _DEFAULT_SOURCE | ||
21 | #include <stdlib.h> | 23 | #include <stdlib.h> |
22 | #include <stdio.h> | 24 | #include <stdio.h> |
23 | #include <stdarg.h> | 25 | #include <stdarg.h> |
@@ -47,6 +49,10 @@ typedef HANDLE rb_scsi_handle_t; | |||
47 | #include <errno.h> | 49 | #include <errno.h> |
48 | #include <sys/ioctl.h> | 50 | #include <sys/ioctl.h> |
49 | #include <scsi/sg.h> | 51 | #include <scsi/sg.h> |
52 | #include <dirent.h> | ||
53 | #include <linux/limits.h> | ||
54 | #include <sys/types.h> | ||
55 | #include <sys/stat.h> | ||
50 | #define RB_SCSI_LINUX | 56 | #define RB_SCSI_LINUX |
51 | typedef int rb_scsi_handle_t; | 57 | typedef int rb_scsi_handle_t; |
52 | #else | 58 | #else |
@@ -151,7 +157,175 @@ void rb_scsi_close(rb_scsi_device_t dev) | |||
151 | free(dev); | 157 | free(dev); |
152 | } | 158 | } |
153 | 159 | ||
154 | /* Windpws */ | 160 | static int is_hctl(const char *name) |
161 | { | ||
162 | char *end; | ||
163 | strtoul(name, &end, 0); /* h */ | ||
164 | if(*end != ':') | ||
165 | return 0; | ||
166 | strtoul(end + 1, &end, 0); /* c */ | ||
167 | if(*end != ':') | ||
168 | return 0; | ||
169 | strtoul(end + 1, &end, 0); /* t */ | ||
170 | if(*end != ':') | ||
171 | return 0; | ||
172 | strtoul(end + 1, &end, 0); /* l */ | ||
173 | return *end == 0; | ||
174 | } | ||
175 | |||
176 | static int _resolve_link_dev_path(char *path, size_t pathsz) | ||
177 | { | ||
178 | /* make sure it is a directory */ | ||
179 | struct stat st; | ||
180 | if(stat(path, &st) < 0) | ||
181 | return 0; | ||
182 | if(!S_ISDIR(st.st_mode)) | ||
183 | return 0; | ||
184 | if(chdir(path) < 0) | ||
185 | return 0; | ||
186 | if(getcwd(path, pathsz) == NULL) | ||
187 | return 0; | ||
188 | return 1; | ||
189 | } | ||
190 | |||
191 | static int resolve_link_dev_path(char *path, size_t pathsz) | ||
192 | { | ||
193 | /* change directory, ask the current path and resolve current directory */ | ||
194 | char curdir[PATH_MAX]; | ||
195 | if(getcwd(curdir, sizeof(curdir)) == NULL) | ||
196 | return 0; | ||
197 | int ret = _resolve_link_dev_path(path, pathsz); | ||
198 | chdir(curdir); | ||
199 | return ret; | ||
200 | } | ||
201 | |||
202 | static int scan_resolve_first_dev_path(char *path, size_t pathsz) | ||
203 | { | ||
204 | size_t pathlen = strlen(path); | ||
205 | char *pathend = path + pathlen; | ||
206 | DIR *dir = opendir(path); | ||
207 | if(dir == NULL) | ||
208 | return 0; | ||
209 | struct dirent *d; | ||
210 | int ret = 0; | ||
211 | while((d = readdir(dir))) | ||
212 | { | ||
213 | if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) | ||
214 | continue; | ||
215 | /* we found the entry, if it is a symlink, resolve it, otherwise it must be a directory */ | ||
216 | if(d->d_type == DT_LNK) | ||
217 | { | ||
218 | snprintf(pathend, pathsz - pathlen, "/%s", d->d_name); | ||
219 | ret = resolve_link_dev_path(path, pathsz); | ||
220 | } | ||
221 | else if(d->d_type == DT_DIR) | ||
222 | { | ||
223 | snprintf(path, pathsz, "/dev/%s", d->d_name); | ||
224 | ret = 1; | ||
225 | } | ||
226 | break; | ||
227 | } | ||
228 | closedir(dir); | ||
229 | return ret; | ||
230 | } | ||
231 | |||
232 | static int scan_resolve_dev_path(char *path, size_t pathsz, const char *match) | ||
233 | { | ||
234 | size_t pathlen = strlen(path); | ||
235 | char *pathend = path + pathlen; | ||
236 | DIR *dir = opendir(path); | ||
237 | if(dir == NULL) | ||
238 | return 0; | ||
239 | struct dirent *d; | ||
240 | int ret = 0; | ||
241 | while((d = readdir(dir))) | ||
242 | { | ||
243 | if(strcmp(d->d_name, match)) | ||
244 | continue; | ||
245 | /* we found the entry, there are two case: | ||
246 | * - directory: we need to scan it and find the first entry | ||
247 | * - symlink: we need to see where it goes and extract the basename */ | ||
248 | snprintf(pathend, pathsz - pathlen, "/%s", d->d_name); | ||
249 | if(d->d_type == DT_DIR) | ||
250 | ret = scan_resolve_first_dev_path(path, pathsz); | ||
251 | else if(d->d_type == DT_LNK) | ||
252 | ret = resolve_link_dev_path(path, pathsz); | ||
253 | break; | ||
254 | } | ||
255 | closedir(dir); | ||
256 | return ret; | ||
257 | } | ||
258 | |||
259 | static char *read_file_or_null(const char *path) | ||
260 | { | ||
261 | FILE *f = fopen(path, "r"); | ||
262 | if(f == NULL) | ||
263 | return NULL; | ||
264 | char buffer[1024]; | ||
265 | if(fgets(buffer, sizeof(buffer), f) == NULL) | ||
266 | { | ||
267 | fclose(f); | ||
268 | return NULL; | ||
269 | } | ||
270 | fclose(f); | ||
271 | /* the kernel appends a '\n' at the end, remove it */ | ||
272 | size_t len = strlen(buffer); | ||
273 | if(len > 0 && buffer[len - 1] == '\n') | ||
274 | buffer[len - 1] = 0; | ||
275 | return strdup(buffer); | ||
276 | } | ||
277 | |||
278 | struct rb_scsi_devent_t *rb_scsi_list(void) | ||
279 | { | ||
280 | /* list devices in /sys/bus/scsi/devices | ||
281 | * we only keep entries of the form h:c:t:l */ | ||
282 | #define SYS_SCSI_DEV_PATH "/sys/bus/scsi/devices" | ||
283 | DIR *dir = opendir(SYS_SCSI_DEV_PATH); | ||
284 | if(dir == NULL) | ||
285 | return NULL; | ||
286 | struct dirent *d; | ||
287 | struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t)); | ||
288 | dev[0].scsi_path = NULL; | ||
289 | dev[0].block_path = NULL; | ||
290 | int nr_dev = 0; | ||
291 | while((d = readdir(dir))) | ||
292 | { | ||
293 | if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) | ||
294 | continue; | ||
295 | /* make sure the name is of the form h:c:t:l, we do not want targets or hosts */ | ||
296 | if(!is_hctl(d->d_name)) | ||
297 | continue; | ||
298 | /* we now need to scan the directory to find the block and scsi generic device path: | ||
299 | * block: there should be a 'block' entry | ||
300 | * scsi: there should be a 'scsi_generic' entry | ||
301 | * Both entries can either be a symlink to the devide, or a directory with a single entry */ | ||
302 | char scsi_path[PATH_MAX]; | ||
303 | snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s", d->d_name); | ||
304 | if(!scan_resolve_dev_path(scsi_path, sizeof(scsi_path), "scsi_generic")) | ||
305 | continue; | ||
306 | char block_path[PATH_MAX]; | ||
307 | snprintf(block_path, sizeof(block_path), SYS_SCSI_DEV_PATH "/%s", d->d_name); | ||
308 | if(!scan_resolve_dev_path(block_path, sizeof(block_path), "block")) | ||
309 | block_path[0] = 0; | ||
310 | dev = realloc(dev, (2 + nr_dev) * sizeof(struct rb_scsi_devent_t)); | ||
311 | dev[nr_dev].scsi_path = strdup(scsi_path); | ||
312 | dev[nr_dev].block_path = block_path[0] == 0 ? NULL : strdup(block_path); | ||
313 | /* fill vendor/model/rev */ | ||
314 | snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/vendor", d->d_name); | ||
315 | dev[nr_dev].vendor = read_file_or_null(scsi_path); | ||
316 | snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/model", d->d_name); | ||
317 | dev[nr_dev].model = read_file_or_null(scsi_path); | ||
318 | snprintf(scsi_path, sizeof(scsi_path), SYS_SCSI_DEV_PATH "/%s/rev", d->d_name); | ||
319 | dev[nr_dev].rev = read_file_or_null(scsi_path); | ||
320 | |||
321 | /* sentinel */ | ||
322 | dev[++nr_dev].scsi_path = NULL; | ||
323 | dev[nr_dev].block_path = NULL; | ||
324 | } | ||
325 | closedir(dir); | ||
326 | return dev; | ||
327 | } | ||
328 | /* Windows */ | ||
155 | #elif defined(RB_SCSI_WINDOWS) | 329 | #elif defined(RB_SCSI_WINDOWS) |
156 | /* return either path or something allocated with malloc() */ | 330 | /* return either path or something allocated with malloc() */ |
157 | static const char *map_to_physical_drive(const char *path, unsigned flags, void *user, | 331 | 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) | |||
275 | free(dev); | 449 | free(dev); |
276 | } | 450 | } |
277 | 451 | ||
452 | struct rb_scsi_devent_t *rb_scsi_list(void) | ||
453 | { | ||
454 | /* unimplemented */ | ||
455 | struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t)); | ||
456 | dev[0].scsi_path = NULL; | ||
457 | dev[0].block_path = NULL; | ||
458 | return dev; | ||
459 | } | ||
278 | /* other targets */ | 460 | /* other targets */ |
279 | #else | 461 | #else |
280 | rb_scsi_device_t rb_scsi_open(const char *path, unsigned flags, void *user, | 462 | 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) | |||
297 | { | 479 | { |
298 | free(dev); | 480 | free(dev); |
299 | } | 481 | } |
482 | |||
483 | struct rb_scsi_devent_t *rb_scsi_list(void) | ||
484 | { | ||
485 | /* unimplemented */ | ||
486 | struct rb_scsi_devent_t *dev = malloc(sizeof(struct rb_scsi_devent_t)); | ||
487 | dev[0].scsi_path = NULL; | ||
488 | dev[0].block_path = NULL; | ||
489 | return dev; | ||
490 | } | ||
300 | #endif | 491 | #endif |
301 | 492 | ||
302 | void rb_scsi_decode_sense(rb_scsi_device_t dev, void *_sense, int sense_len) | 493 | 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) | |||
333 | 524 | ||
334 | #undef rb_printf | 525 | #undef rb_printf |
335 | } | 526 | } |
527 | |||
528 | void rb_scsi_free_list(struct rb_scsi_devent_t *list) | ||
529 | { | ||
530 | if(list == NULL) | ||
531 | return; | ||
532 | for(struct rb_scsi_devent_t *p = list; p->scsi_path; p++) | ||
533 | { | ||
534 | free(p->scsi_path); | ||
535 | if(p->block_path) | ||
536 | free(p->block_path); | ||
537 | if(p->vendor) | ||
538 | free(p->vendor); | ||
539 | if(p->model) | ||
540 | free(p->model); | ||
541 | if(p->rev) | ||
542 | free(p->rev); | ||
543 | } | ||
544 | free(list); | ||
545 | } | ||
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); | |||
89 | /* close a device */ | 89 | /* close a device */ |
90 | void rb_scsi_close(rb_scsi_device_t dev); | 90 | void rb_scsi_close(rb_scsi_device_t dev); |
91 | 91 | ||
92 | /* SCSI device reported by rb_scsi_list() */ | ||
93 | struct rb_scsi_devent_t | ||
94 | { | ||
95 | /* device path to the raw SCSI device, typically: | ||
96 | * - Linux: /dev/sgX | ||
97 | * - Windows: TODO | ||
98 | * This path can be used directly with scsi_rb_open(), and is guaranteed to | ||
99 | * be valid. */ | ||
100 | char *scsi_path; | ||
101 | /* device path to the corresponding block device, if it exists, typically: | ||
102 | * - Linux: /dev/sdX | ||
103 | * - Windows: TODO | ||
104 | * If this path is not-NULL, then it can used directly with scsi_rb_open() */ | ||
105 | char *block_path; | ||
106 | /* various information about the device, can be NULL on error */ | ||
107 | char *vendor; | ||
108 | char *model; | ||
109 | char *rev; | ||
110 | }; | ||
111 | /* try to list all SCSI devices, returns a list of devices or NULL on error | ||
112 | * the list is terminated by an entry with scsi_path=NULL */ | ||
113 | struct rb_scsi_devent_t *rb_scsi_list(void); | ||
114 | /* free the list returned by rb_scsi_list */ | ||
115 | void rb_scsi_free_list(struct rb_scsi_devent_t *list); | ||
116 | |||
92 | #ifdef __cplusplus | 117 | #ifdef __cplusplus |
93 | } | 118 | } |
94 | #endif | 119 | #endif |