diff options
Diffstat (limited to 'sbin/scsi/scsi.c')
-rw-r--r-- | sbin/scsi/scsi.c | 974 |
1 files changed, 974 insertions, 0 deletions
diff --git a/sbin/scsi/scsi.c b/sbin/scsi/scsi.c new file mode 100644 index 0000000..eb399a1 --- /dev/null +++ b/sbin/scsi/scsi.c @@ -0,0 +1,974 @@ +/* + * Written By Julian ELischer + * Copyright julian Elischer 1993. + * Permission is granted to use or redistribute this file in any way as long + * as this notice remains. Julian Elischer does not guarantee that this file + * is totally correct for any given task and users of this file must + * accept responsibility for any damage that occurs from the application of this + * file. + * + * (julian@tfs.com julian@dialix.oz.au) + * + * User SCSI hooks added by Peter Dufault: + * + * Copyright (c) 1994 HD Associates + * (contact: dufault@hda.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of HD Associates + * may not be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY HD ASSOCIATES ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL HD ASSOCIATES BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: scsi.c,v 1.11 1996/04/06 11:00:28 joerg Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <sys/scsiio.h> +#include <sys/file.h> +#include <scsi.h> +#include <ctype.h> +#include <signal.h> + +int fd; +int debuglevel; +int debugflag; +int commandflag; +int reprobe; +int probe_all; +int verbose = 0; +int bus = -1; /* all busses */ +int targ = -1; /* all targs */ +int lun = 0; /* just lun 0 */ +int freeze = 0; /* Freeze this many seconds */ + +int modeflag; +int editflag; +int modepage = 0; /* Read this mode page */ +int pagectl = 0; /* Mode sense page control */ +int seconds = 2; + +void usage(void) +{ + printf( + +"Usage:\n" +"\n" +" scsi -f device -d debug_level # To set debug level\n" +" scsi -f device [-v] -z seconds # To freeze bus\n" +" scsi -f device -m page [-P pc] # To read mode pages\n" +" scsi -f device -p [-b bus] [-l lun] # To probe all devices\n" +" scsi -f device -r [-b bus] [-t targ] [-l lun] # To reprobe a device\n" +" scsi -f device [-v] [-s seconds] -c cmd_fmt [arg0 ... argn] # A command...\n" +" -o count out_fmt [arg0 ... argn] # EITHER (data out)\n" +" -i count in_fmt # OR (data in)\n" +"\n" +"\"out_fmt\" can be \"-\" to read output data from stdin;\n" +"\"in_fmt\" can be \"-\" to write input data to stdout;\n" +"\n" +"If debugging is not compiled in the kernel, \"-d\" will have no effect\n" + +); + + exit (1); +} + +void procargs(int *argc_p, char ***argv_p) +{ + int argc = *argc_p; + char **argv = *argv_p; + extern char *optarg; + extern int optind; + int fflag, + ch; + + fflag = 0; + commandflag = 0; + debugflag = 0; + while ((ch = getopt(argc, argv, "ceprvf:d:b:t:l:z:m:P:s:")) != EOF) { + switch (ch) { + case 'p': + probe_all = 1; + break; + case 'r': + reprobe = 1; + break; + case 'c': + commandflag = 1; + break; + case 'v': + verbose = 1; + break; + case 'e': + editflag = 1; + break; + case 'f': + if ((fd = scsi_open(optarg, O_RDWR)) < 0) { + (void) fprintf(stderr, + "%s: unable to open device %s: %s\n", + argv[0], optarg, strerror(errno)); + exit(errno); + } + fflag = 1; + break; + case 'd': + debuglevel = strtol(optarg, 0, 0); + debugflag = 1; + break; + case 'b': + bus = strtol(optarg, 0, 0); + break; + case 't': + targ = strtol(optarg, 0, 0); + break; + case 'l': + lun = strtol(optarg, 0, 0); + break; + case 'z': + freeze = strtol(optarg, 0, 0); + break; + case 'P': + pagectl = strtol(optarg, 0, 0); + break; + case 's': + seconds = strtol(optarg, 0, 0); + break; + case 'm': + modeflag = 1; + modepage = strtol(optarg, 0, 0); + break; + case '?': + default: + usage(); + } + } + *argc_p = argc - optind; + *argv_p = argv + optind; + + if (!fflag) usage(); +} + +/* get_hook: Structure for evaluating args in a callback. + */ +struct get_hook +{ + int argc; + char **argv; + int got; +}; + +/* iget: Integer argument callback + */ +int iget(void *hook, char *name) +{ + struct get_hook *h = (struct get_hook *)hook; + int arg; + + if (h->got >= h->argc) + { + fprintf(stderr, "Expecting an integer argument.\n"); + usage(); + } + arg = strtol(h->argv[h->got], 0, 0); + h->got++; + + if (verbose && name && *name) + printf("%s: %d\n", name, arg); + + return arg; +} + +/* cget: char * argument callback + */ +char *cget(void *hook, char *name) +{ + struct get_hook *h = (struct get_hook *)hook; + char *arg; + + if (h->got >= h->argc) + { + fprintf(stderr, "Expecting a character pointer argument.\n"); + usage(); + } + arg = h->argv[h->got]; + h->got++; + + if (verbose && name) + printf("cget: %s: %s", name, arg); + + return arg; +} + +/* arg_put: "put argument" callback + */ +void arg_put(void *hook, int letter, void *arg, int count, char *name) +{ + if (verbose && name && *name) + printf("%s: ", name); + + switch(letter) + { + case 'i': + case 'b': + printf("%d ", (int)arg); + break; + + case 'c': + case 'z': + { + char *p = malloc(count + 1); + p[count] = 0; + strncpy(p, (char *)arg, count); + if (letter == 'z') + { + int i; + for (i = count - 1; i >= 0; i--) + if (p[i] == ' ') + p[i] = 0; + else + break; + } + printf("%s ", p); + } + + break; + + default: + printf("Unknown format letter: '%c'\n", letter); + } + if (verbose) + putchar('\n'); +} + +int arg_get (void *hook, char *field_name) +{ + printf("get \"%s\".\n", field_name); + return 0; +} + +/* data_phase: SCSI bus data phase: DATA IN, DATA OUT, or no data transfer. + */ +enum data_phase {none = 0, in, out}; + +/* do_cmd: Send a command to a SCSI device + */ +static void +do_cmd(int fd, char *fmt, int argc, char **argv) +{ + struct get_hook h; + scsireq_t *scsireq = scsireq_new(); + enum data_phase data_phase; + int count, amount; + char *data_fmt, *bp; + + h.argc = argc; + h.argv = argv; + h.got = 0; + + scsireq_reset(scsireq); + + scsireq_build_visit(scsireq, 0, 0, 0, fmt, iget, (void *)&h); + + /* Three choices here: + * 1. We've used up all the args and have no data phase. + * 2. We have input data ("-i") + * 3. We have output data ("-o") + */ + + if (h.got >= h.argc) + { + data_phase = none; + count = scsireq->datalen = 0; + } + else + { + char *flag = cget(&h, 0); + + if (strcmp(flag, "-o") == 0) + { + data_phase = out; + scsireq->flags = SCCMD_WRITE; + } + else if (strcmp(flag, "-i") == 0) + { + data_phase = in; + scsireq->flags = SCCMD_READ; + } + else + { + fprintf(stderr, + "Need either \"-i\" or \"-o\" for data phase; not \"%s\".\n", flag); + usage(); + } + + count = scsireq->datalen = iget(&h, 0); + if (count) + { + data_fmt = cget(&h, 0); + + scsireq->databuf = malloc(count); + + if (data_phase == out) + { + if (strcmp(data_fmt, "-") == 0) /* Read data from stdin */ + { + bp = (char *)scsireq->databuf; + while (count > 0 && (amount = read(0, bp, count)) > 0) + { + count -= amount; + bp += amount; + } + if (amount == -1) + { + perror("read"); + exit(errno); + } + else if (amount == 0) + { + /* early EOF */ + fprintf(stderr, + "Warning: only read %lu bytes out of %lu.\n", + scsireq->datalen - (u_long)count, + scsireq->datalen); + scsireq->datalen -= (u_long)count; + } + } + else + { + bzero(scsireq->databuf, count); + scsireq_encode_visit(scsireq, data_fmt, iget, (void *)&h); + } + } + } + } + + + scsireq->timeout = seconds * 1000; + + if (scsireq_enter(fd, scsireq) == -1) + { + scsi_debug(stderr, -1, scsireq); + exit(errno); + } + + if (SCSIREQ_ERROR(scsireq)) + scsi_debug(stderr, 0, scsireq); + + if (count && data_phase == in) + { + if (strcmp(data_fmt, "-") == 0) /* stdout */ + { + bp = (char *)scsireq->databuf; + while (count > 0 && (amount = write(1, bp, count)) > 0) + { + count -= amount; + bp += amount; + } + if (amount < 0) + { + perror("write"); + exit(errno); + } + else if (amount == 0) + fprintf(stderr, "Warning: wrote only %d bytes out of %d.\n", + scsireq->datalen - count, + scsireq->datalen); + + } + else + { + scsireq_decode_visit(scsireq, data_fmt, arg_put, 0); + putchar('\n'); + } + } +} + +static void +freeze_ioctl(int fd, int op, void *data) +{ + if (ioctl(fd, SCIOCFREEZE, 0) == -1) { + if (errno == ENODEV) { + fprintf(stderr, + "Your kernel must be configured with option SCSI_FREEZE.\n"); + } + else + perror("SCIOCFREEZE"); + exit(errno); + } +} + +/* do_freeze: Freeze the bus for a given number of seconds. + */ +static void do_freeze(int seconds) +{ + if (seconds == -1) { + printf("Hit return to thaw: "); + fflush(stdout); + sync(); + + freeze_ioctl(fd, SCIOCFREEZE, 0); + + (void)getchar(); + + freeze_ioctl(fd, SCIOCTHAW, 0); + } + else { + sync(); + freeze_ioctl(fd, SCIOCFREEZETHAW, &seconds); + if (verbose) { + putchar('\007'); + fflush(stdout); + } + + freeze_ioctl(fd, SCIOCWAITTHAW, 0); + if (verbose) { + putchar('\007'); + fflush(stdout); + } + } +} + +void mode_sense(int fd, u_char *data, int len, int pc, int page) +{ + scsireq_t *scsireq; + + bzero(data, len); + + scsireq = scsireq_new(); + + if (scsireq_enter(fd, scsireq_build(scsireq, + len, data, SCCMD_READ, + "1A 0 v:2 {Page Control} v:6 {Page Code} 0 v:i1 {Allocation Length} 0", + pc, page, len)) == -1) /* Mode sense */ + { + scsi_debug(stderr, -1, scsireq); + exit(errno); + } + + if (SCSIREQ_ERROR(scsireq)) + { + scsi_debug(stderr, 0, scsireq); + exit(-1); + } + + free(scsireq); +} + +void mode_select(int fd, u_char *data, int len, int perm) +{ + scsireq_t *scsireq; + + scsireq = scsireq_new(); + + if (scsireq_enter(fd, scsireq_build(scsireq, + len, data, SCCMD_WRITE, + "15 0:7 v:1 {SP} 0 0 v:i1 {Allocation Length} 0", perm, len)) == -1) /* Mode select */ + { + scsi_debug(stderr, -1, scsireq); + exit(errno); + } + + if (SCSIREQ_ERROR(scsireq)) + { + scsi_debug(stderr, 0, scsireq); + exit(-1); + } + + free(scsireq); +} + + +#define START_ENTRY '{' +#define END_ENTRY '}' + +static void +skipwhite(FILE *f) +{ + int c; + +skip_again: + + while (isspace(c = getc(f))) + ; + + if (c == '#') { + while ((c = getc(f)) != '\n' && c != EOF) + ; + goto skip_again; + } + + ungetc(c, f); +} + +/* mode_lookup: Lookup a format description for a given page. + */ +char *mode_db = "/usr/share/misc/scsi_modes"; +static char *mode_lookup(int page) +{ + char *new_db; + FILE *modes; + int match, next, found, c; + static char fmt[1024]; /* XXX This should be with strealloc */ + int page_desc; + new_db = getenv("SCSI_MODES"); + + if (new_db) + mode_db = new_db; + + modes = fopen(mode_db, "r"); + if (modes == 0) + return 0; + + next = 0; + found = 0; + + while (!found) { + + skipwhite(modes); + + if (fscanf(modes, "%i", &page_desc) != 1) + break; + + if (page_desc == page) + found = 1; + + skipwhite(modes); + if (getc(modes) != START_ENTRY) { + fprintf(stderr, "Expected %c.\n", START_ENTRY); + exit(-1); + } + + match = 1; + while (match != 0) { + c = getc(modes); + if (c == EOF) { + fprintf(stderr, "Expected %c.\n", END_ENTRY); + } + + if (c == START_ENTRY) { + match++; + } + if (c == END_ENTRY) { + match--; + if (match == 0) + break; + } + if (found && c != '\n') { + if (next >= sizeof(fmt)) { + fprintf(stderr, "Stupid program: Buffer overflow.\n"); + exit(ENOMEM); + } + + fmt[next++] = (u_char)c; + } + } + } + fmt[next] = 0; + + return (found) ? fmt : 0; +} + +/* -------- edit: Mode Select Editor --------- + */ +struct editinfo +{ + int can_edit; + int default_value; +} editinfo[64]; /* XXX Bogus fixed size */ + +static int editind; +volatile int edit_opened; +static FILE *edit_file; +static char edit_name[L_tmpnam]; + +static inline void +edit_rewind(void) +{ + editind = 0; +} + +static void +edit_done(void) +{ + int opened; + + sigset_t all, prev; + sigfillset(&all); + + (void)sigprocmask(SIG_SETMASK, &all, &prev); + + opened = (int)edit_opened; + edit_opened = 0; + + (void)sigprocmask(SIG_SETMASK, &prev, 0); + + if (opened) + { + if (fclose(edit_file)) + perror(edit_name); + if (unlink(edit_name)) + perror(edit_name); + } +} + +static void +edit_init(void) +{ + edit_rewind(); + if (tmpnam(edit_name) == 0) { + perror("tmpnam failed"); + exit(errno); + } + if ( (edit_file = fopen(edit_name, "w")) == 0) { + perror(edit_name); + exit(errno); + } + edit_opened = 1; + + atexit(edit_done); +} + +static void +edit_check(void *hook, int letter, void *arg, int count, char *name) +{ + if (letter != 'i' && letter != 'b') { + fprintf(stderr, "Can't edit format %c.\n", letter); + exit(-1); + } + + if (editind >= sizeof(editinfo) / sizeof(editinfo[0])) { + fprintf(stderr, "edit table overflow\n"); + exit(ENOMEM); + } + editinfo[editind].can_edit = ((int)arg != 0); + editind++; +} + +static void +edit_defaults(void *hook, int letter, void *arg, int count, char *name) +{ + if (letter != 'i' && letter != 'b') { + fprintf(stderr, "Can't edit format %c.\n", letter); + exit(-1); + } + + editinfo[editind].default_value = ((int)arg); + editind++; +} + +static void +edit_report(void *hook, int letter, void *arg, int count, char *name) +{ + if (editinfo[editind].can_edit) { + if (letter != 'i' && letter != 'b') { + fprintf(stderr, "Can't report format %c.\n", letter); + exit(-1); + } + + fprintf(edit_file, "%s: %d\n", name, (int)arg); + } + + editind++; +} + +static int +edit_get(void *hook, char *name) +{ + struct get_hook *h = (struct get_hook *)hook; + int arg = editinfo[editind].default_value; + + if (editinfo[editind].can_edit) { + char line[80]; + if (fgets(line, sizeof(line), edit_file) == 0) { + perror("fgets"); + exit(errno); + } + + line[strlen(line) - 1] = 0; + + if (strncmp(name, line, strlen(name)) != 0) { + fprintf(stderr, "Expected \"%s\" and read \"%s\"\n", + name, line); + exit(-1); + } + + arg = strtoul(line + strlen(name) + 2, 0, 0); + } + + editind++; + return arg; +} + +static void +edit_edit(void) +{ + char *system_line; + char *editor = getenv("EDITOR"); + if (!editor) + editor = "vi"; + + fclose(edit_file); + + system_line = malloc(strlen(editor) + strlen(edit_name) + 6); + sprintf(system_line, "%s %s", editor, edit_name); + system(system_line); + free(system_line); + + if ( (edit_file = fopen(edit_name, "r")) == 0) { + perror(edit_name); + exit(errno); + } +} + +static void +mode_edit(int fd, int page, int edit, int argc, char *argv[]) +{ + int i; + u_char data[255]; + u_char *mode_pars; + struct mode_header + { + u_char mdl; /* Mode data length */ + u_char medium_type; + u_char dev_spec_par; + u_char bdl; /* Block descriptor length */ + }; + + struct mode_page_header + { + u_char page_code; + u_char page_length; + }; + + struct mode_header *mh; + struct mode_page_header *mph; + + char *fmt = mode_lookup(page); + if (!fmt && verbose) { + fprintf(stderr, + "No mode data base entry in \"%s\" for page %d; binary %s only.\n", + mode_db, page, (edit ? "edit" : "display")); + } + + if (edit) { + if (!fmt) { + fprintf(stderr, "Sorry: can't edit without a format.\n"); + exit(-1); + } + + if (pagectl != 0 && pagectl != 3) { + fprintf(stderr, +"It only makes sense to edit page 0 (current) or page 3 (saved values)\n"); + exit(-1); + } + + verbose = 1; + + mode_sense(fd, data, sizeof(data), 1, page); + + mh = (struct mode_header *)data; + mph = (struct mode_page_header *) + (((char *)mh) + sizeof(*mh) + mh->bdl); + + mode_pars = (char *)mph + sizeof(*mph); + + edit_init(); + scsireq_buff_decode_visit(mode_pars, mh->mdl, + fmt, edit_check, 0); + + mode_sense(fd, data, sizeof(data), 0, page); + + edit_rewind(); + scsireq_buff_decode_visit(mode_pars, mh->mdl, + fmt, edit_defaults, 0); + + edit_rewind(); + scsireq_buff_decode_visit(mode_pars, mh->mdl, + fmt, edit_report, 0); + + edit_edit(); + + edit_rewind(); + scsireq_buff_encode_visit(mode_pars, mh->mdl, + fmt, edit_get, 0); + + /* Eliminate block descriptors: + */ + bcopy((char *)mph, ((char *)mh) + sizeof(*mh), + sizeof(*mph) + mph->page_length); + + mh->bdl = mh->dev_spec_par = 0; + mph = (struct mode_page_header *) (((char *)mh) + sizeof(*mh)); + mode_pars = ((char *)mph) + 2; + +#if 0 + /* Turn this on to see what you're sending to the + * device: + */ + edit_rewind(); + scsireq_buff_decode_visit(mode_pars, + mh->mdl, fmt, arg_put, 0); +#endif + + edit_done(); + + /* Make it permanent if pageselect is three. + */ + + mph->page_code &= ~0xC0; /* Clear PS and RESERVED */ + mh->mdl = 0; /* Reserved for mode select */ + + mode_select(fd, (char *)mh, + sizeof(*mh) + mh->bdl + sizeof(*mph) + mph->page_length, + (pagectl == 3)); + + exit(0); + } + + mode_sense(fd, data, sizeof(data), pagectl, page); + + /* Skip over the block descriptors. + */ + mh = (struct mode_header *)data; + mph = (struct mode_page_header *)(((char *)mh) + sizeof(*mh) + mh->bdl); + mode_pars = (char *)mph + sizeof(*mph); + + if (!fmt) { + for (i = 0; i < mh->mdl; i++) { + printf("%02x%c",mode_pars[i], + (((i + 1) % 8) == 0) ? '\n' : ' '); + } + putc('\n', stdout); + } else { + verbose = 1; + scsireq_buff_decode_visit(mode_pars, + mh->mdl, fmt, arg_put, 0); + } +} + +/* do_probe_all: Loop over all SCSI IDs and see if something is + * there. This only does BUS 0 LUN 0. + */ +void do_probe_all(void) +{ + scsireq_t *scsireq; + + char vendor_id[8 + 1], product_id[16 + 1], revision[4 + 1]; + int id; + u_char *inq_buf = malloc(96); + struct scsi_addr addr; + + scsireq = scsireq_build(scsireq_new(), + 96, inq_buf, SCCMD_READ, + "12 0 0 0 v 0", 96); + + addr.scbus = (bus == -1) ? 0 : bus; + addr.lun = lun; + + if (addr.scbus || addr.lun) + { + printf("For bus %d lun %d:\n", addr.scbus, addr.lun); + } + + for (id = 0; id < 8; id++) + { + addr.target = id; + + printf("%d: ", id); + if (ioctl(fd, SCIOCADDR, &addr) == -1) { + if (errno == ENXIO) + { + errno = 0; + printf("nothing.\n"); + } + else + printf("SCIOCADDR: %s\n", strerror(errno)); + + continue; + } + + if (scsireq_enter(fd, scsireq) == -1) { + printf("scsireq_enter: %s\n", strerror(errno)); + continue; + } + + vendor_id[sizeof(vendor_id) - 1] = 0; + product_id[sizeof(product_id) - 1] = 0; + revision[sizeof(revision) - 1] = 0; + + scsireq_decode(scsireq, "s8 c8 c16 c4", + vendor_id, product_id, revision); + + printf("%s %s %s\n", vendor_id, product_id, revision); + } +} + +void main(int argc, char **argv) +{ + struct scsi_addr scaddr; + + procargs(&argc,&argv); + + /* XXX This has grown to the point that it should be cleaned up. + */ + if (freeze) { + do_freeze(freeze); + } else if (probe_all) { + do_probe_all(); + } else if(reprobe) { + scaddr.scbus = bus; + scaddr.target = targ; + scaddr.lun = lun; + + if (ioctl(fd,SCIOCREPROBE,&scaddr) == -1) + perror("ioctl"); + } else if(debugflag) { + if (ioctl(fd,SCIOCDEBUG,&debuglevel) == -1) + { + perror("ioctl [SCIODEBUG]"); + exit(1); + } + } else if (commandflag) { + int i; + char *fmt; + + if (argc < 1) { + fprintf(stderr, "Need the command format string.\n"); + usage(); + } + + + fmt = argv[0]; + + argc -= 1; + argv += 1; + + do_cmd(fd, fmt, argc, argv); + } else if (modeflag) { + mode_edit(fd, modepage, editflag, argc, argv); + } + exit(0); +} |