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#include <getopt.h>
24
25#include "../../udev.h"
26
27#define MAX_PATH_LEN			512
28#define MAX_SERIAL_LEN			256
29#define BLKGETSIZE64 _IOR(0x12,114,size_t)
30
31#ifdef USE_LOG
32void log_message(int priority, const char *format, ...)
33{
34	va_list args;
35	static int udev_log = -1;
36
37	if (udev_log == -1) {
38		const char *value;
39
40		value = getenv("UDEV_LOG");
41		if (value)
42			udev_log = log_priority(value);
43		else
44			udev_log = LOG_ERR;
45	}
46
47	if (priority > udev_log)
48		return;
49
50	va_start(args, format);
51	vsyslog(priority, format, args);
52	va_end(args);
53}
54#endif
55
56static char vendor_str[64];
57static char model_str[64];
58static char serial_str[MAX_SERIAL_LEN];
59static char revision_str[64];
60static char type_str[64];
61static char instance_str[64];
62
63static int use_usb_info;
64static int use_num_info;
65
66static void set_str(char *to, const char *from, size_t count)
67{
68	size_t i, j, len;
69
70	/* strip trailing whitespace */
71	len = strnlen(from, count);
72	while (len && isspace(from[len-1]))
73		len--;
74
75	/* strip leading whitespace */
76	i = 0;
77	while (isspace(from[i]) && (i < len))
78		i++;
79
80	j = 0;
81	while (i < len) {
82		/* substitute multiple whitespace */
83		if (isspace(from[i])) {
84			while (isspace(from[i]))
85				i++;
86			to[j++] = '_';
87		}
88		/* Replace '/' with '.' */
89		if (from[i] == '/') {
90			to[j++] = '.';
91			i++;
92			continue;
93		}
94		/* skip non-printable chars */
95		if (!isalnum(from[i]) && !ispunct(from[i])) {
96			i++;
97			continue;
98		}
99		to[j++] = from[i++];
100	}
101	to[j] = '\0';
102}
103
104static void set_usb_iftype(char *to, int if_class_num, size_t len)
105{
106	char *type = "generic";
107
108	switch (if_class_num) {
109	case 1:
110		type = "audio";
111		break;
112	case 3:
113		type = "hid";
114		break;
115	case 7:
116		type = "printer";
117		break;
118	case 8:
119		type = "storage";
120		break;
121	case 2: /* CDC-Control */
122	case 5: /* Physical */
123	case 6: /* Image */
124	case 9: /* HUB */
125	case 0x0a: /* CDC-Data */
126	case 0x0b: /* Chip/Smart Card */
127	case 0x0d: /* Content Security */
128	case 0x0e: /* Video */
129	case 0xdc: /* Diagnostic Device */
130	case 0xe0: /* Wireless Controller */
131	case 0xf2: /* Application-specific */
132	case 0xff: /* Vendor-specific */
133		break;
134	default:
135		break;
136	}
137	strncpy(to, type, len);
138	to[len-1] = '\0';
139}
140
141static int set_usb_mass_storage_ifsubtype(char *to, const char *from, size_t len)
142{
143	int type_num = 0;
144	char *eptr;
145	char *type = "generic";
146
147	type_num = strtoul(from, &eptr, 0);
148	if (eptr != from) {
149		switch (type_num) {
150		case 2:
151			type = "cd";
152			break;
153		case 3:
154			type = "tape";
155			break;
156		case 4: /* UFI */
157		case 5: /* SFF-8070i */
158			type = "floppy";
159			break;
160		case 1: /* RBC devices */
161		case 6: /* Transparent SPC-2 devices */
162			type = "disk";
163			break;
164		default:
165			break;
166		}
167	}
168	strlcpy(to, type, len);
169
170	return type_num;
171}
172
173static void set_scsi_type(char *to, const char *from, size_t len)
174{
175	int type_num;
176	char *eptr;
177	char *type = "generic";
178
179	type_num = strtoul(from, &eptr, 0);
180	if (eptr != from) {
181		switch (type_num) {
182		case 0:
183		case 0xe:
184			type = "disk";
185			break;
186		case 1:
187			type = "tape";
188			break;
189		case 4:
190		case 7:
191		case 0xf:
192			type = "optical";
193			break;
194		case 5:
195			type = "cd";
196			break;
197		default:
198			break;
199		}
200	}
201	strlcpy(to, type, len);
202}
203
204/*
205 * A unique USB identification is generated like this:
206 *
207 * 1.) Get the USB device type from DeviceClass, InterfaceClass
208 *     and InterfaceSubClass
209 * 2.) If the device type is 'Mass-Storage/SPC-2' or 'Mass-Storage/RBC'
210 *     use the SCSI vendor and model as USB-Vendor and USB-model.
211 * 3.) Otherwise use the USB manufacturer and product as
212 *     USB-Vendor and USB-model. Any non-printable characters
213 *     in those strings will be skipped; a slash '/' will be converted
214 *     into a full stop '.'.
215 * 4.) If that fails, too, we will use idVendor and idProduct
216 *     as USB-Vendor and USB-model.
217 * 5.) The USB identification is the USB-vendor and USB-model
218 *     string concatenated with an underscore '_'.
219 * 6.) If the device supplies a serial number, this number
220 *     is concatenated with the identification with an underscore '_'.
221 */
222static int usb_id(const char *devpath)
223{
224	struct sysfs_device *dev;
225	struct sysfs_device *dev_interface;
226	struct sysfs_device *dev_usb;
227	const char *scsi_model, *scsi_vendor, *scsi_type, *scsi_rev;
228	const char *usb_model = NULL, *usb_vendor = NULL, *usb_rev, *usb_serial;
229	const char *if_class, *if_subclass;
230	int if_class_num;
231	int protocol = 0;
232
233	dbg("devpath %s\n", devpath);
234
235	/* get all usb specific information: dev_interface, if_class, dev_usb */
236	dev = sysfs_device_get(devpath);
237	if (dev == NULL) {
238		err("unable to access '%s'", devpath);
239		return 1;
240	}
241
242	/* usb interface directory */
243	dev_interface = sysfs_device_get_parent_with_subsystem(dev, "usb");
244	if (dev_interface == NULL) {
245		info("unable to access usb_interface device of '%s'", devpath);
246		return 1;
247	}
248
249	if_class = sysfs_attr_get_value(dev_interface->devpath, "bInterfaceClass");
250	if (!if_class) {
251		info("%s: cannot get bInterfaceClass attribute", dev_interface->kernel);
252		return 1;
253	}
254	if_class_num = strtoul(if_class, NULL, 16);
255	if (if_class_num == 8) {
256		if_subclass = sysfs_attr_get_value(dev_interface->devpath, "bInterfaceSubClass");
257		if (if_subclass != NULL)
258			protocol = set_usb_mass_storage_ifsubtype(type_str, if_subclass, sizeof(type_str)-1);
259	} else
260		set_usb_iftype(type_str, if_class_num, sizeof(type_str)-1);
261
262	info("%s: if_class %d protocol %d\n", dev_interface->devpath, if_class_num, protocol);
263
264	/* usb device directory */
265	dev_usb = sysfs_device_get_parent_with_subsystem(dev_interface, "usb");
266	if (!dev_usb) {
267		info("unable to find parent 'usb' device of '%s'", devpath);
268		return 1;
269	}
270
271	/* mass storage */
272	if (protocol == 6 && !use_usb_info) {
273		struct sysfs_device *dev_scsi;
274		int host, bus, target, lun;
275
276		/* get scsi device */
277		dev_scsi = sysfs_device_get_parent_with_subsystem(dev, "scsi");
278		if (dev_scsi == NULL) {
279			info("unable to find parent 'scsi' device of '%s'", devpath);
280			goto fallback;
281		}
282		if (sscanf(dev_scsi->kernel, "%d:%d:%d:%d", &host, &bus, &target, &lun) != 4) {
283			info("invalid scsi device '%s'", dev_scsi->kernel);
284			goto fallback;
285		}
286
287		/* Generic SPC-2 device */
288		scsi_vendor = sysfs_attr_get_value(dev_scsi->devpath, "vendor");
289		if (!scsi_vendor) {
290			info("%s: cannot get SCSI vendor attribute", dev_scsi->kernel);
291			goto fallback;
292		}
293		set_str(vendor_str, scsi_vendor, sizeof(vendor_str)-1);
294
295		scsi_model = sysfs_attr_get_value(dev_scsi->devpath, "model");
296		if (!scsi_model) {
297			info("%s: cannot get SCSI model attribute", dev_scsi->kernel);
298			goto fallback;
299		}
300		set_str(model_str, scsi_model, sizeof(model_str)-1);
301
302		scsi_type = sysfs_attr_get_value(dev_scsi->devpath, "type");
303		if (!scsi_type) {
304			info("%s: cannot get SCSI type attribute", dev_scsi->kernel);
305			goto fallback;
306		}
307		set_scsi_type(type_str, scsi_type, sizeof(type_str)-1);
308
309		scsi_rev = sysfs_attr_get_value(dev_scsi->devpath, "rev");
310		if (!scsi_rev) {
311			info("%s: cannot get SCSI revision attribute", dev_scsi->kernel);
312			goto fallback;
313		}
314		set_str(revision_str, scsi_rev, sizeof(revision_str)-1);
315
316		/*
317		 * some broken devices have the same identifiers
318		 * for all luns, export the target:lun number
319		 */
320		sprintf(instance_str, "%d:%d", target, lun);
321	}
322
323fallback:
324	/* Fallback to USB vendor & device */
325	if (vendor_str[0] == '\0') {
326		if (!use_num_info)
327			if (!(usb_vendor = sysfs_attr_get_value(dev_usb->devpath, "manufacturer")))
328				dbg("No USB vendor string found, using idVendor");
329
330		if (!usb_vendor) {
331			if (!(usb_vendor = sysfs_attr_get_value(dev_usb->devpath, "idVendor"))) {
332				dbg("No USB vendor information available\n");
333				sprintf(vendor_str,"0000");
334			}
335		}
336		set_str(vendor_str,usb_vendor, sizeof(vendor_str) - 1);
337	}
338
339	if (model_str[0] == '\0') {
340		if (!use_num_info)
341			if (!(usb_model = sysfs_attr_get_value(dev_usb->devpath, "product")))
342				dbg("No USB model string found, using idProduct");
343
344		if (!usb_model) {
345			if (!(usb_model = sysfs_attr_get_value(dev_usb->devpath, "idProduct")))
346				dbg("No USB model information available\n"); sprintf(model_str,"0000");
347		}
348		set_str(model_str, usb_model, sizeof(model_str) - 1);
349	}
350
351	if (revision_str[0] == '\0') {
352		usb_rev = sysfs_attr_get_value(dev_usb->devpath, "bcdDevice");
353		if (usb_rev)
354			set_str(revision_str, usb_rev, sizeof(revision_str)-1);
355	}
356
357	if (serial_str[0] == '\0') {
358		usb_serial = sysfs_attr_get_value(dev_usb->devpath, "serial");
359		if (usb_serial)
360			set_str(serial_str, usb_serial, sizeof(serial_str)-1);
361	}
362	return 0;
363}
364
365int main(int argc, char **argv)
366{
367	int retval = 0;
368	const char *env;
369	char devpath[MAX_PATH_LEN];
370	static int export;
371	static const struct option options[] = {
372		{ "usb-info", 0, NULL, 'u' },
373		{ "num-info", 0, NULL, 'n' },
374		{ "export", 0, NULL, 'x' },
375		{ "help", 0, NULL, 'h' },
376		{}
377	};
378
379	logging_init("usb_id");
380	sysfs_init();
381
382	while (1) {
383		int option;
384
385		option = getopt_long(argc, argv, "nuxh", options, NULL);
386		if (option == -1)
387			break;
388
389		switch (option) {
390		case 'n':
391			use_num_info = 1;
392			use_usb_info = 1;
393			break;
394		case 'u':
395			use_usb_info = 1;
396			break;
397		case 'x':
398			export = 1;
399			break;
400		case 'h':
401			printf("Usage: usb_id [--usb-info] [--num-info] [--export] [--help] <devpath>\n"
402			       "  --usb-info  use usb strings instead\n"
403			       "  --num-info  use numerical values\n"
404			       "  --export    print values as environemt keys\n"
405			       "  --help      print this help text\n\n");
406		default:
407			retval = 1;
408			goto exit;
409		}
410	}
411
412	env = getenv("DEVPATH");
413	if (env != NULL)
414		strlcpy(devpath, env, sizeof(devpath));
415	else {
416		if (argv[optind] == NULL) {
417			fprintf(stderr, "No device specified\n");
418			retval = 1;
419			goto exit;
420		}
421		strlcpy(devpath, argv[optind], sizeof(devpath));
422	}
423
424	retval = usb_id(devpath);
425
426	if (retval == 0) {
427		char serial[256];
428
429		strlcpy(serial, vendor_str, sizeof(serial));
430		strlcat(serial, "_", sizeof(serial));
431		strlcat(serial, model_str, sizeof(serial));
432		if (serial_str[0] != '\0') {
433			strlcat(serial, "_", sizeof(serial));
434			strlcat(serial, serial_str, sizeof(serial));
435		}
436		if (instance_str[0] != '\0') {
437			strlcat(serial, "-", sizeof(serial));
438			strlcat(serial, instance_str, sizeof(serial));
439		}
440
441		if (export) {
442			printf("ID_VENDOR=%s\n", vendor_str);
443			printf("ID_MODEL=%s\n", model_str);
444			printf("ID_REVISION=%s\n", revision_str);
445			printf("ID_SERIAL=%s\n", serial);
446			if (serial_str[0] != '\0')
447				printf("ID_SERIAL_SHORT=%s\n", serial_str);
448			printf("ID_TYPE=%s\n", type_str);
449			if (instance_str[0] != '\0')
450				printf("ID_INSTANCE=%s\n", instance_str);
451			printf("ID_BUS=usb\n");
452		} else
453			printf("%s\n", serial);
454	}
455
456exit:
457	sysfs_cleanup();
458	logging_close();
459	return retval;
460}
461