1/* 2 * scsi_id.c 3 * 4 * Main section of the scsi_id program 5 * 6 * Copyright (C) IBM Corp. 2003 7 * Copyright (C) SUSE Linux Products GmbH, 2006 8 * 9 * Author: 10 * Patrick Mansfield<patmans@us.ibm.com> 11 * 12 * This program is free software; you can redistribute it and/or modify it 13 * under the terms of the GNU General Public License as published by the 14 * Free Software Foundation version 2 of the License. 15 */ 16 17#include <stdio.h> 18#include <stdlib.h> 19#include <unistd.h> 20#include <signal.h> 21#include <fcntl.h> 22#include <errno.h> 23#include <string.h> 24#include <syslog.h> 25#include <stdarg.h> 26#include <ctype.h> 27#include <getopt.h> 28#include <sys/stat.h> 29 30#include "../../udev.h" 31#include "scsi_id.h" 32#include "scsi_id_version.h" 33 34/* temporary names for mknod */ 35#define TMP_DIR "/dev" 36#define TMP_PREFIX "tmp-scsi" 37 38static const struct option options[] = { 39 { "device", 1, NULL, 'd' }, 40 { "config", 1, NULL, 'f' }, 41 { "page", 1, NULL, 'p' }, 42 { "devpath", 1, NULL, 's' }, 43 { "fallback-to-sysfs", 0, NULL, 'a' }, 44 { "blacklisted", 0, NULL, 'b' }, 45 { "whitelisted", 0, NULL, 'g' }, 46 { "prefix-bus-id", 0, NULL, 'i' }, 47 { "replace-whitespace", 0, NULL, 'u' }, 48 { "ignore-sysfs", 0, NULL, 'n' }, 49 { "verbose", 0, NULL, 'v' }, 50 { "version", 0, NULL, 'V' }, 51 { "export", 0, NULL, 'x' }, 52 { "help", 0, NULL, 'h' }, 53 {} 54}; 55 56static const char short_options[] = "abd:f:ghinp:s:uvVx"; 57static const char dev_short_options[] = "bgp:"; 58 59static int all_good; 60static int always_info; 61static int dev_specified; 62static int sys_specified; 63static char config_file[MAX_PATH_LEN] = SCSI_ID_CONFIG_FILE; 64static int display_bus_id; 65static enum page_code default_page_code; 66static int use_stderr; 67static int debug; 68static int hotplug_mode; 69static int reformat_serial; 70static int ignore_sysfs; 71static int export; 72static char vendor_str[64]; 73static char model_str[64]; 74static char revision_str[16]; 75static char type_str[16]; 76 77#ifdef USE_LOG 78void log_message(int priority, const char *format, ...) 79{ 80 va_list args; 81 static int udev_log = -1; 82 83 if (udev_log == -1) { 84 const char *value; 85 86 value = getenv("UDEV_LOG"); 87 if (value) 88 udev_log = log_priority(value); 89 else 90 udev_log = LOG_ERR; 91 } 92 93 if (priority > udev_log) 94 return; 95 96 va_start(args, format); 97 vsyslog(priority, format, args); 98 va_end(args); 99} 100#endif 101 102static void set_str(char *to, const char *from, size_t count) 103{ 104 size_t i, j, len; 105 106 /* strip trailing whitespace */ 107 len = strnlen(from, count); 108 while (len && isspace(from[len-1])) 109 len--; 110 111 /* strip leading whitespace */ 112 i = 0; 113 while (isspace(from[i]) && (i < len)) 114 i++; 115 116 j = 0; 117 while (i < len) { 118 /* substitute multiple whitespace */ 119 if (isspace(from[i])) { 120 while (isspace(from[i])) 121 i++; 122 to[j++] = '_'; 123 } 124 /* skip chars */ 125 if (from[i] == '/') { 126 i++; 127 continue; 128 } 129 to[j++] = from[i++]; 130 } 131 to[j] = '\0'; 132} 133 134static void set_type(char *to, const char *from, size_t len) 135{ 136 int type_num; 137 char *eptr; 138 char *type = "generic"; 139 140 type_num = strtoul(from, &eptr, 0); 141 if (eptr != from) { 142 switch (type_num) { 143 case 0: 144 type = "disk"; 145 break; 146 case 1: 147 type = "tape"; 148 break; 149 case 4: 150 type = "optical"; 151 break; 152 case 5: 153 type = "cd"; 154 break; 155 case 7: 156 type = "optical"; 157 break; 158 case 0xe: 159 type = "disk"; 160 break; 161 case 0xf: 162 type = "optical"; 163 break; 164 default: 165 break; 166 } 167 } 168 strncpy(to, type, len); 169 to[len-1] = '\0'; 170} 171 172static int create_tmp_dev(const char *devpath, char *tmpdev, int dev_type) 173{ 174 unsigned int maj, min; 175 const char *attr; 176 177 dbg("%s", devpath); 178 attr = sysfs_attr_get_value(devpath, "dev"); 179 if (attr == NULL) { 180 dbg("%s: could not get dev attribute: %s", devpath, strerror(errno)); 181 return -1; 182 } 183 184 dbg("dev value %s", attr); 185 if (sscanf(attr, "%u:%u", &maj, &min) != 2) { 186 err("%s: invalid dev major/minor", devpath); 187 return -1; 188 } 189 190 snprintf(tmpdev, MAX_PATH_LEN, "%s/%s-maj%d-min%d-%u", 191 TMP_DIR, TMP_PREFIX, maj, min, getpid()); 192 193 dbg("tmpdev '%s'", tmpdev); 194 if (mknod(tmpdev, 0600 | dev_type, makedev(maj, min))) { 195 err("mknod failed: %s", strerror(errno)); 196 return -1; 197 } 198 return 0; 199} 200 201/* 202 * get_value: 203 * 204 * buf points to an '=' followed by a quoted string ("foo") or a string ending 205 * with a space or ','. 206 * 207 * Return a pointer to the NUL terminated string, returns NULL if no 208 * matches. 209 */ 210static char *get_value(char **buffer) 211{ 212 static char *quote_string = "\"\n"; 213 static char *comma_string = ",\n"; 214 char *val; 215 char *end; 216 217 if (**buffer == '"') { 218 /* 219 * skip leading quote, terminate when quote seen 220 */ 221 (*buffer)++; 222 end = quote_string; 223 } else { 224 end = comma_string; 225 } 226 val = strsep(buffer, end); 227 if (val && end == quote_string) 228 /* 229 * skip trailing quote 230 */ 231 (*buffer)++; 232 233 while (isspace(**buffer)) 234 (*buffer)++; 235 236 return val; 237} 238 239static int argc_count(char *opts) 240{ 241 int i = 0; 242 while (*opts != '\0') 243 if (*opts++ == ' ') 244 i++; 245 return i; 246} 247 248/* 249 * get_file_options: 250 * 251 * If vendor == NULL, find a line in the config file with only "OPTIONS="; 252 * if vendor and model are set find the first OPTIONS line in the config 253 * file that matches. Set argc and argv to match the OPTIONS string. 254 * 255 * vendor and model can end in '\n'. 256 */ 257static int get_file_options(const char *vendor, const char *model, 258 int *argc, char ***newargv) 259{ 260 char *buffer; 261 FILE *fd; 262 char *buf; 263 char *str1; 264 char *vendor_in, *model_in, *options_in; /* read in from file */ 265 int lineno; 266 int c; 267 int retval = 0; 268 269 dbg("vendor='%s'; model='%s'\n", vendor, model); 270 fd = fopen(config_file, "r"); 271 if (fd == NULL) { 272 dbg("can't open %s\n", config_file); 273 if (errno == ENOENT) { 274 return 1; 275 } else { 276 err("can't open %s: %s", config_file, strerror(errno)); 277 return -1; 278 } 279 } 280 281 /* 282 * Allocate a buffer rather than put it on the stack so we can 283 * keep it around to parse any options (any allocated newargv 284 * points into this buffer for its strings). 285 */ 286 buffer = malloc(MAX_BUFFER_LEN); 287 if (!buffer) { 288 err("Can't allocate memory."); 289 return -1; 290 } 291 292 *newargv = NULL; 293 lineno = 0; 294 while (1) { 295 vendor_in = model_in = options_in = NULL; 296 297 buf = fgets(buffer, MAX_BUFFER_LEN, fd); 298 if (buf == NULL) 299 break; 300 lineno++; 301 if (buf[strlen(buffer) - 1] != '\n') { 302 info("Config file line %d too long.\n", lineno); 303 break; 304 } 305 306 while (isspace(*buf)) 307 buf++; 308 309 /* blank or all whitespace line */ 310 if (*buf == '\0') 311 continue; 312 313 /* comment line */ 314 if (*buf == '#') 315 continue; 316 317 dbg("lineno %d: '%s'\n", lineno, buf); 318 str1 = strsep(&buf, "="); 319 if (str1 && strcasecmp(str1, "VENDOR") == 0) { 320 str1 = get_value(&buf); 321 if (!str1) { 322 retval = -1; 323 break; 324 } 325 vendor_in = str1; 326 327 str1 = strsep(&buf, "="); 328 if (str1 && strcasecmp(str1, "MODEL") == 0) { 329 str1 = get_value(&buf); 330 if (!str1) { 331 retval = -1; 332 break; 333 } 334 model_in = str1; 335 str1 = strsep(&buf, "="); 336 } 337 } 338 339 if (str1 && strcasecmp(str1, "OPTIONS") == 0) { 340 str1 = get_value(&buf); 341 if (!str1) { 342 retval = -1; 343 break; 344 } 345 options_in = str1; 346 } 347 dbg("config file line %d:" 348 " vendor '%s'; model '%s'; options '%s'\n", 349 lineno, vendor_in, model_in, options_in); 350 /* 351 * Only allow: [vendor=foo[,model=bar]]options=stuff 352 */ 353 if (!options_in || (!vendor_in && model_in)) { 354 info("Error parsing config file line %d '%s'", lineno, buffer); 355 retval = -1; 356 break; 357 } 358 if (vendor == NULL) { 359 if (vendor_in == NULL) { 360 dbg("matched global option\n"); 361 break; 362 } 363 } else if ((vendor_in && strncmp(vendor, vendor_in, 364 strlen(vendor_in)) == 0) && 365 (!model_in || (strncmp(model, model_in, 366 strlen(model_in)) == 0))) { 367 /* 368 * Matched vendor and optionally model. 369 * 370 * Note: a short vendor_in or model_in can 371 * give a partial match (that is FOO 372 * matches FOOBAR). 373 */ 374 dbg("matched vendor/model\n"); 375 break; 376 } else { 377 dbg("no match\n"); 378 } 379 } 380 381 if (retval == 0) { 382 if (vendor_in != NULL || model_in != NULL || 383 options_in != NULL) { 384 /* 385 * Something matched. Allocate newargv, and store 386 * values found in options_in. 387 */ 388 strcpy(buffer, options_in); 389 c = argc_count(buffer) + 2; 390 *newargv = calloc(c, sizeof(**newargv)); 391 if (!*newargv) { 392 err("Can't allocate memory."); 393 retval = -1; 394 } else { 395 *argc = c; 396 c = 0; 397 /* 398 * argv[0] at 0 is skipped by getopt, but 399 * store the buffer address there for 400 * later freeing 401 */ 402 (*newargv)[c] = buffer; 403 for (c = 1; c < *argc; c++) 404 (*newargv)[c] = strsep(&buffer, " \t"); 405 } 406 } else { 407 /* No matches */ 408 retval = 1; 409 } 410 } 411 if (retval != 0) 412 free(buffer); 413 fclose(fd); 414 return retval; 415} 416 417static int set_options(int argc, char **argv, const char *short_opts, 418 char *target, char *maj_min_dev) 419{ 420 int option; 421 422 /* 423 * optind is a global extern used by getopt. Since we can call 424 * set_options twice (once for command line, and once for config 425 * file) we have to reset this back to 1. 426 */ 427 optind = 1; 428 while (1) { 429 option = getopt_long(argc, argv, short_opts, options, NULL); 430 if (option == -1) 431 break; 432 433 if (optarg) 434 dbg("option '%c' arg '%s'\n", option, optarg); 435 else 436 dbg("option '%c'\n", option); 437 438 switch (option) { 439 case 'a': 440 always_info = 1; 441 break; 442 case 'b': 443 all_good = 0; 444 break; 445 446 case 'd': 447 dev_specified = 1; 448 strncpy(maj_min_dev, optarg, MAX_PATH_LEN); 449 maj_min_dev[MAX_PATH_LEN-1] = '\0'; 450 break; 451 452 case 'e': 453 use_stderr = 1; 454 break; 455 456 case 'f': 457 strncpy(config_file, optarg, MAX_PATH_LEN); 458 config_file[MAX_PATH_LEN-1] = '\0'; 459 break; 460 461 case 'g': 462 all_good = 1; 463 break; 464 465 case 'h': 466 printf("Usage: scsi_id OPTIONS <device>\n" 467 " --device device node for SG_IO commands\n" 468 " --devpath sysfs devpath\n" 469 " --config location of config file\n" 470 " --page SCSI page (0x80, 0x83, pre-spc3-83)\n" 471 " --fallback-to-sysfs print sysfs values if inquiry fails\n" 472 " --ignore-sysfs ignore sysfs entries\n" 473 " --blacklisted threat device as blacklisted\n" 474 " --whitelisted threat device as whitelisted\n" 475 " --prefix-bus-id prefix SCSI bus id\n" 476 " --replace-whitespace replace all whitespaces by underscores\n" 477 " --verbose verbose logging\n" 478 " --version print version\n" 479 " --export print values as environment keys\n" 480 " --help print this help text\n\n"); 481 exit(0); 482 483 case 'i': 484 display_bus_id = 1; 485 break; 486 487 case 'p': 488 if (strcmp(optarg, "0x80") == 0) { 489 default_page_code = PAGE_80; 490 } else if (strcmp(optarg, "0x83") == 0) { 491 default_page_code = PAGE_83; 492 } else if (strcmp(optarg, "pre-spc3-83") == 0) { 493 default_page_code = PAGE_83_PRE_SPC3; 494 } else { 495 info("Unknown page code '%s'", optarg); 496 return -1; 497 } 498 break; 499 500 case 'n': 501 ignore_sysfs = 1; 502 break; 503 504 case 's': 505 sys_specified = 1; 506 strncpy(target, optarg, MAX_PATH_LEN); 507 target[MAX_PATH_LEN-1] = '\0'; 508 break; 509 510 case 'u': 511 reformat_serial = 1; 512 break; 513 514 case 'x': 515 export = 1; 516 break; 517 518 case 'v': 519 debug++; 520 break; 521 522 case 'V': 523 printf("%s\n", SCSI_ID_VERSION); 524 exit(0); 525 break; 526 527 default: 528 exit(1); 529 } 530 } 531 return 0; 532} 533 534static int per_dev_options(struct sysfs_device *dev_scsi, int *good_bad, int *page_code) 535{ 536 int retval; 537 int newargc; 538 char **newargv = NULL; 539 int option; 540 541 *good_bad = all_good; 542 *page_code = default_page_code; 543 544 retval = get_file_options(vendor_str, model_str, &newargc, &newargv); 545 546 optind = 1; /* reset this global extern */ 547 while (retval == 0) { 548 option = getopt_long(newargc, newargv, dev_short_options, options, NULL); 549 if (option == -1) 550 break; 551 552 if (optarg) 553 dbg("option '%c' arg '%s'\n", option, optarg); 554 else 555 dbg("option '%c'\n", option); 556 557 switch (option) { 558 case 'b': 559 *good_bad = 0; 560 break; 561 562 case 'g': 563 *good_bad = 1; 564 break; 565 566 case 'p': 567 if (strcmp(optarg, "0x80") == 0) { 568 *page_code = PAGE_80; 569 } else if (strcmp(optarg, "0x83") == 0) { 570 *page_code = PAGE_83; 571 } else if (strcmp(optarg, "pre-spc3-83") == 0) { 572 *page_code = PAGE_83_PRE_SPC3; 573 } else { 574 info("Unknown page code '%s'", optarg); 575 retval = -1; 576 } 577 break; 578 579 default: 580 info("Unknown or bad option '%c' (0x%x)", option, option); 581 retval = -1; 582 break; 583 } 584 } 585 586 if (newargv) { 587 free(newargv[0]); 588 free(newargv); 589 } 590 return retval; 591} 592 593static int set_sysfs_values(struct sysfs_device *dev_scsi) 594{ 595 const char *vendor, *model, *type; 596 597 vendor = sysfs_attr_get_value(dev_scsi->devpath, "vendor"); 598 if (!vendor) { 599 info("%s: cannot get vendor attribute", dev_scsi->devpath); 600 return -1; 601 } 602 set_str(vendor_str, vendor, sizeof(vendor_str)-1); 603 604 model = sysfs_attr_get_value(dev_scsi->devpath, "model"); 605 if (!model) { 606 info("%s: cannot get model attribute\n", dev_scsi->devpath); 607 return -1; 608 } 609 set_str(model_str, model, sizeof(model_str)-1); 610 611 type = sysfs_attr_get_value(dev_scsi->devpath, "type"); 612 if (!type) { 613 info("%s: cannot get type attribute", dev_scsi->devpath); 614 return -1; 615 } 616 set_type(type_str, type, sizeof(type_str)); 617 618 type = sysfs_attr_get_value(dev_scsi->devpath, "rev"); 619 if (!type) { 620 info("%s: cannot get type attribute\n", dev_scsi->devpath); 621 return -1; 622 } 623 set_str(revision_str, type, sizeof(revision_str)-1); 624 625 return 0; 626} 627 628static int set_inq_values(struct sysfs_device *dev_scsi, const char *path) 629{ 630 int retval; 631 char vendor[8], model[16], type[4], rev[4]; 632 633 retval = scsi_std_inquiry(dev_scsi, path, vendor, model, rev, type); 634 if (retval) 635 return retval; 636 637 set_str(vendor_str, vendor, 8); 638 set_str(model_str, model, 16); 639 set_type(type_str, type, sizeof(type_str) - 1); 640 set_str(revision_str, rev, sizeof(revision_str) -1); 641 642 return 0; 643} 644 645/* 646 * format_serial: replace to whitespaces by underscores for calling 647 * programs that use the serial for device naming (multipath, Suse 648 * naming, etc...) 649 */ 650static void format_serial(char *serial) 651{ 652 char *p = serial, *q; 653 654 q = p; 655 while (*p != '\0') { 656 if (isspace(*p)) { 657 if (q > serial && q[-1] != '_') { 658 *q = '_'; 659 q++; 660 } 661 } else { 662 *q = *p; 663 q++; 664 } 665 p++; 666 } 667 *q = '\0'; 668} 669 670/* 671 * scsi_id: try to get an id, if one is found, printf it to stdout. 672 * returns a value passed to exit() - 0 if printed an id, else 1. This 673 * could be expanded, for example, if we want to report a failure like no 674 * memory etc. return 2, and return 1 for expected cases (like broken 675 * device found) that do not print an id. 676 */ 677static int scsi_id(const char *devpath, char *maj_min_dev) 678{ 679 int retval; 680 int dev_type = 0; 681 struct sysfs_device *dev; 682 struct sysfs_device *dev_scsi = NULL; 683 int good_dev; 684 int page_code; 685 char serial[MAX_SERIAL_LEN]; 686 char serial_short[MAX_SERIAL_LEN]; 687 const char *bus_str = NULL; 688 689 dbg("devpath %s\n", devpath); 690 691 dev = sysfs_device_get(devpath); 692 if (dev == NULL) { 693 err("unable to access '%s'", devpath); 694 return 1; 695 } 696 697 if (strcmp(dev->subsystem, "block") == 0) 698 dev_type = S_IFBLK; 699 else 700 dev_type = S_IFCHR; 701 702 /* mknod a temp dev to communicate with the device */ 703 if (!dev_specified && create_tmp_dev(dev->devpath, maj_min_dev, dev_type)) { 704 dbg("create_tmp_dev failed\n"); 705 return 1; 706 } 707 708 if (!ignore_sysfs) { 709 /* get scsi parent device */ 710 dev_scsi = sysfs_device_get_parent_with_subsystem(dev, "scsi"); 711 if (dev_scsi == NULL) { 712 err("unable to access parent device of '%s'", devpath); 713 return 1; 714 } 715 set_sysfs_values(dev_scsi); 716 bus_str = "scsi"; 717 } else { 718 dev_scsi = dev; 719 set_inq_values(dev_scsi, maj_min_dev); 720 } 721 722 /* get per device (vendor + model) options from the config file */ 723 retval = per_dev_options(dev_scsi, &good_dev, &page_code); 724 dbg("per dev options: good %d; page code 0x%x", good_dev, page_code); 725 726 if (!good_dev) { 727 retval = 1; 728 } else if (scsi_get_serial(dev_scsi, maj_min_dev, page_code, 729 serial, serial_short, MAX_SERIAL_LEN)) { 730 retval = always_info?0:1; 731 } else { 732 retval = 0; 733 } 734 if (!retval) { 735 if (export) { 736 char serial_str[MAX_SERIAL_LEN]; 737 738 printf("ID_VENDOR=%s\n", vendor_str); 739 printf("ID_MODEL=%s\n", model_str); 740 printf("ID_REVISION=%s\n", revision_str); 741 set_str(serial_str, serial, sizeof(serial_str)); 742 printf("ID_SERIAL=%s\n", serial_str); 743 set_str(serial_str, serial_short, sizeof(serial_str)); 744 printf("ID_SERIAL_SHORT=%s\n", serial_str); 745 printf("ID_TYPE=%s\n", type_str); 746 if (bus_str != NULL) 747 printf("ID_BUS=%s\n", bus_str); 748 } else { 749 if (reformat_serial) 750 format_serial(serial); 751 if (display_bus_id) 752 printf("%s: ", dev_scsi->kernel); 753 printf("%s\n", serial); 754 } 755 dbg("%s\n", serial); 756 retval = 0; 757 } 758 759 if (!dev_specified) 760 unlink(maj_min_dev); 761 762 return retval; 763} 764 765int main(int argc, char **argv) 766{ 767 int retval = 0; 768 char devpath[MAX_PATH_LEN]; 769 char maj_min_dev[MAX_PATH_LEN]; 770 int newargc; 771 const char *env; 772 char **newargv; 773 774 logging_init("scsi_id"); 775 sysfs_init(); 776 dbg("argc is %d\n", argc); 777 778 /* sysfs path can be overridden for testing */ 779 env = getenv("SYSFS_PATH"); 780 if (env) { 781 strncpy(sysfs_path, env, sizeof(sysfs_path)); 782 sysfs_path[sizeof(sysfs_path)-1] = '\0'; 783 } else 784 strcpy(sysfs_path, "/sys"); 785 786 env = getenv("DEVPATH"); 787 if (env) { 788 hotplug_mode = 1; 789 sys_specified = 1; 790 strncpy(devpath, env, MAX_PATH_LEN); 791 devpath[sizeof(devpath)-1] = '\0'; 792 } 793 794 /* 795 * Get config file options. 796 */ 797 newargv = NULL; 798 retval = get_file_options(NULL, NULL, &newargc, &newargv); 799 if (retval < 0) { 800 retval = 1; 801 goto exit; 802 } 803 if (newargv && (retval == 0)) { 804 if (set_options(newargc, newargv, short_options, devpath, 805 maj_min_dev) < 0) { 806 retval = 2; 807 goto exit; 808 } 809 free(newargv); 810 } 811 812 /* 813 * Get command line options (overriding any config file or DEVPATH 814 * settings). 815 */ 816 if (set_options(argc, argv, short_options, devpath, maj_min_dev) < 0) 817 exit(1); 818 819 if (!sys_specified) { 820 info("--devpath=<path> must be specified\n"); 821 retval = 1; 822 goto exit; 823 } 824 825 retval = scsi_id(devpath, maj_min_dev); 826 827exit: 828 sysfs_cleanup(); 829 logging_close(); 830 return retval; 831} 832