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