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