]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/udev/scsi_id/scsi_id.c
Merge pull request #32993 from poettering/cryptenroll-no-pcr
[thirdparty/systemd.git] / src / udev / scsi_id / scsi_id.c
CommitLineData
f13467ec 1/* SPDX-License-Identifier: GPL-2.0-or-later */
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 14#include <stdio.h>
c521693b 15#include <sys/stat.h>
07630cea 16#include <unistd.h>
c521693b 17
152d0efa 18#include "alloc-util.h"
22148897 19#include "build.h"
2b0f5113 20#include "device-nodes.h"
a9a49d2f 21#include "extract-word.h"
3ffd4af2 22#include "fd-util.h"
3f66ded5 23#include "fileio.h"
1001167c 24#include "parse-util.h"
1aa1e248 25#include "scsi_id.h"
07630cea 26#include "string-util.h"
a9a49d2f 27#include "strv.h"
5ea78a39 28#include "strxcpyx.h"
ed142bdb 29#include "udev-util.h"
c521693b 30
9eaa50d0 31static const struct option options[] = {
ed142bdb
ZJS
32 { "device", required_argument, NULL, 'd' },
33 { "config", required_argument, NULL, 'f' },
34 { "page", required_argument, NULL, 'p' },
6fdc9fbc
YW
35 { "denylisted", no_argument, NULL, 'b' },
36 { "allowlisted", no_argument, NULL, 'g' },
37 { "blacklisted", no_argument, NULL, 'b' }, /* backward compat */
38 { "whitelisted", no_argument, NULL, 'g' }, /* backward compat */
ed142bdb
ZJS
39 { "replace-whitespace", no_argument, NULL, 'u' },
40 { "sg-version", required_argument, NULL, 's' },
41 { "verbose", no_argument, NULL, 'v' },
7643ac9a 42 { "version", no_argument, NULL, 'V' }, /* don't advertise -V */
ed142bdb
ZJS
43 { "export", no_argument, NULL, 'x' },
44 { "help", no_argument, NULL, 'h' },
912541b0 45 {}
9eaa50d0
KS
46};
47
ed142bdb
ZJS
48static bool all_good = false;
49static bool dev_specified = false;
ff944daa 50static char config_file[MAX_PATH_LEN] = "/etc/scsi_id.config";
ed142bdb 51static enum page_code default_page_code = PAGE_UNSPECIFIED;
025570aa 52static int sg_version = 4;
ed142bdb
ZJS
53static bool reformat_serial = false;
54static bool export = false;
34129109
KS
55static char vendor_str[64];
56static char model_str[64];
ad88f940
DZ
57static char vendor_enc_str[256];
58static char model_enc_str[256];
aaff09a3
KS
59static char revision_str[16];
60static char type_str[16];
c521693b 61
4dce1b9f
YW
62static void set_type(unsigned type_num, char *to, size_t len) {
63 const char *type;
912541b0 64
4dce1b9f
YW
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;
204594ec 87 case 0x14:
88 /*
89 * Use "zbc" here to be brief and consistent with "lsscsi" command.
90 * Other tools, e.g., "sg3_utils" would say "host managed zoned block".
91 */
92 type = "zbc";
93 break;
4dce1b9f
YW
94 default:
95 type = "generic";
96 break;
912541b0 97 }
d5a89d7d 98 strscpy(to, len, type);
34129109
KS
99}
100
c521693b
GKH
101/*
102 * get_file_options:
103 *
104 * If vendor == NULL, find a line in the config file with only "OPTIONS=";
105 * if vendor and model are set find the first OPTIONS line in the config
106 * file that matches. Set argc and argv to match the OPTIONS string.
107 *
108 * vendor and model can end in '\n'.
109 */
efc2774c
YW
110static int get_file_options(const char *vendor, const char *model,
111 int *argc, char ***newargv) {
3f66ded5 112 _cleanup_free_ char *vendor_in = NULL, *model_in = NULL, *options_in = NULL; /* read in from file */
a9a49d2f 113 _cleanup_strv_free_ char **options_argv = NULL;
c2b2df60 114 _cleanup_fclose_ FILE *f = NULL;
a9a49d2f 115 int lineno, r;
912541b0 116
ed142bdb 117 f = fopen(config_file, "re");
4e361acc 118 if (!f) {
ed142bdb 119 if (errno == ENOENT)
912541b0 120 return 1;
ed142bdb 121 else {
56f64d95 122 log_error_errno(errno, "can't open %s: %m", config_file);
912541b0
KS
123 return -1;
124 }
125 }
126
912541b0
KS
127 *newargv = NULL;
128 lineno = 0;
57255510 129 for (;;) {
3f66ded5
LB
130 _cleanup_free_ char *buffer = NULL, *key = NULL, *value = NULL;
131 const char *buf;
a9a49d2f 132
912541b0
KS
133 vendor_in = model_in = options_in = NULL;
134
3f66ded5
LB
135 r = read_line(f, MAX_BUFFER_LEN, &buffer);
136 if (r < 0)
137 return log_error_errno(r, "read_line() on line %d of %s failed: %m", lineno, config_file);
138 if (r == 0)
912541b0 139 break;
3f66ded5 140 buf = buffer;
912541b0 141 lineno++;
912541b0
KS
142
143 while (isspace(*buf))
144 buf++;
145
146 /* blank or all whitespace line */
147 if (*buf == '\0')
148 continue;
149
150 /* comment line */
151 if (*buf == '#')
152 continue;
153
4f495126 154 r = extract_many_words(&buf, "=\",\n", 0, &key, &value);
a9a49d2f
LB
155 if (r < 2)
156 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer);
912541b0 157
a9a49d2f
LB
158 if (strcaseeq(key, "VENDOR")) {
159 vendor_in = TAKE_PTR(value);
160
161 key = mfree(key);
4f495126 162 r = extract_many_words(&buf, "=\",\n", 0, &key, &value);
a9a49d2f
LB
163 if (r < 2)
164 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer);
165
166 if (strcaseeq(key, "MODEL")) {
167 model_in = TAKE_PTR(value);
168
169 key = mfree(key);
4f495126 170 r = extract_many_words(&buf, "=\",\n", 0, &key, &value);
a9a49d2f
LB
171 if (r < 2)
172 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer);
912541b0 173 }
912541b0 174 }
baa30fbc 175
a9a49d2f
LB
176 if (strcaseeq(key, "OPTIONS"))
177 options_in = TAKE_PTR(value);
178
912541b0
KS
179 /*
180 * Only allow: [vendor=foo[,model=bar]]options=stuff
181 */
a9a49d2f
LB
182 if (!options_in || (!vendor_in && model_in))
183 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer);
1d6cc5d0 184 if (!vendor) {
4e361acc 185 if (!vendor_in)
912541b0 186 break;
ed142bdb 187 } else if (vendor_in &&
0caa9946
YW
188 startswith(vendor, vendor_in) &&
189 (!model_in || startswith(model, model_in))) {
912541b0
KS
190 /*
191 * Matched vendor and optionally model.
192 *
193 * Note: a short vendor_in or model_in can
194 * give a partial match (that is FOO
195 * matches FOOBAR).
196 */
912541b0 197 break;
912541b0 198 }
912541b0 199
a9a49d2f
LB
200 vendor_in = mfree(vendor_in);
201 model_in = mfree(model_in);
202 options_in = mfree(options_in);
203
912541b0 204 }
a9a49d2f
LB
205
206 if (vendor_in == NULL && model_in == NULL && options_in == NULL)
207 return 1; /* No matches */
208
209 /*
210 * Something matched. Allocate newargv, and store
211 * values found in options_in.
212 */
213 options_argv = strv_split(options_in, " \t");
214 if (!options_argv)
215 return log_oom();
216 r = strv_prepend(&options_argv, ""); /* getopt skips over argv[0] */
217 if (r < 0)
218 return r;
219 *newargv = TAKE_PTR(options_argv);
220 *argc = strv_length(*newargv);
221
222 return 0;
c521693b
GKH
223}
224
7643ac9a 225static void help(void) {
5ac0162c
LP
226 printf("Usage: %s [OPTION...] DEVICE\n\n"
227 "SCSI device identification.\n\n"
228 " -h --help Print this message\n"
229 " --version Print version of the program\n\n"
230 " -d --device= Device node for SG_IO commands\n"
231 " -f --config= Location of config file\n"
232 " -p --page=0x80|0x83|pre-spc3-83 SCSI page (0x80, 0x83, pre-spc3-83)\n"
233 " -s --sg-version=3|4 Use SGv3 or SGv4\n"
6fdc9fbc
YW
234 " -b --denylisted Treat device as denylisted\n"
235 " -g --allowlisted Treat device as allowlisted\n"
5ac0162c
LP
236 " -u --replace-whitespace Replace all whitespace by underscores\n"
237 " -v --verbose Verbose logging\n"
bc556335
DDM
238 " -x --export Print values as environment keys\n",
239 program_invocation_short_name);
7643ac9a
ZJS
240}
241
efc2774c
YW
242static int set_options(int argc, char **argv,
243 char *maj_min_dev) {
912541b0
KS
244 int option;
245
246 /*
247 * optind is a global extern used by getopt. Since we can call
248 * set_options twice (once for command line, and once for config
249 * file) we have to reset this back to 1.
250 */
251 optind = 1;
ebc6f34a 252 while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0)
912541b0
KS
253 switch (option) {
254 case 'b':
ed142bdb 255 all_good = false;
912541b0
KS
256 break;
257
258 case 'd':
ed142bdb 259 dev_specified = true;
d5a89d7d 260 strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
912541b0
KS
261 break;
262
912541b0 263 case 'f':
d5a89d7d 264 strscpy(config_file, MAX_PATH_LEN, optarg);
912541b0
KS
265 break;
266
267 case 'g':
ed142bdb 268 all_good = true;
912541b0
KS
269 break;
270
271 case 'h':
7643ac9a 272 help();
a45d7127 273 exit(EXIT_SUCCESS);
912541b0
KS
274
275 case 'p':
ed142bdb 276 if (streq(optarg, "0x80"))
912541b0 277 default_page_code = PAGE_80;
ed142bdb 278 else if (streq(optarg, "0x83"))
912541b0 279 default_page_code = PAGE_83;
ed142bdb 280 else if (streq(optarg, "pre-spc3-83"))
912541b0 281 default_page_code = PAGE_83_PRE_SPC3;
8b803ad6 282 else
ed0cb346
FS
283 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
284 "Unknown page code '%s'",
285 optarg);
912541b0
KS
286 break;
287
288 case 's':
289 sg_version = atoi(optarg);
8b803ad6 290 if (sg_version < 3 || sg_version > 4)
ed0cb346
FS
291 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
292 "Unknown SG version '%s'",
293 optarg);
912541b0
KS
294 break;
295
296 case 'u':
ed142bdb 297 reformat_serial = true;
912541b0
KS
298 break;
299
300 case 'v':
5168f84a
LP
301 log_set_target(LOG_TARGET_CONSOLE);
302 log_set_max_level(LOG_DEBUG);
5168f84a 303 log_open();
912541b0
KS
304 break;
305
306 case 'V':
22148897 307 version();
a45d7127 308 exit(EXIT_SUCCESS);
912541b0 309
ed142bdb
ZJS
310 case 'x':
311 export = true;
312 break;
313
314 case '?':
315 return -1;
316
912541b0 317 default:
04499a70 318 assert_not_reached();
912541b0 319 }
ed142bdb 320
912541b0 321 if (optind < argc && !dev_specified) {
ed142bdb 322 dev_specified = true;
d5a89d7d 323 strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
912541b0 324 }
ed142bdb 325
912541b0 326 return 0;
c521693b
GKH
327}
328
efc2774c 329static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) {
a9a49d2f 330 _cleanup_strv_free_ char **newargv = NULL;
912541b0
KS
331 int retval;
332 int newargc;
912541b0
KS
333 int option;
334
335 *good_bad = all_good;
336 *page_code = default_page_code;
337
efc2774c 338 retval = get_file_options(vendor_str, model_str, &newargc, &newargv);
912541b0
KS
339
340 optind = 1; /* reset this global extern */
341 while (retval == 0) {
ed142bdb 342 option = getopt_long(newargc, newargv, "bgp:", options, NULL);
912541b0
KS
343 if (option == -1)
344 break;
345
912541b0
KS
346 switch (option) {
347 case 'b':
348 *good_bad = 0;
349 break;
350
351 case 'g':
352 *good_bad = 1;
353 break;
354
355 case 'p':
090be865 356 if (streq(optarg, "0x80")) {
912541b0 357 *page_code = PAGE_80;
090be865 358 } else if (streq(optarg, "0x83")) {
912541b0 359 *page_code = PAGE_83;
090be865 360 } else if (streq(optarg, "pre-spc3-83")) {
912541b0
KS
361 *page_code = PAGE_83_PRE_SPC3;
362 } else {
9f6445e3 363 log_error("Unknown page code '%s'", optarg);
912541b0
KS
364 retval = -1;
365 }
366 break;
367
368 default:
5570a097 369 log_error("Unknown or bad option '%c' (0x%x)", option, (unsigned) option);
912541b0
KS
370 retval = -1;
371 break;
372 }
373 }
374
912541b0 375 return retval;
c521693b
GKH
376}
377
efc2774c 378static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
912541b0 379 int retval;
87cf9f5a 380
912541b0 381 dev_scsi->use_sg = sg_version;
78d9ecfd 382
efc2774c 383 retval = scsi_std_inquiry(dev_scsi, path);
912541b0
KS
384 if (retval)
385 return retval;
87cf9f5a 386
2b0f5113
YW
387 encode_devnode_name(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str));
388 encode_devnode_name(dev_scsi->model, model_enc_str, sizeof(model_enc_str));
ad88f940 389
5953d8b9 390 udev_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str)-1);
393fcaf7 391 udev_replace_chars(vendor_str, NULL);
5953d8b9 392 udev_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str)-1);
393fcaf7 393 udev_replace_chars(model_str, NULL);
912541b0 394 set_type(dev_scsi->type, type_str, sizeof(type_str));
5953d8b9 395 udev_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str)-1);
393fcaf7 396 udev_replace_chars(revision_str, NULL);
912541b0 397 return 0;
87cf9f5a
HR
398}
399
c521693b
GKH
400/*
401 * scsi_id: try to get an id, if one is found, printf it to stdout.
caf4c97a 402 * returns a value passed to exit() - 0 if printed an id, else 1.
c521693b 403 */
efc2774c 404static int scsi_id(char *maj_min_dev) {
ed142bdb 405 struct scsi_id_device dev_scsi = {};
912541b0
KS
406 int good_dev;
407 int page_code;
408 int retval = 0;
409
efc2774c 410 if (set_inq_values(&dev_scsi, maj_min_dev) < 0) {
912541b0
KS
411 retval = 1;
412 goto out;
413 }
414
415 /* get per device (vendor + model) options from the config file */
efc2774c 416 per_dev_options(&dev_scsi, &good_dev, &page_code);
912541b0
KS
417 if (!good_dev) {
418 retval = 1;
419 goto out;
420 }
421
422 /* read serial number from mode pages (no values for optical drives) */
efc2774c 423 scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
912541b0
KS
424
425 if (export) {
426 char serial_str[MAX_SERIAL_LEN];
427
428 printf("ID_SCSI=1\n");
429 printf("ID_VENDOR=%s\n", vendor_str);
430 printf("ID_VENDOR_ENC=%s\n", vendor_enc_str);
431 printf("ID_MODEL=%s\n", model_str);
432 printf("ID_MODEL_ENC=%s\n", model_enc_str);
433 printf("ID_REVISION=%s\n", revision_str);
434 printf("ID_TYPE=%s\n", type_str);
435 if (dev_scsi.serial[0] != '\0') {
5953d8b9 436 udev_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
393fcaf7 437 udev_replace_chars(serial_str, NULL);
912541b0 438 printf("ID_SERIAL=%s\n", serial_str);
5953d8b9 439 udev_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str)-1);
393fcaf7 440 udev_replace_chars(serial_str, NULL);
912541b0
KS
441 printf("ID_SERIAL_SHORT=%s\n", serial_str);
442 }
443 if (dev_scsi.wwn[0] != '\0') {
444 printf("ID_WWN=0x%s\n", dev_scsi.wwn);
445 if (dev_scsi.wwn_vendor_extension[0] != '\0') {
446 printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
447 printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
1f6b4113 448 } else
912541b0 449 printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
912541b0 450 }
1f6b4113 451 if (dev_scsi.tgpt_group[0] != '\0')
912541b0 452 printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
1f6b4113 453 if (dev_scsi.unit_serial_number[0] != '\0')
912541b0 454 printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
912541b0
KS
455 goto out;
456 }
457
458 if (dev_scsi.serial[0] == '\0') {
459 retval = 1;
460 goto out;
461 }
462
463 if (reformat_serial) {
464 char serial_str[MAX_SERIAL_LEN];
465
5953d8b9 466 udev_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
393fcaf7 467 udev_replace_chars(serial_str, NULL);
912541b0
KS
468 printf("%s\n", serial_str);
469 goto out;
470 }
471
472 printf("%s\n", dev_scsi.serial);
caf4c97a 473out:
912541b0 474 return retval;
c521693b
GKH
475}
476
8e766630 477int main(int argc, char **argv) {
a9a49d2f 478 _cleanup_strv_free_ char **newargv = NULL;
912541b0
KS
479 int retval = 0;
480 char maj_min_dev[MAX_PATH_LEN];
481 int newargc;
912541b0 482
aa976d87
DDM
483 (void) udev_parse_config();
484 log_setup();
5168f84a 485
912541b0
KS
486 /*
487 * Get config file options.
488 */
efc2774c 489 retval = get_file_options(NULL, NULL, &newargc, &newargv);
912541b0
KS
490 if (retval < 0) {
491 retval = 1;
492 goto exit;
493 }
ed142bdb
ZJS
494 if (retval == 0) {
495 assert(newargv);
496
efc2774c 497 if (set_options(newargc, newargv, maj_min_dev) < 0) {
912541b0
KS
498 retval = 2;
499 goto exit;
500 }
912541b0
KS
501 }
502
503 /*
504 * Get command line options (overriding any config file settings).
505 */
efc2774c 506 if (set_options(argc, argv, maj_min_dev) < 0)
a45d7127 507 exit(EXIT_FAILURE);
912541b0
KS
508
509 if (!dev_specified) {
5ac0162c 510 log_error("No device specified.");
912541b0
KS
511 retval = 1;
512 goto exit;
513 }
514
efc2774c 515 retval = scsi_id(maj_min_dev);
1aa1e248
KS
516
517exit:
baa30fbc 518 log_close();
912541b0 519 return retval;
c521693b 520}