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