]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/udev/scsi_id/scsi_id.c
Merge pull request #18007 from fw-strlen/ipv6_masq_and_dnat
[thirdparty/systemd.git] / src / udev / scsi_id / scsi_id.c
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
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 <sys/stat.h>
17 #include <unistd.h>
18
19 #include "alloc-util.h"
20 #include "build.h"
21 #include "device-nodes.h"
22 #include "fd-util.h"
23 #include "scsi_id.h"
24 #include "string-util.h"
25 #include "strxcpyx.h"
26 #include "udev-util.h"
27
28 static const struct option options[] = {
29 { "device", required_argument, NULL, 'd' },
30 { "config", required_argument, NULL, 'f' },
31 { "page", required_argument, NULL, 'p' },
32 { "blacklisted", no_argument, NULL, 'b' },
33 { "whitelisted", no_argument, NULL, 'g' },
34 { "replace-whitespace", no_argument, NULL, 'u' },
35 { "sg-version", required_argument, NULL, 's' },
36 { "verbose", no_argument, NULL, 'v' },
37 { "version", no_argument, NULL, 'V' }, /* don't advertise -V */
38 { "export", no_argument, NULL, 'x' },
39 { "help", no_argument, NULL, 'h' },
40 {}
41 };
42
43 static bool all_good = false;
44 static bool dev_specified = false;
45 static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config";
46 static enum page_code default_page_code = PAGE_UNSPECIFIED;
47 static int sg_version = 4;
48 static bool reformat_serial = false;
49 static bool export = false;
50 static char vendor_str[64];
51 static char model_str[64];
52 static char vendor_enc_str[256];
53 static char model_enc_str[256];
54 static char revision_str[16];
55 static char type_str[16];
56
57 static void set_type(const char *from, char *to, size_t len) {
58 int type_num;
59 char *eptr;
60 const char *type = "generic";
61
62 type_num = strtoul(from, &eptr, 0);
63 if (eptr != from) {
64 switch (type_num) {
65 case 0:
66 type = "disk";
67 break;
68 case 1:
69 type = "tape";
70 break;
71 case 4:
72 type = "optical";
73 break;
74 case 5:
75 type = "cd";
76 break;
77 case 7:
78 type = "optical";
79 break;
80 case 0xe:
81 type = "disk";
82 break;
83 case 0xf:
84 type = "optical";
85 break;
86 default:
87 break;
88 }
89 }
90 strscpy(to, len, type);
91 }
92
93 /*
94 * get_value:
95 *
96 * buf points to an '=' followed by a quoted string ("foo") or a string ending
97 * with a space or ','.
98 *
99 * Return a pointer to the NUL terminated string, returns NULL if no
100 * matches.
101 */
102 static char *get_value(char **buffer) {
103 static const char *quote_string = "\"\n";
104 static const char *comma_string = ",\n";
105 char *val;
106 const char *end;
107
108 if (**buffer == '"') {
109 /*
110 * skip leading quote, terminate when quote seen
111 */
112 (*buffer)++;
113 end = quote_string;
114 } else
115 end = comma_string;
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) {
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)
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) {
240 if (!vendor_in)
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 static int set_options(int argc, char **argv,
307 char *maj_min_dev) {
308 int option;
309
310 /*
311 * optind is a global extern used by getopt. Since we can call
312 * set_options twice (once for command line, and once for config
313 * file) we have to reset this back to 1.
314 */
315 optind = 1;
316 while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0)
317 switch (option) {
318 case 'b':
319 all_good = false;
320 break;
321
322 case 'd':
323 dev_specified = true;
324 strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
325 break;
326
327 case 'f':
328 strscpy(config_file, MAX_PATH_LEN, optarg);
329 break;
330
331 case 'g':
332 all_good = true;
333 break;
334
335 case 'h':
336 help();
337 exit(EXIT_SUCCESS);
338
339 case 'p':
340 if (streq(optarg, "0x80"))
341 default_page_code = PAGE_80;
342 else if (streq(optarg, "0x83"))
343 default_page_code = PAGE_83;
344 else if (streq(optarg, "pre-spc3-83"))
345 default_page_code = PAGE_83_PRE_SPC3;
346 else
347 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
348 "Unknown page code '%s'",
349 optarg);
350 break;
351
352 case 's':
353 sg_version = atoi(optarg);
354 if (sg_version < 3 || sg_version > 4)
355 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
356 "Unknown SG version '%s'",
357 optarg);
358 break;
359
360 case 'u':
361 reformat_serial = true;
362 break;
363
364 case 'v':
365 log_set_target(LOG_TARGET_CONSOLE);
366 log_set_max_level(LOG_DEBUG);
367 log_open();
368 break;
369
370 case 'V':
371 printf("%s\n", GIT_VERSION);
372 exit(EXIT_SUCCESS);
373
374 case 'x':
375 export = true;
376 break;
377
378 case '?':
379 return -1;
380
381 default:
382 assert_not_reached("Unknown option");
383 }
384
385 if (optind < argc && !dev_specified) {
386 dev_specified = true;
387 strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
388 }
389
390 return 0;
391 }
392
393 static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) {
394 int retval;
395 int newargc;
396 char **newargv = NULL;
397 int option;
398
399 *good_bad = all_good;
400 *page_code = default_page_code;
401
402 retval = get_file_options(vendor_str, model_str, &newargc, &newargv);
403
404 optind = 1; /* reset this global extern */
405 while (retval == 0) {
406 option = getopt_long(newargc, newargv, "bgp:", options, NULL);
407 if (option == -1)
408 break;
409
410 switch (option) {
411 case 'b':
412 *good_bad = 0;
413 break;
414
415 case 'g':
416 *good_bad = 1;
417 break;
418
419 case 'p':
420 if (streq(optarg, "0x80")) {
421 *page_code = PAGE_80;
422 } else if (streq(optarg, "0x83")) {
423 *page_code = PAGE_83;
424 } else if (streq(optarg, "pre-spc3-83")) {
425 *page_code = PAGE_83_PRE_SPC3;
426 } else {
427 log_error("Unknown page code '%s'", optarg);
428 retval = -1;
429 }
430 break;
431
432 default:
433 log_error("Unknown or bad option '%c' (0x%x)", option, option);
434 retval = -1;
435 break;
436 }
437 }
438
439 if (newargv) {
440 free(newargv[0]);
441 free(newargv);
442 }
443 return retval;
444 }
445
446 static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
447 int retval;
448
449 dev_scsi->use_sg = sg_version;
450
451 retval = scsi_std_inquiry(dev_scsi, path);
452 if (retval)
453 return retval;
454
455 encode_devnode_name(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str));
456 encode_devnode_name(dev_scsi->model, model_enc_str, sizeof(model_enc_str));
457
458 udev_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str)-1);
459 udev_replace_chars(vendor_str, NULL);
460 udev_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str)-1);
461 udev_replace_chars(model_str, NULL);
462 set_type(dev_scsi->type, type_str, sizeof(type_str));
463 udev_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str)-1);
464 udev_replace_chars(revision_str, NULL);
465 return 0;
466 }
467
468 /*
469 * scsi_id: try to get an id, if one is found, printf it to stdout.
470 * returns a value passed to exit() - 0 if printed an id, else 1.
471 */
472 static int scsi_id(char *maj_min_dev) {
473 struct scsi_id_device dev_scsi = {};
474 int good_dev;
475 int page_code;
476 int retval = 0;
477
478 if (set_inq_values(&dev_scsi, maj_min_dev) < 0) {
479 retval = 1;
480 goto out;
481 }
482
483 /* get per device (vendor + model) options from the config file */
484 per_dev_options(&dev_scsi, &good_dev, &page_code);
485 if (!good_dev) {
486 retval = 1;
487 goto out;
488 }
489
490 /* read serial number from mode pages (no values for optical drives) */
491 scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
492
493 if (export) {
494 char serial_str[MAX_SERIAL_LEN];
495
496 printf("ID_SCSI=1\n");
497 printf("ID_VENDOR=%s\n", vendor_str);
498 printf("ID_VENDOR_ENC=%s\n", vendor_enc_str);
499 printf("ID_MODEL=%s\n", model_str);
500 printf("ID_MODEL_ENC=%s\n", model_enc_str);
501 printf("ID_REVISION=%s\n", revision_str);
502 printf("ID_TYPE=%s\n", type_str);
503 if (dev_scsi.serial[0] != '\0') {
504 udev_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
505 udev_replace_chars(serial_str, NULL);
506 printf("ID_SERIAL=%s\n", serial_str);
507 udev_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str)-1);
508 udev_replace_chars(serial_str, NULL);
509 printf("ID_SERIAL_SHORT=%s\n", serial_str);
510 }
511 if (dev_scsi.wwn[0] != '\0') {
512 printf("ID_WWN=0x%s\n", dev_scsi.wwn);
513 if (dev_scsi.wwn_vendor_extension[0] != '\0') {
514 printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
515 printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
516 } else
517 printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
518 }
519 if (dev_scsi.tgpt_group[0] != '\0')
520 printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
521 if (dev_scsi.unit_serial_number[0] != '\0')
522 printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
523 goto out;
524 }
525
526 if (dev_scsi.serial[0] == '\0') {
527 retval = 1;
528 goto out;
529 }
530
531 if (reformat_serial) {
532 char serial_str[MAX_SERIAL_LEN];
533
534 udev_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
535 udev_replace_chars(serial_str, NULL);
536 printf("%s\n", serial_str);
537 goto out;
538 }
539
540 printf("%s\n", dev_scsi.serial);
541 out:
542 return retval;
543 }
544
545 int main(int argc, char **argv) {
546 int retval = 0;
547 char maj_min_dev[MAX_PATH_LEN];
548 int newargc;
549 char **newargv = NULL;
550
551 log_set_target(LOG_TARGET_AUTO);
552 udev_parse_config();
553 log_parse_environment();
554 log_open();
555
556 /*
557 * Get config file options.
558 */
559 retval = get_file_options(NULL, NULL, &newargc, &newargv);
560 if (retval < 0) {
561 retval = 1;
562 goto exit;
563 }
564 if (retval == 0) {
565 assert(newargv);
566
567 if (set_options(newargc, newargv, maj_min_dev) < 0) {
568 retval = 2;
569 goto exit;
570 }
571 }
572
573 /*
574 * Get command line options (overriding any config file settings).
575 */
576 if (set_options(argc, argv, maj_min_dev) < 0)
577 exit(EXIT_FAILURE);
578
579 if (!dev_specified) {
580 log_error("No device specified.");
581 retval = 1;
582 goto exit;
583 }
584
585 retval = scsi_id(maj_min_dev);
586
587 exit:
588 if (newargv) {
589 free(newargv[0]);
590 free(newargv);
591 }
592 log_close();
593 return retval;
594 }