1 /* $OpenBSD: scsi.c,v 1.32 2022/12/04 23:50:47 cheloha Exp $ */ 2 /* $FreeBSD: scsi.c,v 1.11 1996/04/06 11:00:28 joerg Exp $ */ 3 4 /* 5 * Written By Julian ELischer 6 * Copyright julian Elischer 1993. 7 * Permission is granted to use or redistribute this file in any way as long 8 * as this notice remains. Julian Elischer does not guarantee that this file 9 * is totally correct for any given task and users of this file must 10 * accept responsibility for any damage that occurs from the application of this 11 * file. 12 * 13 * (julian@tfs.com julian@dialix.oz.au) 14 * 15 * User SCSI hooks added by Peter Dufault: 16 * 17 * Copyright (c) 1994 HD Associates 18 * (contact: dufault@hda.com) 19 * All rights reserved. 20 * 21 * Redistribution and use in source and binary forms, with or without 22 * modification, are permitted provided that the following conditions 23 * are met: 24 * 1. Redistributions of source code must retain the above copyright 25 * notice, this list of conditions and the following disclaimer. 26 * 2. Redistributions in binary form must reproduce the above copyright 27 * notice, this list of conditions and the following disclaimer in the 28 * documentation and/or other materials provided with the distribution. 29 * 3. The name of HD Associates 30 * may not be used to endorse or promote products derived from this software 31 * without specific prior written permission. 32 * 33 * THIS SOFTWARE IS PROVIDED BY HD ASSOCIATES ``AS IS'' AND 34 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL HD ASSOCIATES BE LIABLE 37 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 38 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 39 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 40 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 41 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 42 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 43 * SUCH DAMAGE. 44 */ 45 46 #include <sys/types.h> 47 #include <sys/wait.h> 48 49 #include <fcntl.h> 50 #include <stdio.h> 51 #include <string.h> 52 #include <stdlib.h> 53 #include <unistd.h> 54 #include <errno.h> 55 #include <sys/scsiio.h> 56 #include <ctype.h> 57 #include <signal.h> 58 #include <err.h> 59 #include <paths.h> 60 61 #include "libscsi.h" 62 63 int fd; 64 int debuglevel; 65 int debugflag; 66 int commandflag; 67 int verbose = 0; 68 69 int modeflag; 70 int editflag; 71 int modepage = 0; /* Read this mode page */ 72 int pagectl = 0; /* Mode sense page control */ 73 int seconds = 2; 74 75 void procargs(int *argc_p, char ***argv_p); 76 int iget(void *hook, char *name); 77 char *cget(void *hook, char *name); 78 void arg_put(void *hook, int letter, void *arg, int count, char *name); 79 void mode_sense(int fd, u_char *data, int len, int pc, int page); 80 void mode_select(int fd, u_char *data, int len, int perm); 81 int editit(const char *pathname); 82 83 static void 84 usage(void) 85 { 86 fprintf(stderr, 87 "usage: scsi -f device -d debug_level\n" 88 " scsi -f device -m page [-e] [-P pc]\n" 89 " scsi -f device [-v] [-s seconds] -c cmd_fmt [arg ...]" 90 " -o count out_fmt\n" 91 " [arg ...] -i count in_fmt [arg ...]\n"); 92 93 exit (1); 94 } 95 96 void 97 procargs(int *argc_p, char ***argv_p) 98 { 99 int argc = *argc_p; 100 char **argv = *argv_p; 101 int fflag, ch; 102 103 fflag = 0; 104 commandflag = 0; 105 debugflag = 0; 106 while ((ch = getopt(argc, argv, "cef:d:m:P:s:v")) != -1) { 107 switch (ch) { 108 case 'c': 109 commandflag = 1; 110 break; 111 case 'e': 112 editflag = 1; 113 break; 114 case 'f': 115 if ((fd = scsi_open(optarg, O_RDWR)) < 0) 116 err(1, "unable to open device %s", optarg); 117 fflag = 1; 118 break; 119 case 'd': 120 debuglevel = strtol(optarg, 0, 0); 121 debugflag = 1; 122 break; 123 case 'm': 124 modeflag = 1; 125 modepage = strtol(optarg, 0, 0); 126 break; 127 case 'P': 128 pagectl = strtol(optarg, 0, 0); 129 break; 130 case 's': 131 seconds = strtol(optarg, 0, 0); 132 break; 133 case 'v': 134 verbose = 1; 135 break; 136 default: 137 usage(); 138 } 139 } 140 *argc_p = argc - optind; 141 *argv_p = argv + optind; 142 143 if (!fflag) usage(); 144 } 145 146 /* get_hook: Structure for evaluating args in a callback. 147 */ 148 struct get_hook 149 { 150 int argc; 151 char **argv; 152 int got; 153 }; 154 155 /* iget: Integer argument callback 156 */ 157 int 158 iget(void *hook, char *name) 159 { 160 struct get_hook *h = (struct get_hook *)hook; 161 int arg; 162 163 if (h->got >= h->argc) 164 { 165 fprintf(stderr, "Expecting an integer argument.\n"); 166 usage(); 167 } 168 arg = strtol(h->argv[h->got], 0, 0); 169 h->got++; 170 171 if (verbose && name && *name) 172 printf("%s: %d\n", name, arg); 173 174 return arg; 175 } 176 177 /* cget: char * argument callback 178 */ 179 char * 180 cget(void *hook, char *name) 181 { 182 struct get_hook *h = (struct get_hook *)hook; 183 char *arg; 184 185 if (h->got >= h->argc) 186 { 187 fprintf(stderr, "Expecting a character pointer argument.\n"); 188 usage(); 189 } 190 arg = h->argv[h->got]; 191 h->got++; 192 193 if (verbose && name) 194 printf("cget: %s: %s", name, arg); 195 196 return arg; 197 } 198 199 /* arg_put: "put argument" callback 200 */ 201 void arg_put(void *hook, int letter, void *arg, int count, char *name) 202 { 203 if (verbose && name && *name) 204 printf("%s: ", name); 205 206 switch(letter) 207 { 208 case 'i': 209 case 'b': 210 printf("%ld ", (long)arg); 211 break; 212 213 case 'c': 214 case 'z': 215 { 216 char *p = malloc(count + 1); 217 if (p == NULL) 218 err(1, NULL); 219 220 p[count] = 0; 221 strncpy(p, (char *)arg, count); 222 if (letter == 'z') 223 { 224 int i; 225 for (i = count - 1; i >= 0; i--) 226 if (p[i] == ' ') 227 p[i] = 0; 228 else 229 break; 230 } 231 printf("%s ", p); 232 free(p); 233 } 234 235 break; 236 237 default: 238 printf("Unknown format letter: '%c'\n", letter); 239 } 240 if (verbose) 241 putchar('\n'); 242 } 243 244 /* data_phase: SCSI bus data phase: DATA IN, DATA OUT, or no data transfer. 245 */ 246 enum data_phase {none = 0, in, out}; 247 248 /* do_cmd: Send a command to a SCSI device 249 */ 250 static void 251 do_cmd(int fd, char *fmt, int argc, char **argv) 252 { 253 struct get_hook h; 254 scsireq_t *scsireq = scsireq_new(); 255 enum data_phase data_phase; 256 int count, amount; 257 char *data_fmt, *bp; 258 259 h.argc = argc; 260 h.argv = argv; 261 h.got = 0; 262 263 scsireq_reset(scsireq); 264 265 scsireq_build_visit(scsireq, 0, 0, 0, fmt, iget, (void *)&h); 266 267 /* Three choices here: 268 * 1. We've used up all the args and have no data phase. 269 * 2. We have input data ("-i") 270 * 3. We have output data ("-o") 271 */ 272 273 if (h.got >= h.argc) 274 { 275 data_phase = none; 276 count = scsireq->datalen = 0; 277 } 278 else 279 { 280 char *flag = cget(&h, 0); 281 282 if (strcmp(flag, "-o") == 0) 283 { 284 data_phase = out; 285 scsireq->flags = SCCMD_WRITE; 286 } 287 else if (strcmp(flag, "-i") == 0) 288 { 289 data_phase = in; 290 scsireq->flags = SCCMD_READ; 291 } 292 else 293 { 294 fprintf(stderr, 295 "Need either \"-i\" or \"-o\" for data phase; not \"%s\".\n", flag); 296 usage(); 297 } 298 299 count = scsireq->datalen = iget(&h, 0); 300 if (count) { 301 data_fmt = cget(&h, 0); 302 303 scsireq->databuf = malloc(count); 304 if (scsireq->databuf == NULL) 305 err(1, NULL); 306 307 if (data_phase == out) { 308 if (strcmp(data_fmt, "-") == 0) { 309 bp = (char *)scsireq->databuf; 310 while (count > 0 && 311 (amount = read(STDIN_FILENO, 312 bp, count)) > 0) { 313 count -= amount; 314 bp += amount; 315 } 316 if (amount == -1) 317 err(1, "read"); 318 else if (amount == 0) { 319 /* early EOF */ 320 fprintf(stderr, 321 "Warning: only read %lu bytes out of %lu.\n", 322 scsireq->datalen - (u_long)count, 323 scsireq->datalen); 324 scsireq->datalen -= (u_long)count; 325 } 326 } 327 else 328 { 329 bzero(scsireq->databuf, count); 330 scsireq_encode_visit(scsireq, data_fmt, iget, (void *)&h); 331 } 332 } 333 } 334 } 335 336 337 scsireq->timeout = seconds * 1000; 338 339 if (scsireq_enter(fd, scsireq) == -1) 340 { 341 scsi_debug(stderr, -1, scsireq); 342 exit(1); 343 } 344 345 if (SCSIREQ_ERROR(scsireq)) 346 scsi_debug(stderr, 0, scsireq); 347 348 if (count && data_phase == in) 349 { 350 if (strcmp(data_fmt, "-") == 0) /* stdout */ 351 { 352 bp = (char *)scsireq->databuf; 353 while (count > 0 && (amount = write(STDOUT_FILENO, bp, count)) > 0) 354 { 355 count -= amount; 356 bp += amount; 357 } 358 if (amount < 0) 359 err(1, "write"); 360 else if (amount == 0) 361 fprintf(stderr, "Warning: wrote only %lu bytes out of %lu.\n", 362 scsireq->datalen - count, 363 scsireq->datalen); 364 365 } 366 else 367 { 368 scsireq_decode_visit(scsireq, data_fmt, arg_put, 0); 369 putchar('\n'); 370 } 371 } 372 } 373 374 void mode_sense(int fd, u_char *data, int len, int pc, int page) 375 { 376 scsireq_t *scsireq; 377 378 bzero(data, len); 379 380 scsireq = scsireq_new(); 381 382 if (scsireq_enter(fd, scsireq_build(scsireq, 383 len, data, SCCMD_READ, 384 "1A 0 v:2 {Page Control} v:6 {Page Code} 0 v:i1 {Allocation Length} 0", 385 pc, page, len)) == -1) /* Mode sense */ 386 { 387 scsi_debug(stderr, -1, scsireq); 388 exit(1); 389 } 390 391 if (SCSIREQ_ERROR(scsireq)) 392 { 393 scsi_debug(stderr, 0, scsireq); 394 exit(1); 395 } 396 397 free(scsireq); 398 } 399 400 void mode_select(int fd, u_char *data, int len, int perm) 401 { 402 scsireq_t *scsireq; 403 404 scsireq = scsireq_new(); 405 406 if (scsireq_enter(fd, scsireq_build(scsireq, 407 len, data, SCCMD_WRITE, 408 "15 0:7 v:1 {SP} 0 0 v:i1 {Allocation Length} 0", perm, len)) == -1) /* Mode select */ 409 { 410 scsi_debug(stderr, -1, scsireq); 411 exit(1); 412 } 413 414 if (SCSIREQ_ERROR(scsireq)) 415 { 416 scsi_debug(stderr, 0, scsireq); 417 exit(1); 418 } 419 420 free(scsireq); 421 } 422 423 424 #define START_ENTRY '{' 425 #define END_ENTRY '}' 426 427 static void 428 skipwhite(FILE *f) 429 { 430 int c; 431 432 skip_again: 433 434 while (isspace(c = getc(f))) 435 continue; 436 437 if (c == '#') { 438 while ((c = getc(f)) != '\n' && c != EOF) 439 continue; 440 goto skip_again; 441 } 442 443 ungetc(c, f); 444 } 445 446 /* mode_lookup: Lookup a format description for a given page. 447 */ 448 char *mode_db = "/usr/share/misc/scsi_modes"; 449 static char *mode_lookup(int page) 450 { 451 char *new_db; 452 FILE *modes; 453 int match, next, found, c; 454 static char fmt[1024]; /* XXX This should be with strealloc */ 455 int page_desc; 456 new_db = getenv("SCSI_MODES"); 457 458 if (new_db) 459 mode_db = new_db; 460 461 modes = fopen(mode_db, "r"); 462 if (modes == NULL) 463 return 0; 464 465 next = 0; 466 found = 0; 467 468 while (!found) { 469 470 skipwhite(modes); 471 472 if (fscanf(modes, "%i", &page_desc) != 1) 473 break; 474 475 if (page_desc == page) 476 found = 1; 477 478 skipwhite(modes); 479 if (getc(modes) != START_ENTRY) { 480 errx(1, "Expected %c", START_ENTRY); 481 } 482 483 match = 1; 484 while (match != 0) { 485 c = getc(modes); 486 if (c == EOF) 487 fprintf(stderr, "Expected %c.\n", END_ENTRY); 488 489 if (c == START_ENTRY) { 490 match++; 491 } 492 if (c == END_ENTRY) { 493 match--; 494 if (match == 0) 495 break; 496 } 497 if (found && c != '\n') { 498 if (next >= sizeof(fmt)) { 499 errx(1, "Stupid program: Buffer overflow.\n"); 500 } 501 502 fmt[next++] = (u_char)c; 503 } 504 } 505 } 506 fclose(modes); 507 fmt[next] = 0; 508 509 return (found) ? fmt : 0; 510 } 511 512 /* -------- edit: Mode Select Editor --------- 513 */ 514 struct editinfo 515 { 516 long can_edit; 517 long default_value; 518 } editinfo[64]; /* XXX Bogus fixed size */ 519 520 static int editind; 521 volatile int edit_opened; 522 static FILE *edit_file; 523 static char edit_name[L_tmpnam]; 524 525 static void 526 edit_rewind(void) 527 { 528 editind = 0; 529 } 530 531 static void 532 edit_done(void) 533 { 534 int opened; 535 536 sigset_t all, prev; 537 sigfillset(&all); 538 539 (void)sigprocmask(SIG_SETMASK, &all, &prev); 540 541 opened = (int)edit_opened; 542 edit_opened = 0; 543 544 (void)sigprocmask(SIG_SETMASK, &prev, 0); 545 546 if (opened) 547 { 548 if (fclose(edit_file)) 549 perror(edit_name); 550 if (unlink(edit_name)) 551 perror(edit_name); 552 } 553 } 554 555 static void 556 edit_init(void) 557 { 558 int fd; 559 560 edit_rewind(); 561 strlcpy(edit_name, "/var/tmp/scXXXXXXXX", sizeof edit_name); 562 if ((fd = mkstemp(edit_name)) == -1) 563 err(1, "mkstemp"); 564 if ( (edit_file = fdopen(fd, "w+")) == 0) 565 err(1, "fdopen"); 566 edit_opened = 1; 567 568 atexit(edit_done); 569 } 570 571 static void 572 edit_check(void *hook, int letter, void *arg, int count, char *name) 573 { 574 if (letter != 'i' && letter != 'b') { 575 errx(1, "Can't edit format %c.\n", letter); 576 } 577 578 if (editind >= sizeof(editinfo) / sizeof(editinfo[0])) { 579 errx(1, "edit table overflow"); 580 } 581 editinfo[editind].can_edit = ((long)arg != 0); 582 editind++; 583 } 584 585 static void 586 edit_defaults(void *hook, int letter, void *arg, int count, char *name) 587 { 588 if (letter != 'i' && letter != 'b') { 589 errx(1, "Can't edit format %c.\n", letter); 590 } 591 592 editinfo[editind].default_value = ((long)arg); 593 editind++; 594 } 595 596 static void 597 edit_report(void *hook, int letter, void *arg, int count, char *name) 598 { 599 if (editinfo[editind].can_edit) { 600 if (letter != 'i' && letter != 'b') { 601 errx(1, "Can't report format %c.\n", letter); 602 } 603 604 fprintf(edit_file, "%s: %ld\n", name, (long)arg); 605 } 606 607 editind++; 608 } 609 610 static int 611 edit_get(void *hook, char *name) 612 { 613 int arg = editinfo[editind].default_value; 614 615 if (editinfo[editind].can_edit) { 616 char line[80]; 617 size_t len; 618 if (fgets(line, sizeof(line), edit_file) == NULL) 619 err(1, "fgets"); 620 621 len = strlen(line); 622 if (len && line[len - 1] == '\n') 623 line[len - 1] = '\0'; 624 625 if (strncmp(name, line, strlen(name)) != 0) { 626 errx(1, "Expected \"%s\" and read \"%s\"\n", 627 name, line); 628 } 629 630 arg = strtoul(line + strlen(name) + 2, 0, 0); 631 } 632 633 editind++; 634 return arg; 635 } 636 637 int 638 editit(const char *pathname) 639 { 640 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 641 sig_t sighup, sigint, sigquit; 642 pid_t pid; 643 int st; 644 645 ed = getenv("VISUAL"); 646 if (ed == NULL || ed[0] == '\0') 647 ed = getenv("EDITOR"); 648 if (ed == NULL || ed[0] == '\0') 649 ed = _PATH_VI; 650 if (asprintf(&p, "%s %s", ed, pathname) == -1) 651 return (-1); 652 argp[2] = p; 653 654 top: 655 sighup = signal(SIGHUP, SIG_IGN); 656 sigint = signal(SIGINT, SIG_IGN); 657 sigquit = signal(SIGQUIT, SIG_IGN); 658 if ((pid = fork()) == -1) { 659 int saved_errno = errno; 660 661 (void)signal(SIGHUP, sighup); 662 (void)signal(SIGINT, sigint); 663 (void)signal(SIGQUIT, sigquit); 664 if (saved_errno == EAGAIN) { 665 sleep(1); 666 goto top; 667 } 668 free(p); 669 errno = saved_errno; 670 return (-1); 671 } 672 if (pid == 0) { 673 execv(_PATH_BSHELL, argp); 674 _exit(127); 675 } 676 free(p); 677 for (;;) { 678 if (waitpid(pid, &st, 0) == -1) { 679 if (errno != EINTR) 680 return (-1); 681 } else 682 break; 683 } 684 (void)signal(SIGHUP, sighup); 685 (void)signal(SIGINT, sigint); 686 (void)signal(SIGQUIT, sigquit); 687 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 688 errno = ECHILD; 689 return (-1); 690 } 691 return (0); 692 } 693 694 static void 695 mode_edit(int fd, int page, int edit, int argc, char *argv[]) 696 { 697 int i; 698 u_char data[255]; 699 u_char *mode_pars; 700 struct mode_header 701 { 702 u_char mdl; /* Mode data length */ 703 u_char medium_type; 704 u_char dev_spec_par; 705 u_char bdl; /* Block descriptor length */ 706 }; 707 708 struct mode_page_header 709 { 710 u_char page_code; 711 u_char page_length; 712 }; 713 714 struct mode_header *mh; 715 struct mode_page_header *mph; 716 717 char *fmt = mode_lookup(page); 718 if (!fmt && verbose) { 719 fprintf(stderr, 720 "No mode data base entry in \"%s\" for page %d; binary %s only.\n", 721 mode_db, page, (edit ? "edit" : "display")); 722 } 723 724 if (edit) { 725 if (!fmt) { 726 errx(1, "Sorry: can't edit without a format.\n"); 727 } 728 729 if (pagectl != 0 && pagectl != 3) { 730 errx(1, 731 "It only makes sense to edit page 0 (current) or page 3 (saved values)\n"); 732 } 733 734 verbose = 1; 735 736 mode_sense(fd, data, sizeof(data), 1, page); 737 738 mh = (struct mode_header *)data; 739 mph = (struct mode_page_header *) 740 (((char *)mh) + sizeof(*mh) + mh->bdl); 741 742 mode_pars = (char *)mph + sizeof(*mph); 743 744 edit_init(); 745 scsireq_buff_decode_visit(mode_pars, mh->mdl, 746 fmt, edit_check, 0); 747 748 mode_sense(fd, data, sizeof(data), 0, page); 749 750 edit_rewind(); 751 scsireq_buff_decode_visit(mode_pars, mh->mdl, 752 fmt, edit_defaults, 0); 753 754 edit_rewind(); 755 scsireq_buff_decode_visit(mode_pars, mh->mdl, 756 fmt, edit_report, 0); 757 758 fclose(edit_file); 759 if (editit(edit_name) == -1 && errno != ECHILD) 760 err(1, "edit %s", edit_name); 761 if ((edit_file = fopen(edit_name, "r")) == NULL) 762 err(1, "open %s", edit_name); 763 764 edit_rewind(); 765 scsireq_buff_encode_visit(mode_pars, mh->mdl, 766 fmt, edit_get, 0); 767 768 /* Eliminate block descriptors: 769 */ 770 bcopy((char *)mph, ((char *)mh) + sizeof(*mh), 771 sizeof(*mph) + mph->page_length); 772 773 mh->bdl = 0; 774 mph = (struct mode_page_header *) (((char *)mh) + sizeof(*mh)); 775 mode_pars = ((char *)mph) + 2; 776 777 #if 0 778 /* Turn this on to see what you're sending to the 779 * device: 780 */ 781 edit_rewind(); 782 scsireq_buff_decode_visit(mode_pars, 783 mh->mdl, fmt, arg_put, 0); 784 #endif 785 786 edit_done(); 787 788 /* Make it permanent if pageselect is three. 789 */ 790 791 mph->page_code &= ~0xC0; /* Clear PS and RESERVED */ 792 mh->mdl = 0; /* Reserved for mode select */ 793 794 mode_select(fd, (char *)mh, 795 sizeof(*mh) + mh->bdl + sizeof(*mph) + mph->page_length, 796 (pagectl == 3)); 797 798 exit(0); 799 } 800 801 mode_sense(fd, data, sizeof(data), pagectl, page); 802 803 /* Skip over the block descriptors. 804 */ 805 mh = (struct mode_header *)data; 806 mph = (struct mode_page_header *)(((char *)mh) + sizeof(*mh) + mh->bdl); 807 mode_pars = (char *)mph + sizeof(*mph); 808 809 if (!fmt) { 810 for (i = 0; i < mh->mdl; i++) { 811 printf("%02x%c",mode_pars[i], 812 (((i + 1) % 8) == 0) ? '\n' : ' '); 813 } 814 putc('\n', stdout); 815 } else { 816 verbose = 1; 817 scsireq_buff_decode_visit(mode_pars, 818 mh->mdl, fmt, arg_put, 0); 819 } 820 } 821 822 int 823 main(int argc, char **argv) 824 { 825 procargs(&argc,&argv); 826 827 /* XXX This has grown to the point that it should be cleaned up. 828 */ 829 if (debugflag) { 830 if (ioctl(fd,SCIOCDEBUG,&debuglevel) == -1) 831 err(1, "SCIOCDEBUG"); 832 } else if (commandflag) { 833 char *fmt; 834 835 if (argc < 1) { 836 fprintf(stderr, "Need the command format string.\n"); 837 usage(); 838 } 839 840 841 fmt = argv[0]; 842 843 argc -= 1; 844 argv += 1; 845 846 do_cmd(fd, fmt, argc, argv); 847 } else if (modeflag) 848 mode_edit(fd, modepage, editflag, argc, argv); 849 850 exit(0); 851 } 852