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