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