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