]> git.ipfire.org Git - thirdparty/systemd.git/blame - extras/scsi_id/scsi_id.c
rules: include loop block devices in persistent links
[thirdparty/systemd.git] / extras / scsi_id / scsi_id.c
CommitLineData
c521693b
GKH
1/*
2 * scsi_id.c
3 *
4 * Main section of the scsi_id program
5 *
6 * Copyright (C) IBM Corp. 2003
3d94fb87 7 * Copyright (C) SUSE Linux Products GmbH, 2006
c521693b 8 *
3d94fb87
KS
9 * Author:
10 * Patrick Mansfield<patmans@us.ibm.com>
c521693b 11 *
3d94fb87
KS
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.
c521693b
GKH
15 */
16
17#include <stdio.h>
18#include <stdlib.h>
c521693b
GKH
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>
9eaa50d0 27#include <getopt.h>
c521693b 28#include <sys/stat.h>
c521693b 29
01618658 30#include "../../udev/udev.h"
1aa1e248 31#include "scsi_id.h"
c521693b 32
9eaa50d0 33static const struct option options[] = {
033e9f8c
KS
34 { "device", required_argument, NULL, 'd' },
35 { "config", required_argument, NULL, 'f' },
36 { "page", required_argument, NULL, 'p' },
37 { "blacklisted", no_argument, NULL, 'b' },
38 { "whitelisted", no_argument, NULL, 'g' },
39 { "replace-whitespace", no_argument, NULL, 'u' },
40 { "sg-version", required_argument, NULL, 's' },
41 { "verbose", no_argument, NULL, 'v' },
42 { "version", no_argument, NULL, 'V' },
43 { "export", no_argument, NULL, 'x' },
44 { "help", no_argument, NULL, 'h' },
9eaa50d0
KS
45 {}
46};
47
753417db 48static const char short_options[] = "d:f:ghip:uvVx";
b484e436 49static const char dev_short_options[] = "bgp:";
c521693b 50
c521693b 51static int all_good;
c521693b 52static int dev_specified;
01618658 53static char config_file[MAX_PATH_LEN] = SYSCONFDIR "/scsi_id.config";
50be1401 54static enum page_code default_page_code;
025570aa 55static int sg_version = 4;
c521693b
GKH
56static int use_stderr;
57static int debug;
01f950e2 58static int reformat_serial;
34129109
KS
59static int export;
60static char vendor_str[64];
61static char model_str[64];
aaff09a3
KS
62static char revision_str[16];
63static char type_str[16];
c521693b 64
7d563a17
KS
65static void log_fn(struct udev *udev, int priority,
66 const char *file, int line, const char *fn,
67 const char *format, va_list args)
c521693b 68{
1aa1e248 69 vsyslog(priority, format, args);
c521693b
GKH
70}
71
92f43136 72static void set_type(const char *from, char *to, size_t len)
aaff09a3
KS
73{
74 int type_num;
75 char *eptr;
853ccc43 76 char *type = "generic";
aaff09a3
KS
77
78 type_num = strtoul(from, &eptr, 0);
79 if (eptr != from) {
80 switch (type_num) {
81 case 0:
853ccc43 82 type = "disk";
aaff09a3
KS
83 break;
84 case 1:
853ccc43 85 type = "tape";
aaff09a3
KS
86 break;
87 case 4:
853ccc43 88 type = "optical";
aaff09a3
KS
89 break;
90 case 5:
853ccc43 91 type = "cd";
aaff09a3
KS
92 break;
93 case 7:
853ccc43 94 type = "optical";
aaff09a3
KS
95 break;
96 case 0xe:
853ccc43 97 type = "disk";
aaff09a3
KS
98 break;
99 case 0xf:
853ccc43 100 type = "optical";
34129109
KS
101 break;
102 default:
aaff09a3 103 break;
34129109 104 }
34129109 105 }
111e4f81 106 util_strlcpy(to, type, len);
34129109
KS
107}
108
c521693b
GKH
109/*
110 * get_value:
111 *
112 * buf points to an '=' followed by a quoted string ("foo") or a string ending
113 * with a space or ','.
114 *
115 * Return a pointer to the NUL terminated string, returns NULL if no
116 * matches.
117 */
118static char *get_value(char **buffer)
119{
120 static char *quote_string = "\"\n";
121 static char *comma_string = ",\n";
122 char *val;
123 char *end;
124
125 if (**buffer == '"') {
126 /*
127 * skip leading quote, terminate when quote seen
128 */
129 (*buffer)++;
130 end = quote_string;
131 } else {
132 end = comma_string;
133 }
134 val = strsep(buffer, end);
135 if (val && end == quote_string)
136 /*
137 * skip trailing quote
138 */
139 (*buffer)++;
140
141 while (isspace(**buffer))
142 (*buffer)++;
143
144 return val;
145}
146
147static int argc_count(char *opts)
148{
149 int i = 0;
150 while (*opts != '\0')
151 if (*opts++ == ' ')
152 i++;
153 return i;
154}
155
156/*
157 * get_file_options:
158 *
159 * If vendor == NULL, find a line in the config file with only "OPTIONS=";
160 * if vendor and model are set find the first OPTIONS line in the config
161 * file that matches. Set argc and argv to match the OPTIONS string.
162 *
163 * vendor and model can end in '\n'.
164 */
7d563a17
KS
165static int get_file_options(struct udev *udev,
166 const char *vendor, const char *model,
1aa1e248 167 int *argc, char ***newargv)
c521693b 168{
1bed1db4 169 char *buffer;
c521693b
GKH
170 FILE *fd;
171 char *buf;
172 char *str1;
173 char *vendor_in, *model_in, *options_in; /* read in from file */
174 int lineno;
175 int c;
176 int retval = 0;
c521693b 177
7d563a17 178 dbg(udev, "vendor='%s'; model='%s'\n", vendor, model);
c521693b
GKH
179 fd = fopen(config_file, "r");
180 if (fd == NULL) {
7d563a17 181 dbg(udev, "can't open %s\n", config_file);
c521693b
GKH
182 if (errno == ENOENT) {
183 return 1;
184 } else {
7d563a17 185 err(udev, "can't open %s: %s\n", config_file, strerror(errno));
c521693b
GKH
186 return -1;
187 }
188 }
189
1bed1db4
PM
190 /*
191 * Allocate a buffer rather than put it on the stack so we can
192 * keep it around to parse any options (any allocated newargv
193 * points into this buffer for its strings).
194 */
195 buffer = malloc(MAX_BUFFER_LEN);
196 if (!buffer) {
7d563a17 197 err(udev, "can't allocate memory\n");
1bed1db4
PM
198 return -1;
199 }
200
c521693b
GKH
201 *newargv = NULL;
202 lineno = 0;
c521693b
GKH
203 while (1) {
204 vendor_in = model_in = options_in = NULL;
205
1bed1db4 206 buf = fgets(buffer, MAX_BUFFER_LEN, fd);
c521693b
GKH
207 if (buf == NULL)
208 break;
209 lineno++;
1bed1db4 210 if (buf[strlen(buffer) - 1] != '\n') {
7d563a17 211 err(udev, "Config file line %d too long\n", lineno);
1bed1db4
PM
212 break;
213 }
c521693b
GKH
214
215 while (isspace(*buf))
216 buf++;
217
1aa1e248 218 /* blank or all whitespace line */
c521693b 219 if (*buf == '\0')
c521693b
GKH
220 continue;
221
1aa1e248 222 /* comment line */
c521693b 223 if (*buf == '#')
c521693b
GKH
224 continue;
225
4aa0b15e 226 dbg(udev, "lineno %d: '%s'\n", lineno, buf);
c521693b
GKH
227 str1 = strsep(&buf, "=");
228 if (str1 && strcasecmp(str1, "VENDOR") == 0) {
229 str1 = get_value(&buf);
230 if (!str1) {
231 retval = -1;
232 break;
233 }
234 vendor_in = str1;
235
236 str1 = strsep(&buf, "=");
237 if (str1 && strcasecmp(str1, "MODEL") == 0) {
238 str1 = get_value(&buf);
239 if (!str1) {
240 retval = -1;
241 break;
242 }
243 model_in = str1;
244 str1 = strsep(&buf, "=");
245 }
246 }
247
248 if (str1 && strcasecmp(str1, "OPTIONS") == 0) {
249 str1 = get_value(&buf);
250 if (!str1) {
251 retval = -1;
252 break;
253 }
254 options_in = str1;
255 }
4aa0b15e 256 dbg(udev, "config file line %d:\n"
c521693b
GKH
257 " vendor '%s'; model '%s'; options '%s'\n",
258 lineno, vendor_in, model_in, options_in);
259 /*
260 * Only allow: [vendor=foo[,model=bar]]options=stuff
261 */
262 if (!options_in || (!vendor_in && model_in)) {
7d563a17 263 err(udev, "Error parsing config file line %d '%s'\n", lineno, buffer);
c521693b
GKH
264 retval = -1;
265 break;
266 }
267 if (vendor == NULL) {
268 if (vendor_in == NULL) {
4aa0b15e 269 dbg(udev, "matched global option\n");
c521693b
GKH
270 break;
271 }
272 } else if ((vendor_in && strncmp(vendor, vendor_in,
273 strlen(vendor_in)) == 0) &&
274 (!model_in || (strncmp(model, model_in,
275 strlen(model_in)) == 0))) {
276 /*
277 * Matched vendor and optionally model.
278 *
279 * Note: a short vendor_in or model_in can
280 * give a partial match (that is FOO
281 * matches FOOBAR).
282 */
4aa0b15e 283 dbg(udev, "matched vendor/model\n");
c521693b
GKH
284 break;
285 } else {
4aa0b15e 286 dbg(udev, "no match\n");
c521693b
GKH
287 }
288 }
289
290 if (retval == 0) {
291 if (vendor_in != NULL || model_in != NULL ||
292 options_in != NULL) {
293 /*
294 * Something matched. Allocate newargv, and store
295 * values found in options_in.
296 */
1bed1db4
PM
297 strcpy(buffer, options_in);
298 c = argc_count(buffer) + 2;
c521693b
GKH
299 *newargv = calloc(c, sizeof(**newargv));
300 if (!*newargv) {
7d563a17 301 err(udev, "can't allocate memory\n");
c521693b
GKH
302 retval = -1;
303 } else {
304 *argc = c;
305 c = 0;
1bed1db4
PM
306 /*
307 * argv[0] at 0 is skipped by getopt, but
308 * store the buffer address there for
58310f66 309 * later freeing
1bed1db4
PM
310 */
311 (*newargv)[c] = buffer;
c521693b 312 for (c = 1; c < *argc; c++)
58310f66 313 (*newargv)[c] = strsep(&buffer, " \t");
c521693b
GKH
314 }
315 } else {
1aa1e248 316 /* No matches */
c521693b
GKH
317 retval = 1;
318 }
319 }
1bed1db4
PM
320 if (retval != 0)
321 free(buffer);
c521693b
GKH
322 fclose(fd);
323 return retval;
324}
325
7d563a17
KS
326static int set_options(struct udev *udev,
327 int argc, char **argv, const char *short_opts,
753417db 328 char *maj_min_dev)
c521693b
GKH
329{
330 int option;
c521693b
GKH
331
332 /*
1bed1db4
PM
333 * optind is a global extern used by getopt. Since we can call
334 * set_options twice (once for command line, and once for config
05ec6e75 335 * file) we have to reset this back to 1.
c521693b 336 */
1bed1db4 337 optind = 1;
c521693b 338 while (1) {
9eaa50d0 339 option = getopt_long(argc, argv, short_opts, options, NULL);
c521693b
GKH
340 if (option == -1)
341 break;
342
343 if (optarg)
7d563a17 344 dbg(udev, "option '%c' arg '%s'\n", option, optarg);
c521693b 345 else
7d563a17 346 dbg(udev, "option '%c'\n", option);
c521693b
GKH
347
348 switch (option) {
349 case 'b':
350 all_good = 0;
351 break;
352
c521693b
GKH
353 case 'd':
354 dev_specified = 1;
111e4f81 355 util_strlcpy(maj_min_dev, optarg, MAX_PATH_LEN);
c521693b
GKH
356 break;
357
358 case 'e':
359 use_stderr = 1;
360 break;
361
362 case 'f':
111e4f81 363 util_strlcpy(config_file, optarg, MAX_PATH_LEN);
c521693b
GKH
364 break;
365
366 case 'g':
367 all_good = 1;
368 break;
369
9eaa50d0
KS
370 case 'h':
371 printf("Usage: scsi_id OPTIONS <device>\n"
025570aa
KS
372 " --device= device node for SG_IO commands\n"
373 " --config= location of config file\n"
374 " --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n"
375 " --sg-version=3|4 use SGv3 or SGv4\n"
376 " --blacklisted threat device as blacklisted\n"
377 " --whitelisted threat device as whitelisted\n"
378 " --replace-whitespace replace all whitespaces by underscores\n"
379 " --verbose verbose logging\n"
380 " --version print version\n"
381 " --export print values as environment keys\n"
382 " --help print this help text\n\n");
9eaa50d0
KS
383 exit(0);
384
c521693b
GKH
385 case 'p':
386 if (strcmp(optarg, "0x80") == 0) {
50be1401 387 default_page_code = PAGE_80;
c521693b 388 } else if (strcmp(optarg, "0x83") == 0) {
50be1401
EG
389 default_page_code = PAGE_83;
390 } else if (strcmp(optarg, "pre-spc3-83") == 0) {
391 default_page_code = PAGE_83_PRE_SPC3;
c521693b 392 } else {
7d563a17 393 err(udev, "Unknown page code '%s'\n", optarg);
025570aa
KS
394 return -1;
395 }
396 break;
397
398 case 's':
399 sg_version = atoi(optarg);
400 if (sg_version < 3 || sg_version > 4) {
7d563a17 401 err(udev, "Unknown SG version '%s'\n", optarg);
c521693b
GKH
402 return -1;
403 }
404 break;
405
01f950e2
PM
406 case 'u':
407 reformat_serial = 1;
408 break;
409
34129109
KS
410 case 'x':
411 export = 1;
412 break;
413
c521693b
GKH
414 case 'v':
415 debug++;
416 break;
417
418 case 'V':
01618658 419 printf("%s\n", VERSION);
c521693b
GKH
420 exit(0);
421 break;
422
423 default:
9eaa50d0 424 exit(1);
c521693b
GKH
425 }
426 }
753417db
HR
427 if (optind < argc && !dev_specified) {
428 dev_specified = 1;
111e4f81 429 util_strlcpy(maj_min_dev, argv[optind], MAX_PATH_LEN);
753417db 430 }
c521693b
GKH
431 return 0;
432}
433
7d563a17
KS
434static int per_dev_options(struct udev *udev,
435 struct scsi_id_device *dev_scsi, int *good_bad, int *page_code)
c521693b
GKH
436{
437 int retval;
438 int newargc;
439 char **newargv = NULL;
c521693b 440 int option;
c521693b
GKH
441
442 *good_bad = all_good;
443 *page_code = default_page_code;
c521693b 444
7d563a17 445 retval = get_file_options(udev, vendor_str, model_str, &newargc, &newargv);
c521693b 446
1bed1db4 447 optind = 1; /* reset this global extern */
c521693b 448 while (retval == 0) {
9eaa50d0 449 option = getopt_long(newargc, newargv, dev_short_options, options, NULL);
c521693b
GKH
450 if (option == -1)
451 break;
452
453 if (optarg)
7d563a17 454 dbg(udev, "option '%c' arg '%s'\n", option, optarg);
c521693b 455 else
7d563a17 456 dbg(udev, "option '%c'\n", option);
c521693b
GKH
457
458 switch (option) {
459 case 'b':
460 *good_bad = 0;
461 break;
462
c521693b
GKH
463 case 'g':
464 *good_bad = 1;
465 break;
466
467 case 'p':
468 if (strcmp(optarg, "0x80") == 0) {
50be1401 469 *page_code = PAGE_80;
c521693b 470 } else if (strcmp(optarg, "0x83") == 0) {
50be1401
EG
471 *page_code = PAGE_83;
472 } else if (strcmp(optarg, "pre-spc3-83") == 0) {
473 *page_code = PAGE_83_PRE_SPC3;
c521693b 474 } else {
7d563a17 475 err(udev, "Unknown page code '%s'\n", optarg);
c521693b
GKH
476 retval = -1;
477 }
478 break;
479
480 default:
7d563a17 481 err(udev, "Unknown or bad option '%c' (0x%x)\n", option, option);
c521693b
GKH
482 retval = -1;
483 break;
484 }
485 }
486
1bed1db4
PM
487 if (newargv) {
488 free(newargv[0]);
c521693b 489 free(newargv);
1bed1db4 490 }
c521693b
GKH
491 return retval;
492}
493
7d563a17 494static int set_inq_values(struct udev *udev, struct scsi_id_device *dev_scsi, const char *path)
87cf9f5a
HR
495{
496 int retval;
87cf9f5a 497
025570aa 498 dev_scsi->use_sg = sg_version;
78d9ecfd 499
7d563a17 500 retval = scsi_std_inquiry(udev, dev_scsi, path);
87cf9f5a 501 if (retval)
05364975 502 return retval;
87cf9f5a 503
92f43136 504 udev_util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str));
1340a9e9 505 udev_util_replace_chars(vendor_str, NULL);
92f43136 506 udev_util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str));
1340a9e9 507 udev_util_replace_chars(model_str, NULL);
92f43136
KS
508 set_type(dev_scsi->type, type_str, sizeof(type_str));
509 udev_util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str));
1340a9e9 510 udev_util_replace_chars(revision_str, NULL);
87cf9f5a
HR
511 return 0;
512}
513
01f950e2
PM
514/*
515 * format_serial: replace to whitespaces by underscores for calling
516 * programs that use the serial for device naming (multipath, Suse
517 * naming, etc...)
518 */
519static void format_serial(char *serial)
520{
d313632b 521 char *p = serial, *q;
01f950e2 522
d313632b 523 q = p;
01f950e2 524 while (*p != '\0') {
d313632b
HR
525 if (isspace(*p)) {
526 if (q > serial && q[-1] != '_') {
527 *q = '_';
528 q++;
529 }
530 } else {
531 *q = *p;
532 q++;
533 }
01f950e2
PM
534 p++;
535 }
d313632b 536 *q = '\0';
01f950e2
PM
537}
538
c521693b
GKH
539/*
540 * scsi_id: try to get an id, if one is found, printf it to stdout.
caf4c97a 541 * returns a value passed to exit() - 0 if printed an id, else 1.
c521693b 542 */
7d563a17 543static int scsi_id(struct udev *udev, char *maj_min_dev)
c521693b 544{
753417db 545 struct scsi_id_device dev_scsi;
c521693b
GKH
546 int good_dev;
547 int page_code;
caf4c97a 548 int retval = 0;
c521693b 549
caf4c97a
KS
550 memset(&dev_scsi, 0x00, sizeof(struct scsi_id_device));
551
552 if (set_inq_values(udev, &dev_scsi, maj_min_dev) < 0) {
553 retval = 1;
554 goto out;
555 }
87cf9f5a 556
1aa1e248 557 /* get per device (vendor + model) options from the config file */
caf4c97a 558 per_dev_options(udev, &dev_scsi, &good_dev, &page_code);
4aa0b15e 559 dbg(udev, "per dev options: good %d; page code 0x%x\n", good_dev, page_code);
c521693b
GKH
560 if (!good_dev) {
561 retval = 1;
caf4c97a 562 goto out;
c521693b 563 }
11678eff 564
caf4c97a
KS
565 /* read serial number from mode pages (no values for optical drives) */
566 scsi_get_serial(udev, &dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
567
568 if (export) {
569 char serial_str[MAX_SERIAL_LEN];
570
571 printf("ID_VENDOR=%s\n", vendor_str);
572 printf("ID_MODEL=%s\n", model_str);
573 printf("ID_REVISION=%s\n", revision_str);
574 printf("ID_TYPE=%s\n", type_str);
575 if (dev_scsi.serial[0] != '\0') {
92f43136 576 udev_util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
1340a9e9 577 udev_util_replace_chars(serial_str, NULL);
34129109 578 printf("ID_SERIAL=%s\n", serial_str);
caf4c97a 579 udev_util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str));
1340a9e9 580 udev_util_replace_chars(serial_str, NULL);
11678eff 581 printf("ID_SERIAL_SHORT=%s\n", serial_str);
34129109 582 }
caf4c97a 583 goto out;
c521693b 584 }
c521693b 585
caf4c97a
KS
586 if (dev_scsi.serial[0] == '\0') {
587 retval = 1;
588 goto out;
589 }
590 if (reformat_serial)
591 format_serial(dev_scsi.serial);
592 printf("%s\n", dev_scsi.serial);
593out:
c521693b
GKH
594 return retval;
595}
596
597int main(int argc, char **argv)
598{
7d563a17 599 struct udev *udev;
1aa1e248 600 int retval = 0;
6ecd4d1e 601 char maj_min_dev[MAX_PATH_LEN];
c521693b
GKH
602 int newargc;
603 char **newargv;
604
7d563a17
KS
605 udev = udev_new();
606 if (udev == NULL)
607 goto exit;
608
1aa1e248 609 logging_init("scsi_id");
7d563a17 610 udev_set_log_fn(udev, log_fn);
c521693b 611
c521693b 612 /*
07544a93 613 * Get config file options.
c521693b
GKH
614 */
615 newargv = NULL;
7d563a17 616 retval = get_file_options(udev, NULL, NULL, &newargc, &newargv);
c521693b 617 if (retval < 0) {
1aa1e248
KS
618 retval = 1;
619 goto exit;
620 }
621 if (newargv && (retval == 0)) {
7d563a17 622 if (set_options(udev, newargc, newargv, short_options, maj_min_dev) < 0) {
1aa1e248
KS
623 retval = 2;
624 goto exit;
625 }
c521693b
GKH
626 free(newargv);
627 }
1aa1e248 628
07544a93 629 /*
753417db 630 * Get command line options (overriding any config file settings).
07544a93 631 */
7d563a17 632 if (set_options(udev, argc, argv, short_options, maj_min_dev) < 0)
062db23d 633 exit(1);
c521693b 634
753417db 635 if (!dev_specified) {
7d563a17 636 err(udev, "no device specified\n");
1aa1e248
KS
637 retval = 1;
638 goto exit;
c521693b
GKH
639 }
640
7d563a17 641 retval = scsi_id(udev, maj_min_dev);
1aa1e248
KS
642
643exit:
7d563a17 644 udev_unref(udev);
1aa1e248
KS
645 logging_close();
646 return retval;
c521693b 647}