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