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