]>
Commit | Line | Data |
---|---|---|
7618e112 KS |
1 | /* |
2 | * usb_id.c | |
3 | * | |
4 | * Identify an USB (block) device | |
5 | * | |
6 | * Copyright (c) 2005 SUSE Linux Products GmbH, Germany | |
2321ba6f | 7 | * |
7618e112 KS |
8 | * Author: |
9 | * Hannes Reinecke <hare@suse.de> | |
10 | * | |
2321ba6f KS |
11 | * This program is free software; you can redistribute it and/or modify it |
12 | * under the terms of the GNU General Public License as published by the | |
13 | * Free Software Foundation version 2 of the License. | |
7618e112 KS |
14 | */ |
15 | ||
16 | #include <stdio.h> | |
17 | #include <stdlib.h> | |
18 | #include <stdarg.h> | |
19 | #include <unistd.h> | |
20 | #include <string.h> | |
21 | #include <ctype.h> | |
22 | #include <errno.h> | |
23 | ||
1aa1e248 | 24 | #include "../../udev.h" |
7618e112 | 25 | |
6ecd4d1e | 26 | #define MAX_PATH_LEN 512 |
1aa1e248 | 27 | #define MAX_SERIAL_LEN 256 |
7618e112 KS |
28 | #define BLKGETSIZE64 _IOR(0x12,114,size_t) |
29 | ||
30 | #ifdef USE_LOG | |
31 | void log_message(int priority, const char *format, ...) | |
32 | { | |
33 | va_list args; | |
34 | static int udev_log = -1; | |
35 | ||
36 | if (udev_log == -1) { | |
37 | const char *value; | |
38 | ||
39 | value = getenv("UDEV_LOG"); | |
40 | if (value) | |
41 | udev_log = log_priority(value); | |
42 | else | |
43 | udev_log = LOG_ERR; | |
44 | } | |
45 | ||
46 | if (priority > udev_log) | |
47 | return; | |
48 | ||
49 | va_start(args, format); | |
50 | vsyslog(priority, format, args); | |
51 | va_end(args); | |
52 | } | |
53 | #endif | |
54 | ||
7618e112 KS |
55 | static char vendor_str[64]; |
56 | static char model_str[64]; | |
57 | static char serial_str[MAX_SERIAL_LEN]; | |
58 | static char revision_str[16]; | |
59 | static char type_str[16]; | |
60 | ||
61 | static int use_usb_info; | |
b4a2906b | 62 | static int use_num_info; |
7618e112 KS |
63 | static int export; |
64 | ||
70721db6 | 65 | static void set_str(char *to, const char *from, size_t count) |
7618e112 | 66 | { |
70721db6 | 67 | size_t i, j, len; |
7618e112 KS |
68 | |
69 | /* strip trailing whitespace */ | |
70 | len = strnlen(from, count); | |
c907c823 | 71 | while (len && isspace(from[len-1])) |
7618e112 KS |
72 | len--; |
73 | ||
74 | /* strip leading whitespace */ | |
75 | i = 0; | |
76 | while (isspace(from[i]) && (i < len)) | |
77 | i++; | |
78 | ||
79 | j = 0; | |
80 | while (i < len) { | |
81 | /* substitute multiple whitespace */ | |
82 | if (isspace(from[i])) { | |
83 | while (isspace(from[i])) | |
84 | i++; | |
85 | to[j++] = '_'; | |
86 | } | |
87 | /* Replace '/' with '.' */ | |
88 | if (from[i] == '/') { | |
89 | to[j++] = '.'; | |
90 | i++; | |
91 | continue; | |
92 | } | |
93 | /* skip non-printable chars */ | |
94 | if (!isalnum(from[i]) && !ispunct(from[i])) { | |
95 | i++; | |
96 | continue; | |
97 | } | |
98 | to[j++] = from[i++]; | |
99 | } | |
100 | to[j] = '\0'; | |
101 | } | |
102 | ||
f83cccb9 | 103 | static void set_usb_iftype(char *to, int if_class_num, size_t len) |
7618e112 | 104 | { |
853ccc43 | 105 | char *type = "generic"; |
7618e112 | 106 | |
f83cccb9 KS |
107 | switch (if_class_num) { |
108 | case 1: | |
109 | type = "audio"; | |
110 | break; | |
111 | case 3: | |
112 | type = "hid"; | |
113 | break; | |
114 | case 7: | |
115 | type = "printer"; | |
116 | break; | |
117 | case 8: | |
118 | type = "disk"; | |
119 | break; | |
120 | case 2: /* CDC-Control */ | |
121 | case 5: /* Physical */ | |
122 | case 6: /* Image */ | |
123 | case 9: /* HUB */ | |
124 | case 0x0a: /* CDC-Data */ | |
125 | case 0x0b: /* Chip/Smart Card */ | |
126 | case 0x0d: /* Content Security */ | |
127 | case 0x0e: /* Video */ | |
128 | case 0xdc: /* Diagnostic Device */ | |
129 | case 0xe0: /* Wireless Controller */ | |
130 | case 0xf2: /* Application-specific */ | |
131 | case 0xff: /* Vendor-specific */ | |
132 | break; | |
133 | default: | |
134 | break; | |
7618e112 | 135 | } |
853ccc43 KS |
136 | strncpy(to, type, len); |
137 | to[len-1] = '\0'; | |
7618e112 KS |
138 | } |
139 | ||
f83cccb9 | 140 | static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len) |
7618e112 KS |
141 | { |
142 | int type_num = 0; | |
143 | char *eptr; | |
853ccc43 | 144 | char *type = "generic"; |
7618e112 KS |
145 | |
146 | type_num = strtoul(from, &eptr, 0); | |
147 | if (eptr != from) { | |
148 | switch (type_num) { | |
149 | case 2: | |
853ccc43 | 150 | type = "cd"; |
7618e112 KS |
151 | break; |
152 | case 3: | |
853ccc43 | 153 | type = "tape"; |
7618e112 KS |
154 | break; |
155 | case 4: /* UFI */ | |
156 | case 5: /* SFF-8070i */ | |
853ccc43 | 157 | type = "floppy"; |
7618e112 KS |
158 | break; |
159 | case 1: /* RBC devices */ | |
160 | case 6: /* Transparent SPC-2 devices */ | |
853ccc43 KS |
161 | type = "disk"; |
162 | break; | |
163 | default: | |
7618e112 KS |
164 | break; |
165 | } | |
7618e112 | 166 | } |
853ccc43 KS |
167 | strncpy(to, type, len); |
168 | to[len-1] = '\0'; | |
7618e112 KS |
169 | |
170 | return type_num; | |
171 | } | |
172 | ||
173 | static void set_scsi_type(char *to, const char *from, int count) | |
174 | { | |
175 | int type_num; | |
176 | char *eptr; | |
177 | ||
178 | type_num = strtoul(from, &eptr, 0); | |
179 | if (eptr != from) { | |
180 | switch (type_num) { | |
181 | case 0: | |
182 | sprintf(to, "disk"); | |
183 | break; | |
184 | case 1: | |
185 | sprintf(to, "tape"); | |
186 | break; | |
187 | case 4: | |
188 | sprintf(to, "optical"); | |
189 | break; | |
190 | case 5: | |
191 | sprintf(to, "cd"); | |
192 | break; | |
193 | case 7: | |
194 | sprintf(to, "optical"); | |
195 | break; | |
196 | case 0xe: | |
197 | sprintf(to, "disk"); | |
198 | break; | |
199 | case 0xf: | |
200 | sprintf(to, "optical"); | |
201 | break; | |
202 | default: | |
203 | sprintf(to, "generic"); | |
204 | break; | |
205 | } | |
206 | } else { | |
207 | sprintf(to, "generic"); | |
208 | } | |
209 | } | |
210 | ||
211 | /* | |
212 | * A unique USB identification is generated like this: | |
213 | * | |
214 | * 1.) Get the USB device type from DeviceClass, InterfaceClass | |
215 | * and InterfaceSubClass | |
216 | * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC' | |
217 | * use the SCSI vendor and model as USB-Vendor and USB-model. | |
218 | * 3.) Otherwise use the USB manufacturer and product as | |
219 | * USB-Vendor and USB-model. Any non-printable characters | |
220 | * in those strings will be skipped; a slash '/' will be converted | |
221 | * into a full stop '.'. | |
222 | * 4.) If that fails, too, we will use idVendor and idProduct | |
223 | * as USB-Vendor and USB-model. | |
224 | * 5.) The USB identification is the USB-vendor and USB-model | |
225 | * string concatenated with an underscore '_'. | |
226 | * 6.) If the device supplies a serial number, this number | |
227 | * is concatenated with the identification with an underscore '_'. | |
228 | */ | |
1aa1e248 | 229 | static int usb_id(const char *devpath) |
7618e112 | 230 | { |
1aa1e248 | 231 | struct sysfs_device *dev; |
6ecd4d1e KS |
232 | struct sysfs_device *dev_interface; |
233 | struct sysfs_device *dev_usb; | |
1aa1e248 KS |
234 | const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev; |
235 | const char *usb_model = NULL, *usb_vendor = NULL, *usb_rev, *usb_serial; | |
236 | const char *if_class, *if_subclass; | |
7618e112 KS |
237 | int if_class_num; |
238 | int protocol = 0; | |
239 | ||
1aa1e248 KS |
240 | dbg("devpath %s\n", devpath); |
241 | ||
f83cccb9 | 242 | /* get all usb specific information: dev_interface, if_class, dev_usb */ |
1aa1e248 KS |
243 | dev = sysfs_device_get(devpath); |
244 | if (dev == NULL) { | |
245 | err("unable to access '%s'", devpath); | |
7618e112 KS |
246 | return 1; |
247 | } | |
7618e112 | 248 | |
7618e112 | 249 | /* usb interface directory */ |
f83cccb9 | 250 | dev_interface = sysfs_device_get_parent_with_subsystem(dev, "usb"); |
1aa1e248 | 251 | if (dev_interface == NULL) { |
f83cccb9 | 252 | info("unable to access usb_interface device of '%s'", devpath); |
7618e112 KS |
253 | return 1; |
254 | } | |
33aa91b1 | 255 | |
1aa1e248 | 256 | if_class = sysfs_attr_get_value(dev_interface->devpath, "bInterfaceClass"); |
7618e112 | 257 | if (!if_class) { |
95776dc6 | 258 | info("%s: cannot get bInterfaceClass attribute", dev_interface->kernel); |
b4a2906b | 259 | return 1; |
7618e112 | 260 | } |
1aa1e248 | 261 | if_class_num = strtoul(if_class, NULL, 16); |
f83cccb9 | 262 | if (if_class_num == 8) { |
1aa1e248 | 263 | if_subclass = sysfs_attr_get_value(dev_interface->devpath, "bInterfaceSubClass"); |
f83cccb9 KS |
264 | if (if_subclass != NULL) |
265 | protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1); | |
266 | } else | |
267 | set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1); | |
268 | ||
269 | info("%s: if_class %d protocol %d\n", dev_interface->devpath, if_class_num, protocol); | |
270 | ||
271 | /* usb device directory */ | |
272 | dev_usb = sysfs_device_get_parent_with_subsystem(dev_interface, "usb"); | |
273 | if (!dev_usb) { | |
274 | info("unable to find parent 'usb' device of '%s'", devpath); | |
275 | return 1; | |
7618e112 KS |
276 | } |
277 | ||
f83cccb9 KS |
278 | /* mass storage */ |
279 | if (protocol == 6 && !use_usb_info) { | |
280 | struct sysfs_device *dev_scsi; | |
281 | ||
282 | /* get scsi device */ | |
283 | dev_scsi = sysfs_device_get_parent_with_subsystem(dev, "scsi"); | |
284 | if (dev_scsi == NULL) { | |
285 | info("unable to find parent 'scsi' device of '%s'", devpath); | |
286 | goto fallback; | |
287 | } | |
288 | ||
7618e112 | 289 | /* Generic SPC-2 device */ |
1aa1e248 | 290 | scsi_vendor = sysfs_attr_get_value(dev_scsi->devpath, "vendor"); |
7618e112 | 291 | if (!scsi_vendor) { |
95776dc6 | 292 | info("%s: cannot get SCSI vendor attribute", dev_scsi->kernel); |
f83cccb9 | 293 | goto fallback; |
7618e112 | 294 | } |
1aa1e248 | 295 | set_str(vendor_str, scsi_vendor, sizeof(vendor_str)-1); |
7618e112 | 296 | |
1aa1e248 | 297 | scsi_model = sysfs_attr_get_value(dev_scsi->devpath, "model"); |
7618e112 | 298 | if (!scsi_model) { |
95776dc6 | 299 | info("%s: cannot get SCSI model attribute", dev_scsi->kernel); |
f83cccb9 | 300 | goto fallback; |
7618e112 | 301 | } |
1aa1e248 | 302 | set_str(model_str, scsi_model, sizeof(model_str)-1); |
7618e112 | 303 | |
1aa1e248 | 304 | scsi_type = sysfs_attr_get_value(dev_scsi->devpath, "type"); |
7618e112 | 305 | if (!scsi_type) { |
95776dc6 | 306 | info("%s: cannot get SCSI type attribute", dev_scsi->kernel); |
f83cccb9 | 307 | goto fallback; |
7618e112 | 308 | } |
1aa1e248 | 309 | set_scsi_type(type_str, scsi_type, sizeof(type_str)-1); |
7618e112 | 310 | |
1aa1e248 | 311 | scsi_rev = sysfs_attr_get_value(dev_scsi->devpath, "rev"); |
7618e112 | 312 | if (!scsi_rev) { |
95776dc6 | 313 | info("%s: cannot get SCSI revision attribute", dev_scsi->kernel); |
f83cccb9 | 314 | goto fallback; |
7618e112 | 315 | } |
1aa1e248 | 316 | set_str(revision_str, scsi_rev, sizeof(revision_str)-1); |
7618e112 KS |
317 | } |
318 | ||
f83cccb9 | 319 | fallback: |
7618e112 KS |
320 | /* Fallback to USB vendor & device */ |
321 | if (vendor_str[0] == '\0') { | |
b4a2906b | 322 | if (!use_num_info) |
1aa1e248 | 323 | if (!(usb_vendor = sysfs_attr_get_value(dev_usb->devpath, "manufacturer"))) |
b4a2906b HR |
324 | dbg("No USB vendor string found, using idVendor"); |
325 | ||
7618e112 | 326 | if (!usb_vendor) { |
1aa1e248 | 327 | if (!(usb_vendor = sysfs_attr_get_value(dev_usb->devpath, "idVendor"))) { |
b4a2906b HR |
328 | dbg("No USB vendor information available\n"); |
329 | sprintf(vendor_str,"0000"); | |
330 | } | |
7618e112 | 331 | } |
1aa1e248 | 332 | set_str(vendor_str,usb_vendor, sizeof(vendor_str) - 1); |
7618e112 KS |
333 | } |
334 | ||
335 | if (model_str[0] == '\0') { | |
b4a2906b | 336 | if (!use_num_info) |
1aa1e248 | 337 | if (!(usb_model = sysfs_attr_get_value(dev_usb->devpath, "product"))) |
b4a2906b HR |
338 | dbg("No USB model string found, using idProduct"); |
339 | ||
7618e112 | 340 | if (!usb_model) { |
1aa1e248 KS |
341 | if (!(usb_model = sysfs_attr_get_value(dev_usb->devpath, "idProduct"))) |
342 | dbg("No USB model information available\n"); sprintf(model_str,"0000"); | |
7618e112 | 343 | } |
1aa1e248 | 344 | set_str(model_str, usb_model, sizeof(model_str) - 1); |
7618e112 KS |
345 | } |
346 | ||
347 | if (revision_str[0] == '\0') { | |
1aa1e248 KS |
348 | usb_rev = sysfs_attr_get_value(dev_usb->devpath, "bcdDevice"); |
349 | if (usb_rev) | |
350 | set_str(revision_str, usb_rev, sizeof(revision_str)-1); | |
7618e112 KS |
351 | } |
352 | ||
353 | if (serial_str[0] == '\0') { | |
1aa1e248 KS |
354 | usb_serial = sysfs_attr_get_value(dev_usb->devpath, "serial"); |
355 | if (usb_serial) | |
356 | set_str(serial_str, usb_serial, sizeof(serial_str)-1); | |
7618e112 KS |
357 | } |
358 | return 0; | |
359 | } | |
360 | ||
361 | int main(int argc, char **argv) | |
362 | { | |
1aa1e248 KS |
363 | int retval = 0; |
364 | const char *env; | |
6ecd4d1e | 365 | char devpath[MAX_PATH_LEN]; |
7618e112 KS |
366 | int option; |
367 | ||
1aa1e248 KS |
368 | logging_init("usb_id"); |
369 | sysfs_init(); | |
370 | ||
7618e112 | 371 | dbg("argc is %d", argc); |
1aa1e248 | 372 | |
b4a2906b | 373 | while ((option = getopt(argc, argv, "dnux")) != -1 ) { |
7618e112 KS |
374 | if (optarg) |
375 | dbg("option '%c' arg '%s'", option, optarg); | |
376 | else | |
377 | dbg("option '%c'", option); | |
378 | ||
379 | switch (option) { | |
b4a2906b | 380 | case 'n': |
1aa1e248 KS |
381 | use_num_info = 1; |
382 | use_usb_info = 1; | |
b4a2906b | 383 | break; |
7618e112 | 384 | case 'u': |
1aa1e248 | 385 | use_usb_info = 1; |
7618e112 KS |
386 | break; |
387 | case 'x': | |
1aa1e248 | 388 | export = 1; |
7618e112 KS |
389 | break; |
390 | default: | |
391 | info("Unknown or bad option '%c' (0x%x)", option, option); | |
b4a2906b | 392 | retval = 1; |
7618e112 KS |
393 | break; |
394 | } | |
395 | } | |
396 | ||
1aa1e248 KS |
397 | env = getenv("DEVPATH"); |
398 | if (env != NULL) | |
399 | strlcpy(devpath, env, sizeof(devpath)); | |
400 | else { | |
7618e112 KS |
401 | if (optind == argc) { |
402 | fprintf(stderr, "No device specified\n"); | |
1aa1e248 KS |
403 | retval = 1; |
404 | goto exit; | |
7618e112 | 405 | } |
1aa1e248 | 406 | strlcpy(devpath, argv[optind], sizeof(devpath)); |
7618e112 KS |
407 | } |
408 | ||
1aa1e248 | 409 | retval = usb_id(devpath); |
7618e112 | 410 | |
b4a2906b HR |
411 | if (retval == 0) { |
412 | if (export) { | |
413 | printf("ID_VENDOR=%s\n", vendor_str); | |
414 | printf("ID_MODEL=%s\n", model_str); | |
415 | printf("ID_REVISION=%s\n", revision_str); | |
416 | if (serial_str[0] == '\0') { | |
417 | printf("ID_SERIAL=%s_%s\n", | |
418 | vendor_str, model_str); | |
419 | } else { | |
420 | printf("ID_SERIAL=%s_%s_%s\n", | |
421 | vendor_str, model_str, serial_str); | |
422 | } | |
423 | printf("ID_TYPE=%s\n", type_str); | |
46f01c6d | 424 | printf("ID_BUS=usb\n"); |
7618e112 | 425 | } else { |
b4a2906b HR |
426 | if (serial_str[0] == '\0') { |
427 | printf("%s_%s\n", | |
428 | vendor_str, model_str); | |
429 | } else { | |
430 | printf("%s_%s_%s\n", | |
431 | vendor_str, model_str, serial_str); | |
432 | } | |
7618e112 KS |
433 | } |
434 | } | |
1aa1e248 KS |
435 | |
436 | exit: | |
437 | sysfs_cleanup(); | |
438 | logging_close(); | |
439 | return retval; | |
7618e112 | 440 | } |