]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/scsi_id/scsi_id.c
pkgconfig: define variables relative to ${prefix}/${rootprefix}/${sysconfdir}
[thirdparty/systemd.git] / src / udev / scsi_id / scsi_id.c
1 /* SPDX-License-Identifier: GPL-2.0+ */
2 /*
3 * Copyright © IBM Corp. 2003
4 * Copyright © SUSE Linux Products GmbH, 2006
5 */
6
7 #include <ctype.h>
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <getopt.h>
11 #include <signal.h>
12 #include <stdarg.h>
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/stat.h>
18 #include <unistd.h>
19
20 #include "alloc-util.h"
21 #include "fd-util.h"
22 #include "libudev-private.h"
23 #include "scsi_id.h"
24 #include "string-util.h"
25 #include "udev-util.h"
26
27 static const struct option options[] = {
28 { "device", required_argument, NULL, 'd' },
29 { "config", required_argument, NULL, 'f' },
30 { "page", required_argument, NULL, 'p' },
31 { "blacklisted", no_argument, NULL, 'b' },
32 { "whitelisted", no_argument, NULL, 'g' },
33 { "replace-whitespace", no_argument, NULL, 'u' },
34 { "sg-version", required_argument, NULL, 's' },
35 { "verbose", no_argument, NULL, 'v' },
36 { "version", no_argument, NULL, 'V' }, /* don't advertise -V */
37 { "export", no_argument, NULL, 'x' },
38 { "help", no_argument, NULL, 'h' },
39 {}
40 };
41
42 static bool all_good = false;
43 static bool dev_specified = false;
44 static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config";
45 static enum page_code default_page_code = PAGE_UNSPECIFIED;
46 static int sg_version = 4;
47 static bool reformat_serial = false;
48 static bool export = false;
49 static char vendor_str[64];
50 static char model_str[64];
51 static char vendor_enc_str[256];
52 static char model_enc_str[256];
53 static char revision_str[16];
54 static char type_str[16];
55
56 static void set_type(const char *from, char *to, size_t len) {
57 int type_num;
58 char *eptr;
59 const char *type = "generic";
60
61 type_num = strtoul(from, &eptr, 0);
62 if (eptr != from) {
63 switch (type_num) {
64 case 0:
65 type = "disk";
66 break;
67 case 1:
68 type = "tape";
69 break;
70 case 4:
71 type = "optical";
72 break;
73 case 5:
74 type = "cd";
75 break;
76 case 7:
77 type = "optical";
78 break;
79 case 0xe:
80 type = "disk";
81 break;
82 case 0xf:
83 type = "optical";
84 break;
85 default:
86 break;
87 }
88 }
89 strscpy(to, len, type);
90 }
91
92 /*
93 * get_value:
94 *
95 * buf points to an '=' followed by a quoted string ("foo") or a string ending
96 * with a space or ','.
97 *
98 * Return a pointer to the NUL terminated string, returns NULL if no
99 * matches.
100 */
101 static char *get_value(char **buffer) {
102 static const char *quote_string = "\"\n";
103 static const char *comma_string = ",\n";
104 char *val;
105 const char *end;
106
107 if (**buffer == '"') {
108 /*
109 * skip leading quote, terminate when quote seen
110 */
111 (*buffer)++;
112 end = quote_string;
113 } else {
114 end = comma_string;
115 }
116 val = strsep(buffer, end);
117 if (val && end == quote_string)
118 /*
119 * skip trailing quote
120 */
121 (*buffer)++;
122
123 while (isspace(**buffer))
124 (*buffer)++;
125
126 return val;
127 }
128
129 static int argc_count(char *opts) {
130 int i = 0;
131 while (*opts != '\0')
132 if (*opts++ == ' ')
133 i++;
134 return i;
135 }
136
137 /*
138 * get_file_options:
139 *
140 * If vendor == NULL, find a line in the config file with only "OPTIONS=";
141 * if vendor and model are set find the first OPTIONS line in the config
142 * file that matches. Set argc and argv to match the OPTIONS string.
143 *
144 * vendor and model can end in '\n'.
145 */
146 static int get_file_options(const char *vendor, const char *model,
147 int *argc, char ***newargv) {
148 _cleanup_free_ char *buffer = NULL;
149 _cleanup_fclose_ FILE *f;
150 char *buf;
151 char *str1;
152 char *vendor_in, *model_in, *options_in; /* read in from file */
153 int lineno;
154 int c;
155 int retval = 0;
156
157 f = fopen(config_file, "re");
158 if (f == NULL) {
159 if (errno == ENOENT)
160 return 1;
161 else {
162 log_error_errno(errno, "can't open %s: %m", config_file);
163 return -1;
164 }
165 }
166
167 /*
168 * Allocate a buffer rather than put it on the stack so we can
169 * keep it around to parse any options (any allocated newargv
170 * points into this buffer for its strings).
171 */
172 buffer = malloc(MAX_BUFFER_LEN);
173 if (!buffer)
174 return log_oom();
175
176 *newargv = NULL;
177 lineno = 0;
178 for (;;) {
179 vendor_in = model_in = options_in = NULL;
180
181 buf = fgets(buffer, MAX_BUFFER_LEN, f);
182 if (buf == NULL)
183 break;
184 lineno++;
185 if (buf[strlen(buffer) - 1] != '\n') {
186 log_error("Config file line %d too long", lineno);
187 break;
188 }
189
190 while (isspace(*buf))
191 buf++;
192
193 /* blank or all whitespace line */
194 if (*buf == '\0')
195 continue;
196
197 /* comment line */
198 if (*buf == '#')
199 continue;
200
201 str1 = strsep(&buf, "=");
202 if (str1 && strcaseeq(str1, "VENDOR")) {
203 str1 = get_value(&buf);
204 if (!str1) {
205 retval = log_oom();
206 break;
207 }
208 vendor_in = str1;
209
210 str1 = strsep(&buf, "=");
211 if (str1 && strcaseeq(str1, "MODEL")) {
212 str1 = get_value(&buf);
213 if (!str1) {
214 retval = log_oom();
215 break;
216 }
217 model_in = str1;
218 str1 = strsep(&buf, "=");
219 }
220 }
221
222 if (str1 && strcaseeq(str1, "OPTIONS")) {
223 str1 = get_value(&buf);
224 if (!str1) {
225 retval = log_oom();
226 break;
227 }
228 options_in = str1;
229 }
230
231 /*
232 * Only allow: [vendor=foo[,model=bar]]options=stuff
233 */
234 if (!options_in || (!vendor_in && model_in)) {
235 log_error("Error parsing config file line %d '%s'", lineno, buffer);
236 retval = -1;
237 break;
238 }
239 if (vendor == NULL) {
240 if (vendor_in == NULL)
241 break;
242 } else if (vendor_in &&
243 startswith(vendor, vendor_in) &&
244 (!model_in || startswith(model, model_in))) {
245 /*
246 * Matched vendor and optionally model.
247 *
248 * Note: a short vendor_in or model_in can
249 * give a partial match (that is FOO
250 * matches FOOBAR).
251 */
252 break;
253 }
254 }
255
256 if (retval == 0) {
257 if (vendor_in != NULL || model_in != NULL ||
258 options_in != NULL) {
259 /*
260 * Something matched. Allocate newargv, and store
261 * values found in options_in.
262 */
263 strcpy(buffer, options_in);
264 c = argc_count(buffer) + 2;
265 *newargv = calloc(c, sizeof(**newargv));
266 if (!*newargv)
267 retval = log_oom();
268 else {
269 *argc = c;
270 c = 0;
271 /*
272 * argv[0] at 0 is skipped by getopt, but
273 * store the buffer address there for
274 * later freeing
275 */
276 (*newargv)[c] = buffer;
277 for (c = 1; c < *argc; c++)
278 (*newargv)[c] = strsep(&buffer, " \t");
279 buffer = NULL;
280 }
281 } else {
282 /* No matches */
283 retval = 1;
284 }
285 }
286 return retval;
287 }
288
289 static void help(void) {
290 printf("Usage: %s [OPTION...] DEVICE\n\n"
291 "SCSI device identification.\n\n"
292 " -h --help Print this message\n"
293 " --version Print version of the program\n\n"
294 " -d --device= Device node for SG_IO commands\n"
295 " -f --config= Location of config file\n"
296 " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n"
297 " -s --sg-version=3|4 Use SGv3 or SGv4\n"
298 " -b --blacklisted Treat device as blacklisted\n"
299 " -g --whitelisted Treat device as whitelisted\n"
300 " -u --replace-whitespace Replace all whitespace by underscores\n"
301 " -v --verbose Verbose logging\n"
302 " -x --export Print values as environment keys\n"
303 , program_invocation_short_name);
304
305 }
306
307 static int set_options(int argc, char **argv,
308 char *maj_min_dev) {
309 int option;
310
311 /*
312 * optind is a global extern used by getopt. Since we can call
313 * set_options twice (once for command line, and once for config
314 * file) we have to reset this back to 1.
315 */
316 optind = 1;
317 while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0)
318 switch (option) {
319 case 'b':
320 all_good = false;
321 break;
322
323 case 'd':
324 dev_specified = true;
325 strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
326 break;
327
328 case 'f':
329 strscpy(config_file, MAX_PATH_LEN, optarg);
330 break;
331
332 case 'g':
333 all_good = true;
334 break;
335
336 case 'h':
337 help();
338 exit(EXIT_SUCCESS);
339
340 case 'p':
341 if (streq(optarg, "0x80"))
342 default_page_code = PAGE_80;
343 else if (streq(optarg, "0x83"))
344 default_page_code = PAGE_83;
345 else if (streq(optarg, "pre-spc3-83"))
346 default_page_code = PAGE_83_PRE_SPC3;
347 else {
348 log_error("Unknown page code '%s'", optarg);
349 return -1;
350 }
351 break;
352
353 case 's':
354 sg_version = atoi(optarg);
355 if (sg_version < 3 || sg_version > 4) {
356 log_error("Unknown SG version '%s'", optarg);
357 return -1;
358 }
359 break;
360
361 case 'u':
362 reformat_serial = true;
363 break;
364
365 case 'v':
366 log_set_target(LOG_TARGET_CONSOLE);
367 log_set_max_level(LOG_DEBUG);
368 log_open();
369 break;
370
371 case 'V':
372 printf("%s\n", PACKAGE_VERSION);
373 exit(EXIT_SUCCESS);
374
375 case 'x':
376 export = true;
377 break;
378
379 case '?':
380 return -1;
381
382 default:
383 assert_not_reached("Unknown option");
384 }
385
386 if (optind < argc && !dev_specified) {
387 dev_specified = true;
388 strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
389 }
390
391 return 0;
392 }
393
394 static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) {
395 int retval;
396 int newargc;
397 char **newargv = NULL;
398 int option;
399
400 *good_bad = all_good;
401 *page_code = default_page_code;
402
403 retval = get_file_options(vendor_str, model_str, &newargc, &newargv);
404
405 optind = 1; /* reset this global extern */
406 while (retval == 0) {
407 option = getopt_long(newargc, newargv, "bgp:", options, NULL);
408 if (option == -1)
409 break;
410
411 switch (option) {
412 case 'b':
413 *good_bad = 0;
414 break;
415
416 case 'g':
417 *good_bad = 1;
418 break;
419
420 case 'p':
421 if (streq(optarg, "0x80")) {
422 *page_code = PAGE_80;
423 } else if (streq(optarg, "0x83")) {
424 *page_code = PAGE_83;
425 } else if (streq(optarg, "pre-spc3-83")) {
426 *page_code = PAGE_83_PRE_SPC3;
427 } else {
428 log_error("Unknown page code '%s'", optarg);
429 retval = -1;
430 }
431 break;
432
433 default:
434 log_error("Unknown or bad option '%c' (0x%x)", option, option);
435 retval = -1;
436 break;
437 }
438 }
439
440 if (newargv) {
441 free(newargv[0]);
442 free(newargv);
443 }
444 return retval;
445 }
446
447 static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
448 int retval;
449
450 dev_scsi->use_sg = sg_version;
451
452 retval = scsi_std_inquiry(dev_scsi, path);
453 if (retval)
454 return retval;
455
456 udev_util_encode_string(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str));
457 udev_util_encode_string(dev_scsi->model, model_enc_str, sizeof(model_enc_str));
458
459 util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str));
460 util_replace_chars(vendor_str, NULL);
461 util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str));
462 util_replace_chars(model_str, NULL);
463 set_type(dev_scsi->type, type_str, sizeof(type_str));
464 util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str));
465 util_replace_chars(revision_str, NULL);
466 return 0;
467 }
468
469 /*
470 * scsi_id: try to get an id, if one is found, printf it to stdout.
471 * returns a value passed to exit() - 0 if printed an id, else 1.
472 */
473 static int scsi_id(char *maj_min_dev) {
474 struct scsi_id_device dev_scsi = {};
475 int good_dev;
476 int page_code;
477 int retval = 0;
478
479 if (set_inq_values(&dev_scsi, maj_min_dev) < 0) {
480 retval = 1;
481 goto out;
482 }
483
484 /* get per device (vendor + model) options from the config file */
485 per_dev_options(&dev_scsi, &good_dev, &page_code);
486 if (!good_dev) {
487 retval = 1;
488 goto out;
489 }
490
491 /* read serial number from mode pages (no values for optical drives) */
492 scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
493
494 if (export) {
495 char serial_str[MAX_SERIAL_LEN];
496
497 printf("ID_SCSI=1\n");
498 printf("ID_VENDOR=%s\n", vendor_str);
499 printf("ID_VENDOR_ENC=%s\n", vendor_enc_str);
500 printf("ID_MODEL=%s\n", model_str);
501 printf("ID_MODEL_ENC=%s\n", model_enc_str);
502 printf("ID_REVISION=%s\n", revision_str);
503 printf("ID_TYPE=%s\n", type_str);
504 if (dev_scsi.serial[0] != '\0') {
505 util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
506 util_replace_chars(serial_str, NULL);
507 printf("ID_SERIAL=%s\n", serial_str);
508 util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str));
509 util_replace_chars(serial_str, NULL);
510 printf("ID_SERIAL_SHORT=%s\n", serial_str);
511 }
512 if (dev_scsi.wwn[0] != '\0') {
513 printf("ID_WWN=0x%s\n", dev_scsi.wwn);
514 if (dev_scsi.wwn_vendor_extension[0] != '\0') {
515 printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
516 printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
517 } else
518 printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
519 }
520 if (dev_scsi.tgpt_group[0] != '\0')
521 printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
522 if (dev_scsi.unit_serial_number[0] != '\0')
523 printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
524 goto out;
525 }
526
527 if (dev_scsi.serial[0] == '\0') {
528 retval = 1;
529 goto out;
530 }
531
532 if (reformat_serial) {
533 char serial_str[MAX_SERIAL_LEN];
534
535 util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str));
536 util_replace_chars(serial_str, NULL);
537 printf("%s\n", serial_str);
538 goto out;
539 }
540
541 printf("%s\n", dev_scsi.serial);
542 out:
543 return retval;
544 }
545
546 int main(int argc, char **argv) {
547 int retval = 0;
548 char maj_min_dev[MAX_PATH_LEN];
549 int newargc;
550 char **newargv = NULL;
551
552 log_set_target(LOG_TARGET_AUTO);
553 udev_parse_config();
554 log_parse_environment();
555 log_open();
556
557 /*
558 * Get config file options.
559 */
560 retval = get_file_options(NULL, NULL, &newargc, &newargv);
561 if (retval < 0) {
562 retval = 1;
563 goto exit;
564 }
565 if (retval == 0) {
566 assert(newargv);
567
568 if (set_options(newargc, newargv, maj_min_dev) < 0) {
569 retval = 2;
570 goto exit;
571 }
572 }
573
574 /*
575 * Get command line options (overriding any config file settings).
576 */
577 if (set_options(argc, argv, maj_min_dev) < 0)
578 exit(EXIT_FAILURE);
579
580 if (!dev_specified) {
581 log_error("No device specified.");
582 retval = 1;
583 goto exit;
584 }
585
586 retval = scsi_id(maj_min_dev);
587
588 exit:
589 if (newargv) {
590 free(newargv[0]);
591 free(newargv);
592 }
593 log_close();
594 return retval;
595 }