]> git.ipfire.org Git - thirdparty/systemd.git/blame - 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
CommitLineData
e7145211 1/* SPDX-License-Identifier: GPL-2.0+ */
e0f83fbe 2/*
810adae9
LP
3 * Copyright © IBM Corp. 2003
4 * Copyright © SUSE Linux Products GmbH, 2006
c521693b
GKH
5 */
6
07630cea
LP
7#include <ctype.h>
8#include <errno.h>
9#include <fcntl.h>
10#include <getopt.h>
11#include <signal.h>
ed142bdb
ZJS
12#include <stdarg.h>
13#include <stdbool.h>
07630cea
LP
14#include <stdio.h>
15#include <stdlib.h>
c521693b 16#include <string.h>
c521693b 17#include <sys/stat.h>
07630cea 18#include <unistd.h>
c521693b 19
152d0efa 20#include "alloc-util.h"
681bd2c5 21#include "build.h"
3ffd4af2 22#include "fd-util.h"
5ea78a39 23#include "libudev-util.h"
1aa1e248 24#include "scsi_id.h"
07630cea 25#include "string-util.h"
5ea78a39 26#include "strxcpyx.h"
ed142bdb 27#include "udev-util.h"
c521693b 28
9eaa50d0 29static const struct option options[] = {
ed142bdb
ZJS
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' },
7643ac9a 38 { "version", no_argument, NULL, 'V' }, /* don't advertise -V */
ed142bdb
ZJS
39 { "export", no_argument, NULL, 'x' },
40 { "help", no_argument, NULL, 'h' },
912541b0 41 {}
9eaa50d0
KS
42};
43
ed142bdb
ZJS
44static bool all_good = false;
45static bool dev_specified = false;
ff944daa 46static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config";
ed142bdb 47static enum page_code default_page_code = PAGE_UNSPECIFIED;
025570aa 48static int sg_version = 4;
ed142bdb
ZJS
49static bool reformat_serial = false;
50static bool export = false;
34129109
KS
51static char vendor_str[64];
52static char model_str[64];
ad88f940
DZ
53static char vendor_enc_str[256];
54static char model_enc_str[256];
aaff09a3
KS
55static char revision_str[16];
56static char type_str[16];
c521693b 57
057fc051 58static void set_type(const char *from, char *to, size_t len) {
912541b0
KS
59 int type_num;
60 char *eptr;
4b0060e6 61 const char *type = "generic";
912541b0
KS
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 }
d5a89d7d 91 strscpy(to, len, type);
34129109
KS
92}
93
c521693b
GKH
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 */
057fc051 103static char *get_value(char **buffer) {
4b0060e6
KS
104 static const char *quote_string = "\"\n";
105 static const char *comma_string = ",\n";
912541b0 106 char *val;
4b0060e6 107 const char *end;
912541b0
KS
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;
c521693b
GKH
129}
130
057fc051 131static int argc_count(char *opts) {
912541b0
KS
132 int i = 0;
133 while (*opts != '\0')
134 if (*opts++ == ' ')
135 i++;
136 return i;
c521693b
GKH
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 */
efc2774c
YW
148static int get_file_options(const char *vendor, const char *model,
149 int *argc, char ***newargv) {
3ad97058 150 _cleanup_free_ char *buffer = NULL;
ed142bdb 151 _cleanup_fclose_ FILE *f;
912541b0
KS
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
ed142bdb
ZJS
159 f = fopen(config_file, "re");
160 if (f == NULL) {
161 if (errno == ENOENT)
912541b0 162 return 1;
ed142bdb 163 else {
56f64d95 164 log_error_errno(errno, "can't open %s: %m", config_file);
912541b0
KS
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);
ed142bdb 175 if (!buffer)
0d0f0c50 176 return log_oom();
912541b0
KS
177
178 *newargv = NULL;
179 lineno = 0;
57255510 180 for (;;) {
912541b0
KS
181 vendor_in = model_in = options_in = NULL;
182
ed142bdb 183 buf = fgets(buffer, MAX_BUFFER_LEN, f);
912541b0
KS
184 if (buf == NULL)
185 break;
186 lineno++;
187 if (buf[strlen(buffer) - 1] != '\n') {
9f6445e3 188 log_error("Config file line %d too long", lineno);
912541b0
KS
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
912541b0 203 str1 = strsep(&buf, "=");
b43d1d01 204 if (str1 && strcaseeq(str1, "VENDOR")) {
912541b0
KS
205 str1 = get_value(&buf);
206 if (!str1) {
0d0f0c50 207 retval = log_oom();
912541b0
KS
208 break;
209 }
210 vendor_in = str1;
211
212 str1 = strsep(&buf, "=");
b43d1d01 213 if (str1 && strcaseeq(str1, "MODEL")) {
912541b0
KS
214 str1 = get_value(&buf);
215 if (!str1) {
0d0f0c50 216 retval = log_oom();
912541b0
KS
217 break;
218 }
219 model_in = str1;
220 str1 = strsep(&buf, "=");
221 }
222 }
223
b43d1d01 224 if (str1 && strcaseeq(str1, "OPTIONS")) {
912541b0
KS
225 str1 = get_value(&buf);
226 if (!str1) {
0d0f0c50 227 retval = log_oom();
912541b0
KS
228 break;
229 }
230 options_in = str1;
231 }
baa30fbc 232
912541b0
KS
233 /*
234 * Only allow: [vendor=foo[,model=bar]]options=stuff
235 */
236 if (!options_in || (!vendor_in && model_in)) {
9f6445e3 237 log_error("Error parsing config file line %d '%s'", lineno, buffer);
912541b0
KS
238 retval = -1;
239 break;
240 }
241 if (vendor == NULL) {
baa30fbc 242 if (vendor_in == NULL)
912541b0 243 break;
ed142bdb 244 } else if (vendor_in &&
0caa9946
YW
245 startswith(vendor, vendor_in) &&
246 (!model_in || startswith(model, model_in))) {
912541b0
KS
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 */
912541b0 254 break;
912541b0
KS
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));
1f6b4113 268 if (!*newargv)
0d0f0c50 269 retval = log_oom();
1f6b4113 270 else {
912541b0
KS
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");
3ad97058 281 buffer = NULL;
912541b0
KS
282 }
283 } else {
284 /* No matches */
285 retval = 1;
286 }
287 }
912541b0 288 return retval;
c521693b
GKH
289}
290
7643ac9a 291static void help(void) {
5ac0162c
LP
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);
7643ac9a
ZJS
306
307}
308
efc2774c
YW
309static int set_options(int argc, char **argv,
310 char *maj_min_dev) {
912541b0
KS
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;
ebc6f34a 319 while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0)
912541b0
KS
320 switch (option) {
321 case 'b':
ed142bdb 322 all_good = false;
912541b0
KS
323 break;
324
325 case 'd':
ed142bdb 326 dev_specified = true;
d5a89d7d 327 strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
912541b0
KS
328 break;
329
912541b0 330 case 'f':
d5a89d7d 331 strscpy(config_file, MAX_PATH_LEN, optarg);
912541b0
KS
332 break;
333
334 case 'g':
ed142bdb 335 all_good = true;
912541b0
KS
336 break;
337
338 case 'h':
7643ac9a 339 help();
a45d7127 340 exit(EXIT_SUCCESS);
912541b0
KS
341
342 case 'p':
ed142bdb 343 if (streq(optarg, "0x80"))
912541b0 344 default_page_code = PAGE_80;
ed142bdb 345 else if (streq(optarg, "0x83"))
912541b0 346 default_page_code = PAGE_83;
ed142bdb 347 else if (streq(optarg, "pre-spc3-83"))
912541b0 348 default_page_code = PAGE_83_PRE_SPC3;
ed142bdb 349 else {
9f6445e3 350 log_error("Unknown page code '%s'", optarg);
912541b0
KS
351 return -1;
352 }
353 break;
354
355 case 's':
356 sg_version = atoi(optarg);
357 if (sg_version < 3 || sg_version > 4) {
9f6445e3 358 log_error("Unknown SG version '%s'", optarg);
912541b0
KS
359 return -1;
360 }
361 break;
362
363 case 'u':
ed142bdb 364 reformat_serial = true;
912541b0
KS
365 break;
366
367 case 'v':
5168f84a
LP
368 log_set_target(LOG_TARGET_CONSOLE);
369 log_set_max_level(LOG_DEBUG);
5168f84a 370 log_open();
912541b0
KS
371 break;
372
373 case 'V':
681bd2c5 374 printf("%s\n", GIT_VERSION);
a45d7127 375 exit(EXIT_SUCCESS);
912541b0 376
ed142bdb
ZJS
377 case 'x':
378 export = true;
379 break;
380
381 case '?':
382 return -1;
383
912541b0 384 default:
ed142bdb 385 assert_not_reached("Unknown option");
912541b0 386 }
ed142bdb 387
912541b0 388 if (optind < argc && !dev_specified) {
ed142bdb 389 dev_specified = true;
d5a89d7d 390 strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
912541b0 391 }
ed142bdb 392
912541b0 393 return 0;
c521693b
GKH
394}
395
efc2774c 396static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) {
912541b0
KS
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
efc2774c 405 retval = get_file_options(vendor_str, model_str, &newargc, &newargv);
912541b0
KS
406
407 optind = 1; /* reset this global extern */
408 while (retval == 0) {
ed142bdb 409 option = getopt_long(newargc, newargv, "bgp:", options, NULL);
912541b0
KS
410 if (option == -1)
411 break;
412
912541b0
KS
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':
090be865 423 if (streq(optarg, "0x80")) {
912541b0 424 *page_code = PAGE_80;
090be865 425 } else if (streq(optarg, "0x83")) {
912541b0 426 *page_code = PAGE_83;
090be865 427 } else if (streq(optarg, "pre-spc3-83")) {
912541b0
KS
428 *page_code = PAGE_83_PRE_SPC3;
429 } else {
9f6445e3 430 log_error("Unknown page code '%s'", optarg);
912541b0
KS
431 retval = -1;
432 }
433 break;
434
435 default:
9f6445e3 436 log_error("Unknown or bad option '%c' (0x%x)", option, option);
912541b0
KS
437 retval = -1;
438 break;
439 }
440 }
441
442 if (newargv) {
443 free(newargv[0]);
444 free(newargv);
445 }
446 return retval;
c521693b
GKH
447}
448
efc2774c 449static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
912541b0 450 int retval;
87cf9f5a 451
912541b0 452 dev_scsi->use_sg = sg_version;
78d9ecfd 453
efc2774c 454 retval = scsi_std_inquiry(dev_scsi, path);
912541b0
KS
455 if (retval)
456 return retval;
87cf9f5a 457
912541b0
KS
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));
ad88f940 460
c0c59154 461 util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str)-1);
912541b0 462 util_replace_chars(vendor_str, NULL);
c0c59154 463 util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str)-1);
912541b0
KS
464 util_replace_chars(model_str, NULL);
465 set_type(dev_scsi->type, type_str, sizeof(type_str));
c0c59154 466 util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str)-1);
912541b0
KS
467 util_replace_chars(revision_str, NULL);
468 return 0;
87cf9f5a
HR
469}
470
c521693b
GKH
471/*
472 * scsi_id: try to get an id, if one is found, printf it to stdout.
caf4c97a 473 * returns a value passed to exit() - 0 if printed an id, else 1.
c521693b 474 */
efc2774c 475static int scsi_id(char *maj_min_dev) {
ed142bdb 476 struct scsi_id_device dev_scsi = {};
912541b0
KS
477 int good_dev;
478 int page_code;
479 int retval = 0;
480
efc2774c 481 if (set_inq_values(&dev_scsi, maj_min_dev) < 0) {
912541b0
KS
482 retval = 1;
483 goto out;
484 }
485
486 /* get per device (vendor + model) options from the config file */
efc2774c 487 per_dev_options(&dev_scsi, &good_dev, &page_code);
912541b0
KS
488 if (!good_dev) {
489 retval = 1;
490 goto out;
491 }
492
493 /* read serial number from mode pages (no values for optical drives) */
efc2774c 494 scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
912541b0
KS
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') {
c0c59154 507 util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
912541b0
KS
508 util_replace_chars(serial_str, NULL);
509 printf("ID_SERIAL=%s\n", serial_str);
c0c59154 510 util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str)-1);
912541b0
KS
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);
1f6b4113 519 } else
912541b0 520 printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
912541b0 521 }
1f6b4113 522 if (dev_scsi.tgpt_group[0] != '\0')
912541b0 523 printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
1f6b4113 524 if (dev_scsi.unit_serial_number[0] != '\0')
912541b0 525 printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
912541b0
KS
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
c0c59154 537 util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
912541b0
KS
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);
caf4c97a 544out:
912541b0 545 return retval;
c521693b
GKH
546}
547
8e766630 548int main(int argc, char **argv) {
912541b0
KS
549 int retval = 0;
550 char maj_min_dev[MAX_PATH_LEN];
551 int newargc;
ed142bdb 552 char **newargv = NULL;
912541b0 553
b237a168
ZJS
554 log_set_target(LOG_TARGET_AUTO);
555 udev_parse_config();
5168f84a
LP
556 log_parse_environment();
557 log_open();
558
912541b0
KS
559 /*
560 * Get config file options.
561 */
efc2774c 562 retval = get_file_options(NULL, NULL, &newargc, &newargv);
912541b0
KS
563 if (retval < 0) {
564 retval = 1;
565 goto exit;
566 }
ed142bdb
ZJS
567 if (retval == 0) {
568 assert(newargv);
569
efc2774c 570 if (set_options(newargc, newargv, maj_min_dev) < 0) {
912541b0
KS
571 retval = 2;
572 goto exit;
573 }
912541b0
KS
574 }
575
576 /*
577 * Get command line options (overriding any config file settings).
578 */
efc2774c 579 if (set_options(argc, argv, maj_min_dev) < 0)
a45d7127 580 exit(EXIT_FAILURE);
912541b0
KS
581
582 if (!dev_specified) {
5ac0162c 583 log_error("No device specified.");
912541b0
KS
584 retval = 1;
585 goto exit;
586 }
587
efc2774c 588 retval = scsi_id(maj_min_dev);
1aa1e248
KS
589
590exit:
ed142bdb
ZJS
591 if (newargv) {
592 free(newargv[0]);
593 free(newargv);
594 }
baa30fbc 595 log_close();
912541b0 596 return retval;
c521693b 597}