Branch data Line data Source code
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 : 0 : static void set_type(const char *from, char *to, size_t len) {
59 : : int type_num;
60 : : char *eptr;
61 : 0 : const char *type = "generic";
62 : :
63 : 0 : type_num = strtoul(from, &eptr, 0);
64 [ # # ]: 0 : if (eptr != from) {
65 [ # # # # : 0 : switch (type_num) {
# # # # ]
66 : 0 : case 0:
67 : 0 : type = "disk";
68 : 0 : break;
69 : 0 : case 1:
70 : 0 : type = "tape";
71 : 0 : break;
72 : 0 : case 4:
73 : 0 : type = "optical";
74 : 0 : break;
75 : 0 : case 5:
76 : 0 : type = "cd";
77 : 0 : break;
78 : 0 : case 7:
79 : 0 : type = "optical";
80 : 0 : break;
81 : 0 : case 0xe:
82 : 0 : type = "disk";
83 : 0 : break;
84 : 0 : case 0xf:
85 : 0 : type = "optical";
86 : 0 : break;
87 : 0 : default:
88 : 0 : break;
89 : : }
90 : 0 : }
91 : 0 : strscpy(to, len, type);
92 : 0 : }
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 : 0 : 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 [ # # ]: 0 : if (**buffer == '"') {
110 : : /*
111 : : * skip leading quote, terminate when quote seen
112 : : */
113 : 0 : (*buffer)++;
114 : 0 : end = quote_string;
115 : : } else {
116 : 0 : end = comma_string;
117 : : }
118 : 0 : val = strsep(buffer, end);
119 [ # # # # ]: 0 : if (val && end == quote_string)
120 : : /*
121 : : * skip trailing quote
122 : : */
123 : 0 : (*buffer)++;
124 : :
125 [ # # ]: 0 : while (isspace(**buffer))
126 : 0 : (*buffer)++;
127 : :
128 : 0 : return val;
129 : : }
130 : :
131 : 0 : static int argc_count(char *opts) {
132 : 0 : int i = 0;
133 [ # # ]: 0 : while (*opts != '\0')
134 [ # # ]: 0 : if (*opts++ == ' ')
135 : 0 : i++;
136 : 0 : 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 : 0 : static int get_file_options(const char *vendor, const char *model,
149 : : int *argc, char ***newargv) {
150 : 0 : _cleanup_free_ char *buffer = NULL;
151 : 0 : _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 : 0 : int retval = 0;
158 : :
159 : 0 : f = fopen(config_file, "re");
160 [ # # ]: 0 : if (!f) {
161 [ # # ]: 0 : if (errno == ENOENT)
162 : 0 : return 1;
163 : : else {
164 [ # # ]: 0 : log_error_errno(errno, "can't open %s: %m", config_file);
165 : 0 : 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 : 0 : buffer = malloc(MAX_BUFFER_LEN);
175 [ # # ]: 0 : if (!buffer)
176 : 0 : return log_oom();
177 : :
178 : 0 : *newargv = NULL;
179 : 0 : lineno = 0;
180 : : for (;;) {
181 : 0 : vendor_in = model_in = options_in = NULL;
182 : :
183 : 0 : buf = fgets(buffer, MAX_BUFFER_LEN, f);
184 [ # # ]: 0 : if (!buf)
185 : 0 : break;
186 : 0 : lineno++;
187 [ # # ]: 0 : if (buf[strlen(buffer) - 1] != '\n') {
188 [ # # ]: 0 : log_error("Config file line %d too long", lineno);
189 : 0 : break;
190 : : }
191 : :
192 [ # # ]: 0 : while (isspace(*buf))
193 : 0 : buf++;
194 : :
195 : : /* blank or all whitespace line */
196 [ # # ]: 0 : if (*buf == '\0')
197 : 0 : continue;
198 : :
199 : : /* comment line */
200 [ # # ]: 0 : if (*buf == '#')
201 : 0 : continue;
202 : :
203 : 0 : str1 = strsep(&buf, "=");
204 [ # # # # ]: 0 : if (str1 && strcaseeq(str1, "VENDOR")) {
205 : 0 : str1 = get_value(&buf);
206 [ # # ]: 0 : if (!str1) {
207 : 0 : retval = log_oom();
208 : 0 : break;
209 : : }
210 : 0 : vendor_in = str1;
211 : :
212 : 0 : str1 = strsep(&buf, "=");
213 [ # # # # ]: 0 : if (str1 && strcaseeq(str1, "MODEL")) {
214 : 0 : str1 = get_value(&buf);
215 [ # # ]: 0 : if (!str1) {
216 : 0 : retval = log_oom();
217 : 0 : break;
218 : : }
219 : 0 : model_in = str1;
220 : 0 : str1 = strsep(&buf, "=");
221 : : }
222 : : }
223 : :
224 [ # # # # ]: 0 : if (str1 && strcaseeq(str1, "OPTIONS")) {
225 : 0 : str1 = get_value(&buf);
226 [ # # ]: 0 : if (!str1) {
227 : 0 : retval = log_oom();
228 : 0 : break;
229 : : }
230 : 0 : options_in = str1;
231 : : }
232 : :
233 : : /*
234 : : * Only allow: [vendor=foo[,model=bar]]options=stuff
235 : : */
236 [ # # # # : 0 : if (!options_in || (!vendor_in && model_in)) {
# # ]
237 [ # # ]: 0 : log_error("Error parsing config file line %d '%s'", lineno, buffer);
238 : 0 : retval = -1;
239 : 0 : break;
240 : : }
241 [ # # ]: 0 : if (vendor == NULL) {
242 [ # # ]: 0 : if (!vendor_in)
243 : 0 : break;
244 [ # # # # ]: 0 : } else if (vendor_in &&
245 [ # # ]: 0 : startswith(vendor, vendor_in) &&
246 [ # # ]: 0 : (!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 [ # # ]: 0 : if (retval == 0) {
259 [ # # # # : 0 : 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 : 0 : strcpy(buffer, options_in);
266 : 0 : c = argc_count(buffer) + 2;
267 : 0 : *newargv = calloc(c, sizeof(**newargv));
268 [ # # ]: 0 : if (!*newargv)
269 : 0 : retval = log_oom();
270 : : else {
271 : 0 : *argc = c;
272 : 0 : 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 : 0 : (*newargv)[c] = buffer;
279 [ # # ]: 0 : for (c = 1; c < *argc; c++)
280 : 0 : (*newargv)[c] = strsep(&buffer, " \t");
281 : 0 : buffer = NULL;
282 : : }
283 : : } else {
284 : : /* No matches */
285 : 0 : retval = 1;
286 : : }
287 : : }
288 : 0 : return retval;
289 : : }
290 : :
291 : 0 : static void help(void) {
292 : 0 : 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 : 0 : }
308 : :
309 : 0 : 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 : 0 : optind = 1;
319 [ # # ]: 0 : while ((option = getopt_long(argc, argv, "d:f:gp:uvVxhbs:", options, NULL)) >= 0)
320 [ # # # # : 0 : switch (option) {
# # # # #
# # # # ]
321 : 0 : case 'b':
322 : 0 : all_good = false;
323 : 0 : break;
324 : :
325 : 0 : case 'd':
326 : 0 : dev_specified = true;
327 : 0 : strscpy(maj_min_dev, MAX_PATH_LEN, optarg);
328 : 0 : break;
329 : :
330 : 0 : case 'f':
331 : 0 : strscpy(config_file, MAX_PATH_LEN, optarg);
332 : 0 : break;
333 : :
334 : 0 : case 'g':
335 : 0 : all_good = true;
336 : 0 : break;
337 : :
338 : 0 : case 'h':
339 : 0 : help();
340 : 0 : exit(EXIT_SUCCESS);
341 : :
342 : 0 : case 'p':
343 [ # # ]: 0 : if (streq(optarg, "0x80"))
344 : 0 : default_page_code = PAGE_80;
345 [ # # ]: 0 : else if (streq(optarg, "0x83"))
346 : 0 : default_page_code = PAGE_83;
347 [ # # ]: 0 : else if (streq(optarg, "pre-spc3-83"))
348 : 0 : default_page_code = PAGE_83_PRE_SPC3;
349 : : else
350 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
351 : : "Unknown page code '%s'",
352 : : optarg);
353 : 0 : break;
354 : :
355 : 0 : case 's':
356 : 0 : sg_version = atoi(optarg);
357 [ # # # # ]: 0 : if (sg_version < 3 || sg_version > 4)
358 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
359 : : "Unknown SG version '%s'",
360 : : optarg);
361 : 0 : break;
362 : :
363 : 0 : case 'u':
364 : 0 : reformat_serial = true;
365 : 0 : break;
366 : :
367 : 0 : case 'v':
368 : 0 : log_set_target(LOG_TARGET_CONSOLE);
369 : 0 : log_set_max_level(LOG_DEBUG);
370 : 0 : log_open();
371 : 0 : break;
372 : :
373 : 0 : case 'V':
374 : 0 : printf("%s\n", GIT_VERSION);
375 : 0 : exit(EXIT_SUCCESS);
376 : :
377 : 0 : case 'x':
378 : 0 : export = true;
379 : 0 : break;
380 : :
381 : 0 : case '?':
382 : 0 : return -1;
383 : :
384 : 0 : default:
385 : 0 : assert_not_reached("Unknown option");
386 : : }
387 : :
388 [ # # # # ]: 0 : if (optind < argc && !dev_specified) {
389 : 0 : dev_specified = true;
390 : 0 : strscpy(maj_min_dev, MAX_PATH_LEN, argv[optind]);
391 : : }
392 : :
393 : 0 : return 0;
394 : : }
395 : :
396 : 0 : static int per_dev_options(struct scsi_id_device *dev_scsi, int *good_bad, int *page_code) {
397 : : int retval;
398 : : int newargc;
399 : 0 : char **newargv = NULL;
400 : : int option;
401 : :
402 : 0 : *good_bad = all_good;
403 : 0 : *page_code = default_page_code;
404 : :
405 : 0 : retval = get_file_options(vendor_str, model_str, &newargc, &newargv);
406 : :
407 : 0 : optind = 1; /* reset this global extern */
408 [ # # ]: 0 : while (retval == 0) {
409 : 0 : option = getopt_long(newargc, newargv, "bgp:", options, NULL);
410 [ # # ]: 0 : if (option == -1)
411 : 0 : break;
412 : :
413 [ # # # # ]: 0 : switch (option) {
414 : 0 : case 'b':
415 : 0 : *good_bad = 0;
416 : 0 : break;
417 : :
418 : 0 : case 'g':
419 : 0 : *good_bad = 1;
420 : 0 : break;
421 : :
422 : 0 : case 'p':
423 [ # # ]: 0 : if (streq(optarg, "0x80")) {
424 : 0 : *page_code = PAGE_80;
425 [ # # ]: 0 : } else if (streq(optarg, "0x83")) {
426 : 0 : *page_code = PAGE_83;
427 [ # # ]: 0 : } else if (streq(optarg, "pre-spc3-83")) {
428 : 0 : *page_code = PAGE_83_PRE_SPC3;
429 : : } else {
430 [ # # ]: 0 : log_error("Unknown page code '%s'", optarg);
431 : 0 : retval = -1;
432 : : }
433 : 0 : break;
434 : :
435 : 0 : default:
436 [ # # ]: 0 : log_error("Unknown or bad option '%c' (0x%x)", option, option);
437 : 0 : retval = -1;
438 : 0 : break;
439 : : }
440 : : }
441 : :
442 [ # # ]: 0 : if (newargv) {
443 : 0 : free(newargv[0]);
444 : 0 : free(newargv);
445 : : }
446 : 0 : return retval;
447 : : }
448 : :
449 : 0 : static int set_inq_values(struct scsi_id_device *dev_scsi, const char *path) {
450 : : int retval;
451 : :
452 : 0 : dev_scsi->use_sg = sg_version;
453 : :
454 : 0 : retval = scsi_std_inquiry(dev_scsi, path);
455 [ # # ]: 0 : if (retval)
456 : 0 : return retval;
457 : :
458 : 0 : udev_util_encode_string(dev_scsi->vendor, vendor_enc_str, sizeof(vendor_enc_str));
459 : 0 : udev_util_encode_string(dev_scsi->model, model_enc_str, sizeof(model_enc_str));
460 : :
461 : 0 : util_replace_whitespace(dev_scsi->vendor, vendor_str, sizeof(vendor_str)-1);
462 : 0 : util_replace_chars(vendor_str, NULL);
463 : 0 : util_replace_whitespace(dev_scsi->model, model_str, sizeof(model_str)-1);
464 : 0 : util_replace_chars(model_str, NULL);
465 : 0 : set_type(dev_scsi->type, type_str, sizeof(type_str));
466 : 0 : util_replace_whitespace(dev_scsi->revision, revision_str, sizeof(revision_str)-1);
467 : 0 : util_replace_chars(revision_str, NULL);
468 : 0 : 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 : 0 : static int scsi_id(char *maj_min_dev) {
476 : 0 : struct scsi_id_device dev_scsi = {};
477 : : int good_dev;
478 : : int page_code;
479 : 0 : int retval = 0;
480 : :
481 [ # # ]: 0 : if (set_inq_values(&dev_scsi, maj_min_dev) < 0) {
482 : 0 : retval = 1;
483 : 0 : goto out;
484 : : }
485 : :
486 : : /* get per device (vendor + model) options from the config file */
487 : 0 : per_dev_options(&dev_scsi, &good_dev, &page_code);
488 [ # # ]: 0 : if (!good_dev) {
489 : 0 : retval = 1;
490 : 0 : goto out;
491 : : }
492 : :
493 : : /* read serial number from mode pages (no values for optical drives) */
494 : 0 : scsi_get_serial(&dev_scsi, maj_min_dev, page_code, MAX_SERIAL_LEN);
495 : :
496 [ # # ]: 0 : if (export) {
497 : : char serial_str[MAX_SERIAL_LEN];
498 : :
499 : 0 : printf("ID_SCSI=1\n");
500 : 0 : printf("ID_VENDOR=%s\n", vendor_str);
501 : 0 : printf("ID_VENDOR_ENC=%s\n", vendor_enc_str);
502 : 0 : printf("ID_MODEL=%s\n", model_str);
503 : 0 : printf("ID_MODEL_ENC=%s\n", model_enc_str);
504 : 0 : printf("ID_REVISION=%s\n", revision_str);
505 : 0 : printf("ID_TYPE=%s\n", type_str);
506 [ # # ]: 0 : if (dev_scsi.serial[0] != '\0') {
507 : 0 : util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
508 : 0 : util_replace_chars(serial_str, NULL);
509 : 0 : printf("ID_SERIAL=%s\n", serial_str);
510 : 0 : util_replace_whitespace(dev_scsi.serial_short, serial_str, sizeof(serial_str)-1);
511 : 0 : util_replace_chars(serial_str, NULL);
512 : 0 : printf("ID_SERIAL_SHORT=%s\n", serial_str);
513 : : }
514 [ # # ]: 0 : if (dev_scsi.wwn[0] != '\0') {
515 : 0 : printf("ID_WWN=0x%s\n", dev_scsi.wwn);
516 [ # # ]: 0 : if (dev_scsi.wwn_vendor_extension[0] != '\0') {
517 : 0 : printf("ID_WWN_VENDOR_EXTENSION=0x%s\n", dev_scsi.wwn_vendor_extension);
518 : 0 : printf("ID_WWN_WITH_EXTENSION=0x%s%s\n", dev_scsi.wwn, dev_scsi.wwn_vendor_extension);
519 : : } else
520 : 0 : printf("ID_WWN_WITH_EXTENSION=0x%s\n", dev_scsi.wwn);
521 : : }
522 [ # # ]: 0 : if (dev_scsi.tgpt_group[0] != '\0')
523 : 0 : printf("ID_TARGET_PORT=%s\n", dev_scsi.tgpt_group);
524 [ # # ]: 0 : if (dev_scsi.unit_serial_number[0] != '\0')
525 : 0 : printf("ID_SCSI_SERIAL=%s\n", dev_scsi.unit_serial_number);
526 : 0 : goto out;
527 : : }
528 : :
529 [ # # ]: 0 : if (dev_scsi.serial[0] == '\0') {
530 : 0 : retval = 1;
531 : 0 : goto out;
532 : : }
533 : :
534 [ # # ]: 0 : if (reformat_serial) {
535 : : char serial_str[MAX_SERIAL_LEN];
536 : :
537 : 0 : util_replace_whitespace(dev_scsi.serial, serial_str, sizeof(serial_str)-1);
538 : 0 : util_replace_chars(serial_str, NULL);
539 : 0 : printf("%s\n", serial_str);
540 : 0 : goto out;
541 : : }
542 : :
543 : 0 : printf("%s\n", dev_scsi.serial);
544 : 0 : out:
545 : 0 : return retval;
546 : : }
547 : :
548 : 0 : int main(int argc, char **argv) {
549 : 0 : int retval = 0;
550 : : char maj_min_dev[MAX_PATH_LEN];
551 : : int newargc;
552 : 0 : char **newargv = NULL;
553 : :
554 : 0 : log_set_target(LOG_TARGET_AUTO);
555 : 0 : udev_parse_config();
556 : 0 : log_parse_environment();
557 : 0 : log_open();
558 : :
559 : : /*
560 : : * Get config file options.
561 : : */
562 : 0 : retval = get_file_options(NULL, NULL, &newargc, &newargv);
563 [ # # ]: 0 : if (retval < 0) {
564 : 0 : retval = 1;
565 : 0 : goto exit;
566 : : }
567 [ # # ]: 0 : if (retval == 0) {
568 [ # # ]: 0 : assert(newargv);
569 : :
570 [ # # ]: 0 : if (set_options(newargc, newargv, maj_min_dev) < 0) {
571 : 0 : retval = 2;
572 : 0 : goto exit;
573 : : }
574 : : }
575 : :
576 : : /*
577 : : * Get command line options (overriding any config file settings).
578 : : */
579 [ # # ]: 0 : if (set_options(argc, argv, maj_min_dev) < 0)
580 : 0 : exit(EXIT_FAILURE);
581 : :
582 [ # # ]: 0 : if (!dev_specified) {
583 [ # # ]: 0 : log_error("No device specified.");
584 : 0 : retval = 1;
585 : 0 : goto exit;
586 : : }
587 : :
588 : 0 : retval = scsi_id(maj_min_dev);
589 : :
590 : 0 : exit:
591 [ # # ]: 0 : if (newargv) {
592 : 0 : free(newargv[0]);
593 : 0 : free(newargv);
594 : : }
595 : 0 : log_close();
596 : 0 : return retval;
597 : : }
|