1/*
2 * scsi_id.c
3 *
4 * Main section of the scsi_id program
5 *
6 * Copyright (C) IBM Corp. 2003
7 * Copyright (C) SUSE Linux Products GmbH, 2006
8 *
9 * Author:
10 *	Patrick Mansfield<patmans@us.ibm.com>
11 *
12 *	This program is free software; you can redistribute it and/or modify it
13 *	under the terms of the GNU General Public License as published by the
14 *	Free Software Foundation version 2 of the License.
15 */
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <unistd.h>
20#include <signal.h>
21#include <fcntl.h>
22#include <errno.h>
23#include <string.h>
24#include <syslog.h>
25#include <stdarg.h>
26#include <ctype.h>
27#include <getopt.h>
28#include <sys/stat.h>
29
30#include "../../udev.h"
31#include "scsi_id.h"
32#include "scsi_id_version.h"
33
34/* temporary names for mknod  */
35#define TMP_DIR		"/dev"
36#define TMP_PREFIX	"tmp-scsi"
37
38static const struct option options[] = {
39	{ "device", 1, NULL, 'd' },
40	{ "config", 1, NULL, 'f' },
41	{ "page", 1, NULL, 'p' },
42	{ "devpath", 1, NULL, 's' },
43	{ "fallback-to-sysfs", 0, NULL, 'a' },
44	{ "blacklisted", 0, NULL, 'b' },
45	{ "whitelisted", 0, NULL, 'g' },
46	{ "prefix-bus-id", 0, NULL, 'i' },
47	{ "replace-whitespace", 0, NULL, 'u' },
48	{ "ignore-sysfs", 0, NULL, 'n' },
49	{ "verbose", 0, NULL, 'v' },
50	{ "version", 0, NULL, 'V' },
51	{ "export", 0, NULL, 'x' },
52	{ "help", 0, NULL, 'h' },
53	{}
54};
55
56static const char short_options[] = "abd:f:ghinp:s:uvVx";
57static const char dev_short_options[] = "bgp:";
58
59static int all_good;
60static int always_info;
61static int dev_specified;
62static int sys_specified;
63static char config_file[MAX_PATH_LEN] = SCSI_ID_CONFIG_FILE;
64static int display_bus_id;
65static enum page_code default_page_code;
66static int use_stderr;
67static int debug;
68static int hotplug_mode;
69static int reformat_serial;
70static int ignore_sysfs;
71static int export;
72static char vendor_str[64];
73static char model_str[64];
74static char revision_str[16];
75static char type_str[16];
76
77#ifdef USE_LOG
78void log_message(int priority, const char *format, ...)
79{
80	va_list args;
81	static int udev_log = -1;
82
83	if (udev_log == -1) {
84		const char *value;
85
86		value = getenv("UDEV_LOG");
87		if (value)
88			udev_log = log_priority(value);
89		else
90			udev_log = LOG_ERR;
91	}
92
93	if (priority > udev_log)
94		return;
95
96	va_start(args, format);
97	vsyslog(priority, format, args);
98	va_end(args);
99}
100#endif
101
102static void set_str(char *to, const char *from, size_t count)
103{
104	size_t i, j, len;
105
106	/* strip trailing whitespace */
107	len = strnlen(from, count);
108	while (len && isspace(from[len-1]))
109		len--;
110
111	/* strip leading whitespace */
112	i = 0;
113	while (isspace(from[i]) && (i < len))
114		i++;
115
116	j = 0;
117	while (i < len) {
118		/* substitute multiple whitespace */
119		if (isspace(from[i])) {
120			while (isspace(from[i]))
121				i++;
122			to[j++] = '_';
123		}
124		/* skip chars */
125		if (from[i] == '/') {
126			i++;
127			continue;
128		}
129		to[j++] = from[i++];
130	}
131	to[j] = '\0';
132}
133
134static void set_type(char *to, const char *from, size_t len)
135{
136	int type_num;
137	char *eptr;
138	char *type = "generic";
139
140	type_num = strtoul(from, &eptr, 0);
141	if (eptr != from) {
142		switch (type_num) {
143		case 0:
144			type = "disk";
145			break;
146		case 1:
147			type = "tape";
148			break;
149		case 4:
150			type = "optical";
151			break;
152		case 5:
153			type = "cd";
154			break;
155		case 7:
156			type = "optical";
157			break;
158		case 0xe:
159			type = "disk";
160			break;
161		case 0xf:
162			type = "optical";
163			break;
164		default:
165			break;
166		}
167	}
168	strncpy(to, type, len);
169	to[len-1] = '\0';
170}
171
172static int create_tmp_dev(const char *devpath, char *tmpdev, int dev_type)
173{
174	unsigned int maj, min;
175	const char *attr;
176
177	dbg("%s", devpath);
178	attr = sysfs_attr_get_value(devpath, "dev");
179	if (attr == NULL) {
180		dbg("%s: could not get dev attribute: %s", devpath, strerror(errno));
181		return -1;
182	}
183
184	dbg("dev value %s", attr);
185	if (sscanf(attr, "%u:%u", &maj, &min) != 2) {
186		err("%s: invalid dev major/minor", devpath);
187		return -1;
188	}
189
190	snprintf(tmpdev, MAX_PATH_LEN, "%s/%s-maj%d-min%d-%u",
191		 TMP_DIR, TMP_PREFIX, maj, min, getpid());
192
193	dbg("tmpdev '%s'", tmpdev);
194	if (mknod(tmpdev, 0600 | dev_type, makedev(maj, min))) {
195		err("mknod failed: %s", strerror(errno));
196		return -1;
197	}
198	return 0;
199}
200
201/*
202 * get_value:
203 *
204 * buf points to an '=' followed by a quoted string ("foo") or a string ending
205 * with a space or ','.
206 *
207 * Return a pointer to the NUL terminated string, returns NULL if no
208 * matches.
209 */
210static char *get_value(char **buffer)
211{
212	static char *quote_string = "\"\n";
213	static char *comma_string = ",\n";
214	char *val;
215	char *end;
216
217	if (**buffer == '"') {
218		/*
219		 * skip leading quote, terminate when quote seen
220		 */
221		(*buffer)++;
222		end = quote_string;
223	} else {
224		end = comma_string;
225	}
226	val = strsep(buffer, end);
227	if (val && end == quote_string)
228		/*
229		 * skip trailing quote
230		 */
231		(*buffer)++;
232
233	while (isspace(**buffer))
234		(*buffer)++;
235
236	return val;
237}
238
239static int argc_count(char *opts)
240{
241	int i = 0;
242	while (*opts != '\0')
243		if (*opts++ == ' ')
244			i++;
245	return i;
246}
247
248/*
249 * get_file_options:
250 *
251 * If vendor == NULL, find a line in the config file with only "OPTIONS=";
252 * if vendor and model are set find the first OPTIONS line in the config
253 * file that matches. Set argc and argv to match the OPTIONS string.
254 *
255 * vendor and model can end in '\n'.
256 */
257static int get_file_options(const char *vendor, const char *model,
258			    int *argc, char ***newargv)
259{
260	char *buffer;
261	FILE *fd;
262	char *buf;
263	char *str1;
264	char *vendor_in, *model_in, *options_in; /* read in from file */
265	int lineno;
266	int c;
267	int retval = 0;
268
269	dbg("vendor='%s'; model='%s'\n", vendor, model);
270	fd = fopen(config_file, "r");
271	if (fd == NULL) {
272		dbg("can't open %s\n", config_file);
273		if (errno == ENOENT) {
274			return 1;
275		} else {
276			err("can't open %s: %s", config_file, strerror(errno));
277			return -1;
278		}
279	}
280
281	/*
282	 * Allocate a buffer rather than put it on the stack so we can
283	 * keep it around to parse any options (any allocated newargv
284	 * points into this buffer for its strings).
285	 */
286	buffer = malloc(MAX_BUFFER_LEN);
287	if (!buffer) {
288		err("Can't allocate memory.");
289		return -1;
290	}
291
292	*newargv = NULL;
293	lineno = 0;
294	while (1) {
295		vendor_in = model_in = options_in = NULL;
296
297		buf = fgets(buffer, MAX_BUFFER_LEN, fd);
298		if (buf == NULL)
299			break;
300		lineno++;
301		if (buf[strlen(buffer) - 1] != '\n') {
302			info("Config file line %d too long.\n", lineno);
303			break;
304		}
305
306		while (isspace(*buf))
307			buf++;
308
309		/* blank or all whitespace line */
310		if (*buf == '\0')
311			continue;
312
313		/* comment line */
314		if (*buf == '#')
315			continue;
316
317		dbg("lineno %d: '%s'\n", lineno, buf);
318		str1 = strsep(&buf, "=");
319		if (str1 && strcasecmp(str1, "VENDOR") == 0) {
320			str1 = get_value(&buf);
321			if (!str1) {
322				retval = -1;
323				break;
324			}
325			vendor_in = str1;
326
327			str1 = strsep(&buf, "=");
328			if (str1 && strcasecmp(str1, "MODEL") == 0) {
329				str1 = get_value(&buf);
330				if (!str1) {
331					retval = -1;
332					break;
333				}
334				model_in = str1;
335				str1 = strsep(&buf, "=");
336			}
337		}
338
339		if (str1 && strcasecmp(str1, "OPTIONS") == 0) {
340			str1 = get_value(&buf);
341			if (!str1) {
342				retval = -1;
343				break;
344			}
345			options_in = str1;
346		}
347		dbg("config file line %d:"
348			" vendor '%s'; model '%s'; options '%s'\n",
349			lineno, vendor_in, model_in, options_in);
350		/*
351		 * Only allow: [vendor=foo[,model=bar]]options=stuff
352		 */
353		if (!options_in || (!vendor_in && model_in)) {
354			info("Error parsing config file line %d '%s'", lineno, buffer);
355			retval = -1;
356			break;
357		}
358		if (vendor == NULL) {
359			if (vendor_in == NULL) {
360				dbg("matched global option\n");
361				break;
362			}
363		} else if ((vendor_in && strncmp(vendor, vendor_in,
364						 strlen(vendor_in)) == 0) &&
365			   (!model_in || (strncmp(model, model_in,
366						  strlen(model_in)) == 0))) {
367				/*
368				 * Matched vendor and optionally model.
369				 *
370				 * Note: a short vendor_in or model_in can
371				 * give a partial match (that is FOO
372				 * matches FOOBAR).
373				 */
374				dbg("matched vendor/model\n");
375				break;
376		} else {
377			dbg("no match\n");
378		}
379	}
380
381	if (retval == 0) {
382		if (vendor_in != NULL || model_in != NULL ||
383		    options_in != NULL) {
384			/*
385			 * Something matched. Allocate newargv, and store
386			 * values found in options_in.
387			 */
388			strcpy(buffer, options_in);
389			c = argc_count(buffer) + 2;
390			*newargv = calloc(c, sizeof(**newargv));
391			if (!*newargv) {
392				err("Can't allocate memory.");
393				retval = -1;
394			} else {
395				*argc = c;
396				c = 0;
397				/*
398				 * argv[0] at 0 is skipped by getopt, but
399				 * store the buffer address there for
400				 * later freeing
401				 */
402				(*newargv)[c] = buffer;
403				for (c = 1; c < *argc; c++)
404					(*newargv)[c] = strsep(&buffer, " \t");
405			}
406		} else {
407			/* No matches  */
408			retval = 1;
409		}
410	}
411	if (retval != 0)
412		free(buffer);
413	fclose(fd);
414	return retval;
415}
416
417static int set_options(int argc, char **argv, const char *short_opts,
418		       char *target, char *maj_min_dev)
419{
420	int option;
421
422	/*
423	 * optind is a global extern used by getopt. Since we can call
424	 * set_options twice (once for command line, and once for config
425	 * file) we have to reset this back to 1.
426	 */
427	optind = 1;
428	while (1) {
429		option = getopt_long(argc, argv, short_opts, options, NULL);
430		if (option == -1)
431			break;
432
433		if (optarg)
434			dbg("option '%c' arg '%s'\n", option, optarg);
435		else
436			dbg("option '%c'\n", option);
437
438		switch (option) {
439		case 'a':
440			always_info = 1;
441			break;
442		case 'b':
443			all_good = 0;
444			break;
445
446		case 'd':
447			dev_specified = 1;
448			strncpy(maj_min_dev, optarg, MAX_PATH_LEN);
449			maj_min_dev[MAX_PATH_LEN-1] = '\0';
450			break;
451
452		case 'e':
453			use_stderr = 1;
454			break;
455
456		case 'f':
457			strncpy(config_file, optarg, MAX_PATH_LEN);
458			config_file[MAX_PATH_LEN-1] = '\0';
459			break;
460
461		case 'g':
462			all_good = 1;
463			break;
464
465		case 'h':
466			printf("Usage: scsi_id OPTIONS <device>\n"
467			       "  --device               device node for SG_IO commands\n"
468			       "  --devpath              sysfs devpath\n"
469			       "  --config               location of config file\n"
470			       "  --page                 SCSI page (0x80, 0x83, pre-spc3-83)\n"
471			       "  --fallback-to-sysfs    print sysfs values if inquiry fails\n"
472			       "  --ignore-sysfs         ignore sysfs entries\n"
473			       "  --blacklisted          threat device as blacklisted\n"
474			       "  --whitelisted          threat device as whitelisted\n"
475			       "  --prefix-bus-id        prefix SCSI bus id\n"
476			       "  --replace-whitespace   replace all whitespaces by underscores\n"
477			       "  --verbose              verbose logging\n"
478			       "  --version              print version\n"
479			       "  --export               print values as environment keys\n"
480			       "  --help                 print this help text\n\n");
481			exit(0);
482
483		case 'i':
484			display_bus_id = 1;
485			break;
486
487		case 'p':
488			if (strcmp(optarg, "0x80") == 0) {
489				default_page_code = PAGE_80;
490			} else if (strcmp(optarg, "0x83") == 0) {
491				default_page_code = PAGE_83;
492			} else if (strcmp(optarg, "pre-spc3-83") == 0) {
493				default_page_code = PAGE_83_PRE_SPC3;
494			} else {
495				info("Unknown page code '%s'", optarg);
496				return -1;
497			}
498			break;
499
500		case 'n':
501			ignore_sysfs = 1;
502			break;
503
504		case 's':
505			sys_specified = 1;
506			strncpy(target, optarg, MAX_PATH_LEN);
507			target[MAX_PATH_LEN-1] = '\0';
508			break;
509
510		case 'u':
511			reformat_serial = 1;
512			break;
513
514		case 'x':
515			export = 1;
516			break;
517
518		case 'v':
519			debug++;
520			break;
521
522		case 'V':
523			printf("%s\n", SCSI_ID_VERSION);
524			exit(0);
525			break;
526
527		default:
528			exit(1);
529		}
530	}
531	return 0;
532}
533
534static int per_dev_options(struct sysfs_device *dev_scsi, int *good_bad, int *page_code)
535{
536	int retval;
537	int newargc;
538	char **newargv = NULL;
539	int option;
540
541	*good_bad = all_good;
542	*page_code = default_page_code;
543
544	retval = get_file_options(vendor_str, model_str, &newargc, &newargv);
545
546	optind = 1; /* reset this global extern */
547	while (retval == 0) {
548		option = getopt_long(newargc, newargv, dev_short_options, options, NULL);
549		if (option == -1)
550			break;
551
552		if (optarg)
553			dbg("option '%c' arg '%s'\n", option, optarg);
554		else
555			dbg("option '%c'\n", option);
556
557		switch (option) {
558		case 'b':
559			*good_bad = 0;
560			break;
561
562		case 'g':
563			*good_bad = 1;
564			break;
565
566		case 'p':
567			if (strcmp(optarg, "0x80") == 0) {
568				*page_code = PAGE_80;
569			} else if (strcmp(optarg, "0x83") == 0) {
570				*page_code = PAGE_83;
571			} else if (strcmp(optarg, "pre-spc3-83") == 0) {
572				*page_code = PAGE_83_PRE_SPC3;
573			} else {
574				info("Unknown page code '%s'", optarg);
575				retval = -1;
576			}
577			break;
578
579		default:
580			info("Unknown or bad option '%c' (0x%x)", option, option);
581			retval = -1;
582			break;
583		}
584	}
585
586	if (newargv) {
587		free(newargv[0]);
588		free(newargv);
589	}
590	return retval;
591}
592
593static int set_sysfs_values(struct sysfs_device *dev_scsi)
594{
595	const char *vendor, *model, *type;
596
597	vendor = sysfs_attr_get_value(dev_scsi->devpath, "vendor");
598	if (!vendor) {
599		info("%s: cannot get vendor attribute", dev_scsi->devpath);
600		return -1;
601	}
602	set_str(vendor_str, vendor, sizeof(vendor_str)-1);
603
604	model = sysfs_attr_get_value(dev_scsi->devpath, "model");
605	if (!model) {
606		info("%s: cannot get model attribute\n", dev_scsi->devpath);
607		return -1;
608	}
609	set_str(model_str, model, sizeof(model_str)-1);
610
611	type = sysfs_attr_get_value(dev_scsi->devpath, "type");
612	if (!type) {
613		info("%s: cannot get type attribute", dev_scsi->devpath);
614		return -1;
615	}
616	set_type(type_str, type, sizeof(type_str));
617
618	type = sysfs_attr_get_value(dev_scsi->devpath, "rev");
619	if (!type) {
620		info("%s: cannot get type attribute\n", dev_scsi->devpath);
621		return -1;
622	}
623	set_str(revision_str, type, sizeof(revision_str)-1);
624
625	return 0;
626}
627
628static int set_inq_values(struct sysfs_device *dev_scsi, const char *path)
629{
630	int retval;
631	char vendor[8], model[16], type[4], rev[4];
632
633	retval = scsi_std_inquiry(dev_scsi, path, vendor, model, rev, type);
634	if (retval)
635	    return retval;
636
637	set_str(vendor_str, vendor, 8);
638	set_str(model_str, model, 16);
639	set_type(type_str, type, sizeof(type_str) - 1);
640	set_str(revision_str, rev, sizeof(revision_str) -1);
641
642	return 0;
643}
644
645/*
646 * format_serial: replace to whitespaces by underscores for calling
647 * programs that use the serial for device naming (multipath, Suse
648 * naming, etc...)
649 */
650static void format_serial(char *serial)
651{
652	char *p = serial, *q;
653
654	q = p;
655	while (*p != '\0') {
656		if (isspace(*p)) {
657			if (q > serial && q[-1] != '_') {
658				*q = '_';
659				q++;
660			}
661		} else {
662			*q = *p;
663			q++;
664		}
665		p++;
666	}
667	*q = '\0';
668}
669
670/*
671 * scsi_id: try to get an id, if one is found, printf it to stdout.
672 * returns a value passed to exit() - 0 if printed an id, else 1. This
673 * could be expanded, for example, if we want to report a failure like no
674 * memory etc. return 2, and return 1 for expected cases (like broken
675 * device found) that do not print an id.
676 */
677static int scsi_id(const char *devpath, char *maj_min_dev)
678{
679	int retval;
680	int dev_type = 0;
681	struct sysfs_device *dev;
682	struct sysfs_device *dev_scsi = NULL;
683	int good_dev;
684	int page_code;
685	char serial[MAX_SERIAL_LEN];
686	char serial_short[MAX_SERIAL_LEN];
687	const char *bus_str = NULL;
688
689	dbg("devpath %s\n", devpath);
690
691	dev = sysfs_device_get(devpath);
692	if (dev == NULL) {
693		err("unable to access '%s'", devpath);
694		return 1;
695	}
696
697	if (strcmp(dev->subsystem, "block") == 0)
698		dev_type = S_IFBLK;
699	else
700		dev_type = S_IFCHR;
701
702	/* mknod a temp dev to communicate with the device */
703	if (!dev_specified && create_tmp_dev(dev->devpath, maj_min_dev, dev_type)) {
704		dbg("create_tmp_dev failed\n");
705		return 1;
706	}
707
708	if (!ignore_sysfs) {
709		/* get scsi parent device */
710		dev_scsi = sysfs_device_get_parent_with_subsystem(dev, "scsi");
711		if (dev_scsi == NULL) {
712			err("unable to access parent device of '%s'", devpath);
713			return 1;
714		}
715		set_sysfs_values(dev_scsi);
716		bus_str = "scsi";
717	} else {
718		dev_scsi = dev;
719		set_inq_values(dev_scsi, maj_min_dev);
720	}
721
722	/* get per device (vendor + model) options from the config file */
723	retval = per_dev_options(dev_scsi, &good_dev, &page_code);
724	dbg("per dev options: good %d; page code 0x%x", good_dev, page_code);
725
726	if (!good_dev) {
727		retval = 1;
728	} else if (scsi_get_serial(dev_scsi, maj_min_dev, page_code,
729				   serial, serial_short, MAX_SERIAL_LEN)) {
730		retval = always_info?0:1;
731	} else {
732		retval = 0;
733	}
734	if (!retval) {
735		if (export) {
736			char serial_str[MAX_SERIAL_LEN];
737
738			printf("ID_VENDOR=%s\n", vendor_str);
739			printf("ID_MODEL=%s\n", model_str);
740			printf("ID_REVISION=%s\n", revision_str);
741			set_str(serial_str, serial, sizeof(serial_str));
742			printf("ID_SERIAL=%s\n", serial_str);
743			set_str(serial_str, serial_short, sizeof(serial_str));
744			printf("ID_SERIAL_SHORT=%s\n", serial_str);
745			printf("ID_TYPE=%s\n", type_str);
746			if (bus_str != NULL)
747				printf("ID_BUS=%s\n", bus_str);
748		} else {
749			if (reformat_serial)
750				format_serial(serial);
751			if (display_bus_id)
752				printf("%s: ", dev_scsi->kernel);
753			printf("%s\n", serial);
754		}
755		dbg("%s\n", serial);
756		retval = 0;
757	}
758
759	if (!dev_specified)
760		unlink(maj_min_dev);
761
762	return retval;
763}
764
765int main(int argc, char **argv)
766{
767	int retval = 0;
768	char devpath[MAX_PATH_LEN];
769	char maj_min_dev[MAX_PATH_LEN];
770	int newargc;
771	const char *env;
772	char **newargv;
773
774	logging_init("scsi_id");
775	sysfs_init();
776	dbg("argc is %d\n", argc);
777
778	/* sysfs path can be overridden for testing */
779	env = getenv("SYSFS_PATH");
780	if (env) {
781		strncpy(sysfs_path, env, sizeof(sysfs_path));
782		sysfs_path[sizeof(sysfs_path)-1] = '\0';
783	} else
784		strcpy(sysfs_path, "/sys");
785
786	env = getenv("DEVPATH");
787	if (env) {
788		hotplug_mode = 1;
789		sys_specified = 1;
790		strncpy(devpath, env, MAX_PATH_LEN);
791		devpath[sizeof(devpath)-1] = '\0';
792	}
793
794	/*
795	 * Get config file options.
796	 */
797	newargv = NULL;
798	retval = get_file_options(NULL, NULL, &newargc, &newargv);
799	if (retval < 0) {
800		retval = 1;
801		goto exit;
802	}
803	if (newargv && (retval == 0)) {
804		if (set_options(newargc, newargv, short_options, devpath,
805				maj_min_dev) < 0) {
806			retval = 2;
807			goto exit;
808		}
809		free(newargv);
810	}
811
812	/*
813	 * Get command line options (overriding any config file or DEVPATH
814	 * settings).
815	 */
816	if (set_options(argc, argv, short_options, devpath, maj_min_dev) < 0)
817		exit(1);
818
819	if (!sys_specified) {
820		info("--devpath=<path> must be specified\n");
821		retval = 1;
822		goto exit;
823	}
824
825	retval = scsi_id(devpath, maj_min_dev);
826
827exit:
828	sysfs_cleanup();
829	logging_close();
830	return retval;
831}
832