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