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