chiark / gitweb /
b7d67958c9a1bd6d4edec4902da1af95b3d30344
[elogind.git] / extras / usb_id / usb_id.c
1 /*
2  * usb_id.c
3  *
4  * Identify an USB (block) device
5  *
6  * Copyright (c) 2005 SUSE Linux Products GmbH, Germany
7  *
8  * Author:
9  *      Hannes Reinecke <hare@suse.de>
10  *
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.
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
24 #include <../../libsysfs/sysfs/libsysfs.h>
25 #include "../../udev_utils.h"
26 #include "../../logging.h"
27
28 #define MAX_NAME_LEN                    72
29 #define MAX_SERIAL_LEN                  256
30 #define BLKGETSIZE64 _IOR(0x12,114,size_t)
31
32 #ifdef USE_LOG
33 void log_message(int priority, const char *format, ...)
34 {
35         va_list args;
36         static int udev_log = -1;
37
38         if (udev_log == -1) {
39                 const char *value;
40
41                 value = getenv("UDEV_LOG");
42                 if (value)
43                         udev_log = log_priority(value);
44                 else
45                         udev_log = LOG_ERR;
46         }
47
48         if (priority > udev_log)
49                 return;
50
51         va_start(args, format);
52         vsyslog(priority, format, args);
53         va_end(args);
54 }
55 #endif
56
57 char sysfs_mnt_path[SYSFS_PATH_MAX];
58 static char vendor_str[64];
59 static char model_str[64];
60 static char serial_str[MAX_SERIAL_LEN];
61 static char revision_str[16];
62 static char type_str[16];
63
64 static int use_usb_info;
65 static int use_num_info;
66 static int export;
67 static int debug;
68
69 static void set_str(char *to, const char *from, size_t count)
70 {
71         size_t i, j, len;
72
73         /* strip trailing whitespace */
74         len = strnlen(from, count);
75         while (len && isspace(from[len-1]))
76                 len--;
77
78         /* strip leading whitespace */
79         i = 0;
80         while (isspace(from[i]) && (i < len))
81                 i++;
82
83         j = 0;
84         while (i < len) {
85                 /* substitute multiple whitespace */
86                 if (isspace(from[i])) {
87                         while (isspace(from[i]))
88                                 i++;
89                         to[j++] = '_';
90                 }
91                 /* Replace '/' with '.' */
92                 if (from[i] == '/') {
93                         to[j++] = '.';
94                         i++;
95                         continue;
96                 }
97                 /* skip non-printable chars */
98                 if (!isalnum(from[i]) && !ispunct(from[i])) {
99                         i++;
100                         continue;
101                 }
102                 to[j++] = from[i++];
103         }
104         to[j] = '\0';
105 }
106
107 /*
108  * set_usb_iftype
109  *
110  * Set the type based on the USB interface class
111  */
112 static void set_usb_iftype(char *to, const char *from, size_t len)
113 {
114         int type_num;
115         char *eptr;
116         char *type = "generic";
117
118         type_num = strtoul(from, &eptr, 0);
119         if (eptr != from) {
120                 switch (type_num) {
121                 case 1:
122                         type = "audio";
123                         break;
124                 case 3:
125                         type = "hid";
126                         break;
127                 case 7:
128                         type = "printer";
129                         break;
130                 case 8:
131                         type = "disk";
132                         break;
133                 case 2: /* CDC-Control */
134                 case 5: /* Physical */
135                 case 6: /* Image */
136                 case 9: /* HUB */
137                 case 0x0a: /* CDC-Data */
138                 case 0x0b: /* Chip/Smart Card */
139                 case 0x0d: /* Content Security */
140                 case 0x0e: /* Video */
141                 case 0xdc: /* Diagnostic Device */
142                 case 0xe0: /* Wireless Controller */
143                 case 0xf2: /* Application-specific */
144                 case 0xff: /* Vendor-specific */
145                 default:
146                         break;
147                 }
148         }
149         strncpy(to, type, len);
150         to[len-1] = '\0';
151 }
152
153 /*
154  * set_usb_ifsybtype
155  *
156  * Set the type base on the interfaceSubClass.
157  * Valid for Mass-Storage devices (type 8) only.
158  */
159 static int set_usb_ifsubtype(char *to, const char *from, size_t len)
160 {
161         int type_num = 0;
162         char *eptr;
163         char *type = "generic";
164
165         type_num = strtoul(from, &eptr, 0);
166         if (eptr != from) {
167                 switch (type_num) {
168                 case 2:
169                         type = "cd";
170                         break;
171                 case 3:
172                         type = "tape";
173                         break;
174                 case 4: /* UFI */
175                 case 5: /* SFF-8070i */
176                         type = "floppy";
177                         break;
178                 case 1: /* RBC devices */
179                 case 6: /* Transparent SPC-2 devices */
180                         type = "disk";
181                         break;
182                 default:
183                         break;
184                 }
185         }
186         strncpy(to, type, len);
187         to[len-1] = '\0';
188
189         return type_num;
190 }
191
192 static void set_scsi_type(char *to, const char *from, int count)
193 {
194         int type_num;
195         char *eptr;
196
197         type_num = strtoul(from, &eptr, 0);
198         if (eptr != from) {
199                 switch (type_num) {
200                 case 0:
201                         sprintf(to, "disk");
202                         break;
203                 case 1:
204                         sprintf(to, "tape");
205                         break;
206                 case 4:
207                         sprintf(to, "optical");
208                         break;
209                 case 5:
210                         sprintf(to, "cd");
211                         break;
212                 case 7:
213                         sprintf(to, "optical");
214                         break;
215                 case 0xe:
216                         sprintf(to, "disk");
217                         break;
218                 case 0xf:
219                         sprintf(to, "optical");
220                         break;
221                 default:
222                         sprintf(to, "generic");
223                         break;
224                 }
225         } else {
226                 sprintf(to, "generic");
227         }
228 }
229
230 /*
231  * A unique USB identification is generated like this:
232  *
233  * 1.) Get the USB device type from DeviceClass, InterfaceClass
234  *     and InterfaceSubClass
235  * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC'
236  *     use the SCSI vendor and model as USB-Vendor and USB-model.
237  * 3.) Otherwise use the USB manufacturer and product as
238  *     USB-Vendor and USB-model. Any non-printable characters
239  *     in those strings will be skipped; a slash '/' will be converted
240  *     into a full stop '.'.
241  * 4.) If that fails, too, we will use idVendor and idProduct
242  *     as USB-Vendor and USB-model.
243  * 5.) The USB identification is the USB-vendor and USB-model
244  *     string concatenated with an underscore '_'.
245  * 6.) If the device supplies a serial number, this number
246  *     is concatenated with the identification with an underscore '_'.
247  */
248 static int usb_id(const char *target_path)
249 {
250         struct sysfs_class_device *class_dev; /* of target_path */
251         struct sysfs_class_device *class_dev_parent; /* for partitions */
252         struct sysfs_device *scsi_dev; /* the scsi_device */
253         struct sysfs_device *target_dev;
254         struct sysfs_device *host_dev, *interface_dev, *usb_dev;
255         struct sysfs_attribute *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev;
256         struct sysfs_attribute *usb_model = NULL, *usb_vendor = NULL, *usb_rev, *usb_serial;
257         struct sysfs_attribute *if_class, *if_subclass;
258         int if_class_num;
259         int protocol = 0;
260
261         class_dev = sysfs_open_class_device_path(target_path);
262         if (!class_dev) {
263                 info("open class %s failed: %s", target_path, strerror(errno));
264                 return 1;
265         }
266         class_dev_parent = sysfs_get_classdev_parent(class_dev);
267         if (class_dev_parent) {
268                 scsi_dev = sysfs_get_classdev_device(class_dev_parent);
269         } else {
270                 scsi_dev = sysfs_get_classdev_device(class_dev);
271         }
272
273         /*
274          * The close of scsi_dev will close class_dev or class_dev_parent.
275          */
276
277         /*
278          * We assume we are called after the device is completely ready,
279          * so we don't have to loop here like udev. (And we are usually
280          * called via udev.)
281          */
282         if (!scsi_dev) {
283                 /*
284                  * errno is not set if we can't find the device link, so
285                  * don't print it out here.
286                  */
287                 info("Cannot find sysfs device associated with %s", target_path);
288                 return 1;
289         }
290
291         /*
292          * Allow only scsi devices.
293          *
294          * Other block devices can support SG IO, but only ide-cd does, so
295          * for now, don't bother with anything else.
296          */
297         if (strcmp(scsi_dev->bus, "scsi") != 0) {
298                 info("%s is not a scsi device", target_path);
299                 return 1;
300         }
301
302         /* target directory */
303         target_dev = sysfs_get_device_parent(scsi_dev);
304         /* host directory */
305         host_dev = sysfs_get_device_parent(target_dev);
306         /* usb interface directory */
307         interface_dev = sysfs_get_device_parent(host_dev);
308         /* usb device directory */
309         usb_dev = sysfs_get_device_parent(interface_dev);
310
311         if (strcmp(interface_dev->bus, "usb") != 0) {
312                 info("%s is not an usb device", target_path);
313                 return 1;
314         }
315
316         if_class = sysfs_get_device_attr(interface_dev, "bInterfaceClass");
317         if (!if_class) {
318                 info("%s: cannot get bInterfaceClass attribute", interface_dev->name);
319                 return 1;
320         }
321         if_class_num = strtoul(if_class->value, NULL, 16);
322         if (if_class_num != 8) {
323                 set_usb_iftype(type_str, if_class->value, sizeof(type_str) - 1);
324                 protocol = 0;
325         } else {
326                 if_subclass = sysfs_get_device_attr(interface_dev, 
327                                                     "bInterfaceSubClass");
328                 protocol = set_usb_ifsubtype(type_str, if_subclass->value, 
329                                              sizeof(type_str) -1 );
330         }
331
332         if (!use_usb_info && protocol == 6) {
333                 /* Generic SPC-2 device */
334                 scsi_vendor = sysfs_get_device_attr(scsi_dev, "vendor");
335                 if (!scsi_vendor) {
336                         info("%s: cannot get SCSI vendor attribute", scsi_dev->name);
337                         return 1;
338                 }
339                 set_str(vendor_str, scsi_vendor->value, sizeof(vendor_str)-1);
340
341                 scsi_model = sysfs_get_device_attr(scsi_dev, "model");
342                 if (!scsi_model) {
343                         info("%s: cannot get SCSI model attribute", scsi_dev->name);
344                         return 1;
345                 }
346                 set_str(model_str, scsi_model->value, sizeof(model_str)-1);
347
348                 scsi_type = sysfs_get_device_attr(scsi_dev, "type");
349                 if (!scsi_type) {
350                         info("%s: cannot get SCSI type attribute", scsi_dev->name);
351                         return 1;
352                 }
353                 set_scsi_type(type_str, scsi_type->value, sizeof(type_str)-1);
354
355                 scsi_rev = sysfs_get_device_attr(scsi_dev, "rev");
356                 if (!scsi_rev) {
357                         info("%s: cannot get SCSI revision attribute", scsi_dev->name);
358                         return 1;
359                 }
360                 set_str(revision_str, scsi_rev->value, sizeof(revision_str)-1);
361
362         }
363
364         /* Fallback to USB vendor & device */
365         if (vendor_str[0] == '\0') {
366                 if (!use_num_info)
367                         if (!(usb_vendor = sysfs_get_device_attr(usb_dev, "manufacturer")))
368                                 dbg("No USB vendor string found, using idVendor");
369
370                 if (!usb_vendor) {
371                         if (!(usb_vendor = sysfs_get_device_attr(usb_dev, "idVendor"))) {
372                                 dbg("No USB vendor information available\n");
373                                 sprintf(vendor_str,"0000");
374                         }
375                 }
376                 set_str(vendor_str,usb_vendor->value, sizeof(vendor_str) - 1);
377         }
378         
379         if (model_str[0] == '\0') {
380                 if (!use_num_info)
381                         if (!(usb_model = sysfs_get_device_attr(usb_dev, "product")))
382                                 dbg("No USB model string found, using idProduct");
383                 
384                 if (!usb_model) {
385                         if (!(usb_model = sysfs_get_device_attr(usb_dev, "idProduct"))) {
386                                 dbg("No USB model information available\n");
387                                 sprintf(model_str,"0000");
388                         }
389                 }
390                 set_str(model_str, usb_model->value, sizeof(model_str) - 1);
391         }
392
393         if (revision_str[0] == '\0') {
394                 usb_rev = sysfs_get_device_attr(usb_dev, "bcdDevice");
395                 if (usb_rev) {
396                         set_str(revision_str, usb_rev->value, 
397                                 sizeof(revision_str) - 1);
398                 }
399         }
400
401         if (serial_str[0] == '\0') {
402                 usb_serial = sysfs_get_device_attr(usb_dev, "serial");
403                 if (usb_serial) {
404                         set_str(serial_str, usb_serial->value,
405                                 sizeof(serial_str) - 1);
406                 }
407         }
408         return 0;
409 }
410
411 int main(int argc, char **argv)
412 {
413         int retval;
414         char *devpath;
415         char target_path[MAX_NAME_LEN];
416         int option;
417
418         dbg("argc is %d", argc);
419         if (sysfs_get_mnt_path(sysfs_mnt_path, MAX_NAME_LEN)) {
420                 info("sysfs_get_mnt_path failed: %s",
421                         strerror(errno));
422                 exit(1);
423         }
424
425         while ((option = getopt(argc, argv, "dnux")) != -1 ) {
426                 if (optarg)
427                         dbg("option '%c' arg '%s'", option, optarg);
428                 else
429                         dbg("option '%c'", option);
430
431                 switch (option) {
432                 case 'd':
433                         debug = 1;
434                         break;
435                 case 'n':
436                         use_num_info=1;
437                         use_usb_info=1;
438                         break;
439                 case 'u':
440                         use_usb_info=1;
441                         break;
442                 case 'x':
443                         export=1;
444                         break;
445                 default:
446                         info("Unknown or bad option '%c' (0x%x)", option, option);
447                         retval = 1;
448                         break;
449                 }
450         }
451
452         devpath = getenv("DEVPATH");
453         if (devpath) {
454                 strncpy(target_path, sysfs_mnt_path, MAX_NAME_LEN);
455                 strncat(target_path, devpath, MAX_NAME_LEN);
456         } else {
457                 if (optind == argc) {
458                         fprintf(stderr, "No device specified\n");
459                         exit(1);
460                 }
461                 devpath = argv[optind];
462                 strncpy(target_path, devpath, MAX_NAME_LEN);
463         }
464
465         retval = usb_id(target_path);
466
467         if (retval == 0) {
468                 if (export) {
469                         printf("ID_VENDOR=%s\n", vendor_str);
470                         printf("ID_MODEL=%s\n", model_str);
471                         printf("ID_REVISION=%s\n", revision_str);
472                         if (serial_str[0] == '\0') {
473                                 printf("ID_SERIAL=%s_%s\n", 
474                                        vendor_str, model_str);
475                         } else {
476                                 printf("ID_SERIAL=%s_%s_%s\n", 
477                                        vendor_str, model_str, serial_str);
478                         }
479                         printf("ID_TYPE=%s\n", type_str);
480                         printf("ID_BUS=usb\n");
481                 } else {
482                         if (serial_str[0] == '\0') {
483                                 printf("%s_%s\n", 
484                                        vendor_str, model_str);
485                         } else {
486                                 printf("%s_%s_%s\n", 
487                                        vendor_str, model_str, serial_str);
488                         }
489                 }
490         }
491         exit(retval);
492 }