chiark / gitweb /
scsi_id: accept tabs in /etc/scsi_id.conf
[elogind.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 }