]>
Commit | Line | Data |
---|---|---|
d7867b31 KS |
1 | /* |
2 | * USB device properties and persistent device path | |
3 | * | |
4 | * Copyright (c) 2005 SUSE Linux Products GmbH, Germany | |
5 | * Author: Hannes Reinecke <hare@suse.de> | |
6 | * | |
1298001e | 7 | * Copyright (C) 2005-2011 Kay Sievers <kay@vrfy.org> |
d7867b31 KS |
8 | * |
9 | * This program is free software: you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation, either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU General Public License | |
20 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
21 | */ | |
22 | ||
23 | #include <stdio.h> | |
24 | #include <stdlib.h> | |
25 | #include <stdarg.h> | |
26 | #include <unistd.h> | |
27 | #include <string.h> | |
28 | #include <ctype.h> | |
29 | #include <fcntl.h> | |
30 | #include <errno.h> | |
31 | ||
32 | #include "udev.h" | |
33 | ||
d7867b31 KS |
34 | static void set_usb_iftype(char *to, int if_class_num, size_t len) |
35 | { | |
04a9d3a0 | 36 | const char *type = "generic"; |
912541b0 KS |
37 | |
38 | switch (if_class_num) { | |
39 | case 1: | |
40 | type = "audio"; | |
41 | break; | |
42 | case 2: /* CDC-Control */ | |
43 | break; | |
44 | case 3: | |
45 | type = "hid"; | |
46 | break; | |
47 | case 5: /* Physical */ | |
48 | break; | |
49 | case 6: | |
50 | type = "media"; | |
51 | break; | |
52 | case 7: | |
53 | type = "printer"; | |
54 | break; | |
55 | case 8: | |
56 | type = "storage"; | |
57 | break; | |
58 | case 9: | |
59 | type = "hub"; | |
60 | break; | |
61 | case 0x0a: /* CDC-Data */ | |
62 | break; | |
63 | case 0x0b: /* Chip/Smart Card */ | |
64 | break; | |
65 | case 0x0d: /* Content Security */ | |
66 | break; | |
67 | case 0x0e: | |
68 | type = "video"; | |
69 | break; | |
70 | case 0xdc: /* Diagnostic Device */ | |
71 | break; | |
72 | case 0xe0: /* Wireless Controller */ | |
73 | break; | |
74 | case 0xfe: /* Application-specific */ | |
75 | break; | |
76 | case 0xff: /* Vendor-specific */ | |
77 | break; | |
78 | default: | |
79 | break; | |
80 | } | |
81 | strncpy(to, type, len); | |
82 | to[len-1] = '\0'; | |
d7867b31 KS |
83 | } |
84 | ||
85 | static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len) | |
86 | { | |
912541b0 KS |
87 | int type_num = 0; |
88 | char *eptr; | |
04a9d3a0 | 89 | const char *type = "generic"; |
912541b0 KS |
90 | |
91 | type_num = strtoul(from, &eptr, 0); | |
92 | if (eptr != from) { | |
93 | switch (type_num) { | |
94 | case 2: | |
95 | type = "atapi"; | |
96 | break; | |
97 | case 3: | |
98 | type = "tape"; | |
99 | break; | |
100 | case 4: /* UFI */ | |
101 | case 5: /* SFF-8070i */ | |
102 | type = "floppy"; | |
103 | break; | |
104 | case 1: /* RBC devices */ | |
105 | type = "rbc"; | |
106 | break; | |
107 | case 6: /* Transparent SPC-2 devices */ | |
108 | type = "scsi"; | |
109 | break; | |
110 | default: | |
111 | break; | |
112 | } | |
113 | } | |
114 | util_strscpy(to, len, type); | |
115 | return type_num; | |
d7867b31 KS |
116 | } |
117 | ||
118 | static void set_scsi_type(char *to, const char *from, size_t len) | |
119 | { | |
912541b0 KS |
120 | int type_num; |
121 | char *eptr; | |
04a9d3a0 | 122 | const char *type = "generic"; |
912541b0 KS |
123 | |
124 | type_num = strtoul(from, &eptr, 0); | |
125 | if (eptr != from) { | |
126 | switch (type_num) { | |
127 | case 0: | |
128 | case 0xe: | |
129 | type = "disk"; | |
130 | break; | |
131 | case 1: | |
132 | type = "tape"; | |
133 | break; | |
134 | case 4: | |
135 | case 7: | |
136 | case 0xf: | |
137 | type = "optical"; | |
138 | break; | |
139 | case 5: | |
140 | type = "cd"; | |
141 | break; | |
142 | default: | |
143 | break; | |
144 | } | |
145 | } | |
146 | util_strscpy(to, len, type); | |
d7867b31 KS |
147 | } |
148 | ||
912541b0 KS |
149 | #define USB_DT_DEVICE 0x01 |
150 | #define USB_DT_INTERFACE 0x04 | |
d7867b31 KS |
151 | |
152 | static int dev_if_packed_info(struct udev_device *dev, char *ifs_str, size_t len) | |
153 | { | |
912541b0 KS |
154 | char *filename = NULL; |
155 | int fd; | |
156 | ssize_t size; | |
157 | unsigned char buf[18 + 65535]; | |
158 | unsigned int pos, strpos; | |
159 | struct usb_interface_descriptor { | |
160 | u_int8_t bLength; | |
161 | u_int8_t bDescriptorType; | |
162 | u_int8_t bInterfaceNumber; | |
163 | u_int8_t bAlternateSetting; | |
164 | u_int8_t bNumEndpoints; | |
165 | u_int8_t bInterfaceClass; | |
166 | u_int8_t bInterfaceSubClass; | |
167 | u_int8_t bInterfaceProtocol; | |
168 | u_int8_t iInterface; | |
169 | } __attribute__((packed)); | |
170 | int err = 0; | |
171 | ||
172 | if (asprintf(&filename, "%s/descriptors", udev_device_get_syspath(dev)) < 0) { | |
173 | err = -1; | |
174 | goto out; | |
175 | } | |
176 | fd = open(filename, O_RDONLY|O_CLOEXEC); | |
177 | if (fd < 0) { | |
178 | fprintf(stderr, "error opening USB device 'descriptors' file\n"); | |
179 | err = -1; | |
180 | goto out; | |
181 | } | |
182 | size = read(fd, buf, sizeof(buf)); | |
183 | close(fd); | |
184 | if (size < 18 || size == sizeof(buf)) { | |
185 | err = -1; | |
186 | goto out; | |
187 | } | |
188 | ||
189 | pos = 0; | |
190 | strpos = 0; | |
191 | ifs_str[0] = '\0'; | |
192 | while (pos < sizeof(buf) && strpos+7 < len-2) { | |
193 | struct usb_interface_descriptor *desc; | |
194 | char if_str[8]; | |
195 | ||
196 | desc = (struct usb_interface_descriptor *) &buf[pos]; | |
197 | if (desc->bLength < 3) | |
198 | break; | |
199 | pos += desc->bLength; | |
200 | ||
201 | if (desc->bDescriptorType != USB_DT_INTERFACE) | |
202 | continue; | |
203 | ||
204 | if (snprintf(if_str, 8, ":%02x%02x%02x", | |
205 | desc->bInterfaceClass, | |
206 | desc->bInterfaceSubClass, | |
207 | desc->bInterfaceProtocol) != 7) | |
208 | continue; | |
209 | ||
210 | if (strstr(ifs_str, if_str) != NULL) | |
211 | continue; | |
212 | ||
213 | memcpy(&ifs_str[strpos], if_str, 8), | |
214 | strpos += 7; | |
215 | } | |
216 | if (strpos > 0) { | |
217 | ifs_str[strpos++] = ':'; | |
218 | ifs_str[strpos++] = '\0'; | |
219 | } | |
d7867b31 | 220 | out: |
912541b0 KS |
221 | free(filename); |
222 | return err; | |
d7867b31 KS |
223 | } |
224 | ||
225 | /* | |
226 | * A unique USB identification is generated like this: | |
227 | * | |
228 | * 1.) Get the USB device type from InterfaceClass and InterfaceSubClass | |
229 | * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC' | |
230 | * use the SCSI vendor and model as USB-Vendor and USB-model. | |
231 | * 3.) Otherwise use the USB manufacturer and product as | |
232 | * USB-Vendor and USB-model. Any non-printable characters | |
233 | * in those strings will be skipped; a slash '/' will be converted | |
234 | * into a full stop '.'. | |
235 | * 4.) If that fails, too, we will use idVendor and idProduct | |
236 | * as USB-Vendor and USB-model. | |
237 | * 5.) The USB identification is the USB-vendor and USB-model | |
238 | * string concatenated with an underscore '_'. | |
239 | * 6.) If the device supplies a serial number, this number | |
240 | * is concatenated with the identification with an underscore '_'. | |
241 | */ | |
e216e514 | 242 | static int builtin_usb_id(struct udev_device *dev, int argc, char *argv[], bool test) |
d7867b31 | 243 | { |
912541b0 KS |
244 | char vendor_str[64]; |
245 | char vendor_str_enc[256]; | |
246 | const char *vendor_id; | |
247 | char model_str[64]; | |
248 | char model_str_enc[256]; | |
249 | const char *product_id; | |
250 | char serial_str[UTIL_NAME_SIZE]; | |
251 | char packed_if_str[UTIL_NAME_SIZE]; | |
252 | char revision_str[64]; | |
253 | char type_str[64]; | |
254 | char instance_str[64]; | |
255 | const char *ifnum = NULL; | |
256 | const char *driver = NULL; | |
257 | char serial[256]; | |
258 | ||
912541b0 KS |
259 | struct udev_device *dev_interface = NULL; |
260 | struct udev_device *dev_usb = NULL; | |
261 | const char *if_class, *if_subclass; | |
262 | int if_class_num; | |
263 | int protocol = 0; | |
264 | size_t l; | |
265 | char *s; | |
266 | ||
267 | vendor_str[0] = '\0'; | |
268 | model_str[0] = '\0'; | |
269 | serial_str[0] = '\0'; | |
270 | packed_if_str[0] = '\0'; | |
271 | revision_str[0] = '\0'; | |
272 | type_str[0] = '\0'; | |
273 | instance_str[0] = '\0'; | |
274 | ||
912541b0 KS |
275 | /* shortcut, if we are called directly for a "usb_device" type */ |
276 | if (udev_device_get_devtype(dev) != NULL && strcmp(udev_device_get_devtype(dev), "usb_device") == 0) { | |
277 | dev_if_packed_info(dev, packed_if_str, sizeof(packed_if_str)); | |
278 | dev_usb = dev; | |
279 | goto fallback; | |
280 | } | |
281 | ||
282 | /* usb interface directory */ | |
283 | dev_interface = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_interface"); | |
284 | if (dev_interface == NULL) { | |
baa30fbc | 285 | log_debug("unable to access usb_interface device of '%s'\n", |
912541b0 KS |
286 | udev_device_get_syspath(dev)); |
287 | return EXIT_FAILURE; | |
288 | } | |
289 | ||
290 | ifnum = udev_device_get_sysattr_value(dev_interface, "bInterfaceNumber"); | |
291 | driver = udev_device_get_sysattr_value(dev_interface, "driver"); | |
292 | ||
293 | if_class = udev_device_get_sysattr_value(dev_interface, "bInterfaceClass"); | |
294 | if (!if_class) { | |
baa30fbc | 295 | log_debug("%s: cannot get bInterfaceClass attribute\n", |
912541b0 KS |
296 | udev_device_get_sysname(dev)); |
297 | return EXIT_FAILURE; | |
298 | } | |
299 | ||
300 | if_class_num = strtoul(if_class, NULL, 16); | |
301 | if (if_class_num == 8) { | |
302 | /* mass storage */ | |
303 | if_subclass = udev_device_get_sysattr_value(dev_interface, "bInterfaceSubClass"); | |
304 | if (if_subclass != NULL) | |
305 | protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1); | |
306 | } else { | |
307 | set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1); | |
308 | } | |
309 | ||
baa30fbc | 310 | log_debug("%s: if_class %d protocol %d\n", |
912541b0 KS |
311 | udev_device_get_syspath(dev_interface), if_class_num, protocol); |
312 | ||
313 | /* usb device directory */ | |
314 | dev_usb = udev_device_get_parent_with_subsystem_devtype(dev_interface, "usb", "usb_device"); | |
315 | if (!dev_usb) { | |
baa30fbc | 316 | log_debug("unable to find parent 'usb' device of '%s'\n", |
912541b0 KS |
317 | udev_device_get_syspath(dev)); |
318 | return EXIT_FAILURE; | |
319 | } | |
320 | ||
321 | /* all interfaces of the device in a single string */ | |
322 | dev_if_packed_info(dev_usb, packed_if_str, sizeof(packed_if_str)); | |
323 | ||
324 | /* mass storage : SCSI or ATAPI */ | |
325 | if ((protocol == 6 || protocol == 2)) { | |
326 | struct udev_device *dev_scsi; | |
327 | const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev; | |
328 | int host, bus, target, lun; | |
329 | ||
330 | /* get scsi device */ | |
331 | dev_scsi = udev_device_get_parent_with_subsystem_devtype(dev, "scsi", "scsi_device"); | |
332 | if (dev_scsi == NULL) { | |
baa30fbc | 333 | log_debug("unable to find parent 'scsi' device of '%s'\n", |
912541b0 KS |
334 | udev_device_get_syspath(dev)); |
335 | goto fallback; | |
336 | } | |
337 | if (sscanf(udev_device_get_sysname(dev_scsi), "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) { | |
baa30fbc | 338 | log_debug("invalid scsi device '%s'\n", udev_device_get_sysname(dev_scsi)); |
912541b0 KS |
339 | goto fallback; |
340 | } | |
341 | ||
342 | /* Generic SPC-2 device */ | |
343 | scsi_vendor = udev_device_get_sysattr_value(dev_scsi, "vendor"); | |
344 | if (!scsi_vendor) { | |
baa30fbc | 345 | log_debug("%s: cannot get SCSI vendor attribute\n", |
912541b0 KS |
346 | udev_device_get_sysname(dev_scsi)); |
347 | goto fallback; | |
348 | } | |
349 | udev_util_encode_string(scsi_vendor, vendor_str_enc, sizeof(vendor_str_enc)); | |
350 | util_replace_whitespace(scsi_vendor, vendor_str, sizeof(vendor_str)-1); | |
351 | util_replace_chars(vendor_str, NULL); | |
352 | ||
353 | scsi_model = udev_device_get_sysattr_value(dev_scsi, "model"); | |
354 | if (!scsi_model) { | |
baa30fbc | 355 | log_debug("%s: cannot get SCSI model attribute\n", |
912541b0 KS |
356 | udev_device_get_sysname(dev_scsi)); |
357 | goto fallback; | |
358 | } | |
359 | udev_util_encode_string(scsi_model, model_str_enc, sizeof(model_str_enc)); | |
360 | util_replace_whitespace(scsi_model, model_str, sizeof(model_str)-1); | |
361 | util_replace_chars(model_str, NULL); | |
362 | ||
363 | scsi_type = udev_device_get_sysattr_value(dev_scsi, "type"); | |
364 | if (!scsi_type) { | |
baa30fbc | 365 | log_debug("%s: cannot get SCSI type attribute\n", |
912541b0 KS |
366 | udev_device_get_sysname(dev_scsi)); |
367 | goto fallback; | |
368 | } | |
369 | set_scsi_type(type_str, scsi_type, sizeof(type_str)-1); | |
370 | ||
371 | scsi_rev = udev_device_get_sysattr_value(dev_scsi, "rev"); | |
372 | if (!scsi_rev) { | |
baa30fbc | 373 | log_debug("%s: cannot get SCSI revision attribute\n", |
912541b0 KS |
374 | udev_device_get_sysname(dev_scsi)); |
375 | goto fallback; | |
376 | } | |
377 | util_replace_whitespace(scsi_rev, revision_str, sizeof(revision_str)-1); | |
378 | util_replace_chars(revision_str, NULL); | |
379 | ||
380 | /* | |
381 | * some broken devices have the same identifiers | |
382 | * for all luns, export the target:lun number | |
383 | */ | |
384 | sprintf(instance_str, "%d:%d", target, lun); | |
385 | } | |
d7867b31 KS |
386 | |
387 | fallback: | |
912541b0 KS |
388 | vendor_id = udev_device_get_sysattr_value(dev_usb, "idVendor"); |
389 | product_id = udev_device_get_sysattr_value(dev_usb, "idProduct"); | |
390 | ||
391 | /* fallback to USB vendor & device */ | |
392 | if (vendor_str[0] == '\0') { | |
393 | const char *usb_vendor = NULL; | |
394 | ||
395 | usb_vendor = udev_device_get_sysattr_value(dev_usb, "manufacturer"); | |
396 | if (!usb_vendor) | |
397 | usb_vendor = vendor_id; | |
398 | if (!usb_vendor) { | |
baa30fbc | 399 | log_debug("No USB vendor information available\n"); |
912541b0 KS |
400 | return EXIT_FAILURE; |
401 | } | |
402 | udev_util_encode_string(usb_vendor, vendor_str_enc, sizeof(vendor_str_enc)); | |
403 | util_replace_whitespace(usb_vendor, vendor_str, sizeof(vendor_str)-1); | |
404 | util_replace_chars(vendor_str, NULL); | |
405 | } | |
406 | ||
407 | if (model_str[0] == '\0') { | |
408 | const char *usb_model = NULL; | |
409 | ||
410 | usb_model = udev_device_get_sysattr_value(dev_usb, "product"); | |
411 | if (!usb_model) | |
412 | usb_model = product_id; | |
baa30fbc | 413 | if (!usb_model) |
912541b0 | 414 | return EXIT_FAILURE; |
912541b0 KS |
415 | udev_util_encode_string(usb_model, model_str_enc, sizeof(model_str_enc)); |
416 | util_replace_whitespace(usb_model, model_str, sizeof(model_str)-1); | |
417 | util_replace_chars(model_str, NULL); | |
418 | } | |
419 | ||
420 | if (revision_str[0] == '\0') { | |
421 | const char *usb_rev; | |
422 | ||
423 | usb_rev = udev_device_get_sysattr_value(dev_usb, "bcdDevice"); | |
424 | if (usb_rev) { | |
425 | util_replace_whitespace(usb_rev, revision_str, sizeof(revision_str)-1); | |
426 | util_replace_chars(revision_str, NULL); | |
427 | } | |
428 | } | |
429 | ||
430 | if (serial_str[0] == '\0') { | |
431 | const char *usb_serial; | |
432 | ||
433 | usb_serial = udev_device_get_sysattr_value(dev_usb, "serial"); | |
434 | if (usb_serial) { | |
435 | util_replace_whitespace(usb_serial, serial_str, sizeof(serial_str)-1); | |
436 | util_replace_chars(serial_str, NULL); | |
437 | } | |
438 | } | |
439 | ||
440 | s = serial; | |
441 | l = util_strpcpyl(&s, sizeof(serial), vendor_str, "_", model_str, NULL); | |
442 | if (serial_str[0] != '\0') | |
443 | l = util_strpcpyl(&s, l, "_", serial_str, NULL); | |
444 | ||
445 | if (instance_str[0] != '\0') | |
446 | util_strpcpyl(&s, l, "-", instance_str, NULL); | |
447 | ||
448 | udev_builtin_add_property(dev, test, "ID_VENDOR", vendor_str); | |
449 | udev_builtin_add_property(dev, test, "ID_VENDOR_ENC", vendor_str_enc); | |
450 | udev_builtin_add_property(dev, test, "ID_VENDOR_ID", vendor_id); | |
451 | udev_builtin_add_property(dev, test, "ID_MODEL", model_str); | |
452 | udev_builtin_add_property(dev, test, "ID_MODEL_ENC", model_str_enc); | |
453 | udev_builtin_add_property(dev, test, "ID_MODEL_ID", product_id); | |
454 | udev_builtin_add_property(dev, test, "ID_REVISION", revision_str); | |
455 | udev_builtin_add_property(dev, test, "ID_SERIAL", serial); | |
456 | if (serial_str[0] != '\0') | |
457 | udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str); | |
458 | if (type_str[0] != '\0') | |
459 | udev_builtin_add_property(dev, test, "ID_TYPE", type_str); | |
460 | if (instance_str[0] != '\0') | |
461 | udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str); | |
462 | udev_builtin_add_property(dev, test, "ID_BUS", "usb"); | |
463 | if (packed_if_str[0] != '\0') | |
464 | udev_builtin_add_property(dev, test, "ID_USB_INTERFACES", packed_if_str); | |
465 | if (ifnum != NULL) | |
466 | udev_builtin_add_property(dev, test, "ID_USB_INTERFACE_NUM", ifnum); | |
467 | if (driver != NULL) | |
468 | udev_builtin_add_property(dev, test, "ID_USB_DRIVER", driver); | |
469 | return EXIT_SUCCESS; | |
d7867b31 KS |
470 | } |
471 | ||
472 | const struct udev_builtin udev_builtin_usb_id = { | |
912541b0 KS |
473 | .name = "usb_id", |
474 | .cmd = builtin_usb_id, | |
475 | .help = "usb device properties", | |
476 | .run_once = true, | |
d7867b31 | 477 | }; |