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