chiark / gitweb /
scsi_id: remove dead files
[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                                  * alter freeing.
381                                  */
382                                 (*newargv)[c] = buffer;
383                                 for (c = 1; c < *argc; c++)
384                                         (*newargv)[c] = strsep(&buffer, " ");
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. [Note glibc handles
406          * setting this to 0, but klibc does not.]
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 's':
464                         sys_specified = 1;
465                         strncpy(target, optarg, MAX_PATH_LEN);
466                         target[MAX_PATH_LEN-1] = '\0';
467                         break;
468
469                 case 'u':
470                         reformat_serial = 1;
471                         break;
472
473                 case 'x':
474                         export = 1;
475                         break;
476
477                 case 'v':
478                         debug++;
479                         break;
480
481                 case 'V':
482                         info("scsi_id version: %s\n", SCSI_ID_VERSION);
483                         exit(0);
484                         break;
485
486                 default:
487                         info("Unknown or bad option '%c' (0x%x)", option, option);
488                         return -1;
489                 }
490         }
491         return 0;
492 }
493
494 static int per_dev_options(struct sysfs_device *dev_scsi, int *good_bad, int *page_code)
495 {
496         int retval;
497         int newargc;
498         char **newargv = NULL;
499         const char *vendor, *model, *type;
500         int option;
501
502         *good_bad = all_good;
503         *page_code = default_page_code;
504
505         vendor = sysfs_attr_get_value(dev_scsi->devpath, "vendor");
506         if (!vendor) {
507                 info("%s: cannot get vendor attribute", dev_scsi->devpath);
508                 return -1;
509         }
510         set_str(vendor_str, vendor, sizeof(vendor_str)-1);
511
512         model = sysfs_attr_get_value(dev_scsi->devpath, "model");
513         if (!model) {
514                 info("%s: cannot get model attribute\n", dev_scsi->devpath);
515                 return -1;
516         }
517         set_str(model_str, model, sizeof(model_str)-1);
518
519         type = sysfs_attr_get_value(dev_scsi->devpath, "type");
520         if (!type) {
521                 info("%s: cannot get type attribute", dev_scsi->devpath);
522                 return -1;
523         }
524         set_type(type_str, type, sizeof(type_str));
525
526         type = sysfs_attr_get_value(dev_scsi->devpath, "rev");
527         if (!type) {
528                 info("%s: cannot get type attribute\n", dev_scsi->devpath);
529                 return -1;
530         }
531         set_str(revision_str, type, sizeof(revision_str)-1);
532
533         retval = get_file_options(vendor, model, &newargc, &newargv);
534
535         optind = 1; /* reset this global extern */
536         while (retval == 0) {
537                 option = getopt(newargc, newargv, dev_short_options);
538                 if (option == -1)
539                         break;
540
541                 if (optarg)
542                         dbg("option '%c' arg '%s'\n", option, optarg);
543                 else
544                         dbg("option '%c'\n", option);
545
546                 switch (option) {
547                 case 'b':
548                         *good_bad = 0;
549                         break;
550
551                 case 'g':
552                         *good_bad = 1;
553                         break;
554
555                 case 'p':
556                         if (strcmp(optarg, "0x80") == 0) {
557                                 *page_code = PAGE_80;
558                         } else if (strcmp(optarg, "0x83") == 0) {
559                                 *page_code = PAGE_83;
560                         } else if (strcmp(optarg, "pre-spc3-83") == 0) {
561                                 *page_code = PAGE_83_PRE_SPC3; 
562                         } else {
563                                 info("Unknown page code '%s'", optarg);
564                                 retval = -1;
565                         }
566                         break;
567
568                 default:
569                         info("Unknown or bad option '%c' (0x%x)", option, option);
570                         retval = -1;
571                         break;
572                 }
573         }
574
575         if (newargv) {
576                 free(newargv[0]);
577                 free(newargv);
578         }
579         return retval;
580 }
581
582 /*
583  * format_serial: replace to whitespaces by underscores for calling
584  * programs that use the serial for device naming (multipath, Suse
585  * naming, etc...)
586  */
587 static void format_serial(char *serial)
588 {
589         char *p = serial, *q;
590
591         q = p;
592         while (*p != '\0') {
593                 if (isspace(*p)) {
594                         if (q > serial && q[-1] != '_') {
595                                 *q = '_';
596                                 q++;
597                         }
598                 } else {
599                         *q = *p;
600                         q++;
601                 }
602                 p++;
603         }
604         *q = '\0';
605 }
606
607 /*
608  * scsi_id: try to get an id, if one is found, printf it to stdout.
609  * returns a value passed to exit() - 0 if printed an id, else 1. This
610  * could be expanded, for example, if we want to report a failure like no
611  * memory etc. return 2, and return 1 for expected cases (like broken
612  * device found) that do not print an id.
613  */
614 static int scsi_id(const char *devpath, char *maj_min_dev)
615 {
616         int retval;
617         int dev_type = 0;
618         char *serial, *unaligned_buf;
619         struct sysfs_device *dev;
620         struct sysfs_device *dev_scsi;
621         int good_dev;
622         int page_code;
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 #define ALIGN   512
655         unaligned_buf = malloc(MAX_SERIAL_LEN + ALIGN);
656         serial = (char*) (((unsigned long) unaligned_buf + (ALIGN - 1))
657                           & ~(ALIGN - 1));
658         dbg("buffer unaligned 0x%p; aligned 0x%p\n", unaligned_buf, serial);
659 #undef ALIGN
660
661         if (!good_dev) {
662                 retval = 1;
663         } else if (scsi_get_serial(dev_scsi, maj_min_dev, page_code,
664                                    serial, MAX_SERIAL_LEN)) {
665                 retval = always_info?0:1;
666         } else {
667                 retval = 0;
668         }
669         if (!retval) {
670                 if (export) {
671                         static char serial_str[64];
672                         printf("ID_VENDOR=%s\n", vendor_str);
673                         printf("ID_MODEL=%s\n", model_str);
674                         printf("ID_REVISION=%s\n", revision_str);
675                         set_str(serial_str, serial, sizeof(serial_str));
676                         printf("ID_SERIAL=%s\n", serial_str);
677                         printf("ID_TYPE=%s\n", type_str);
678                         printf("ID_BUS=scsi\n");
679                 } else {
680                         if (reformat_serial)
681                                 format_serial(serial);
682                         if (display_bus_id)
683                                 printf("%s: ", dev_scsi->kernel_name);
684                         printf("%s\n", serial);
685                 }
686                 dbg("%s\n", serial);
687                 retval = 0;
688         }
689
690         if (!dev_specified)
691                 unlink(maj_min_dev);
692
693         return retval;
694 }
695
696 int main(int argc, char **argv)
697 {
698         int retval = 0;
699         char devpath[MAX_PATH_LEN];
700         char maj_min_dev[MAX_PATH_LEN];
701         int newargc;
702         const char *env;
703         char **newargv;
704
705         logging_init("scsi_id");
706         sysfs_init();
707         dbg("argc is %d\n", argc);
708
709         /* sysfs path can be overridden for testing */
710         env = getenv("SYSFS_PATH");
711         if (env) {
712                 strncpy(sysfs_path, env, sizeof(sysfs_path));
713                 sysfs_path[sizeof(sysfs_path)-1] = '\0';
714         } else
715                 strcpy(sysfs_path, "/sys");
716
717         env = getenv("DEVPATH");
718         if (env) {
719                 hotplug_mode = 1;
720                 sys_specified = 1;
721                 strncpy(devpath, env, MAX_PATH_LEN);
722                 devpath[sizeof(devpath)-1] = '\0';
723         }
724
725         /*
726          * Get config file options.
727          */
728         newargv = NULL;
729         retval = get_file_options(NULL, NULL, &newargc, &newargv);
730         if (retval < 0) {
731                 retval = 1;
732                 goto exit;
733         }
734         if (newargv && (retval == 0)) {
735                 if (set_options(newargc, newargv, short_options, devpath,
736                                 maj_min_dev) < 0) {
737                         retval = 2;
738                         goto exit;
739                 }
740                 free(newargv);
741         }
742
743         /*
744          * Get command line options (overriding any config file or DEVPATH
745          * settings).
746          */
747         if (set_options(argc, argv, short_options, devpath, maj_min_dev) < 0)
748                 exit(1);
749
750         if (!sys_specified) {
751                 info("-s must be specified\n");
752                 retval = 1;
753                 goto exit;
754         }
755
756         retval = scsi_id(devpath, maj_min_dev);
757
758 exit:
759         sysfs_cleanup();
760         logging_close();
761         return retval;
762 }