/* a tool to open a scsi device and issue some useful commands such as INQUIRY and helpers to call various PERSISTENT RESERVATION functions Copyright ronnie sahlberg 2007 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . */ /* very incomplete and needs to be enhanced with noice command line options to drive it. we need access to an array that supports the PERSISTENT RESERVATION cdb's before we can proceed */ /* scsi bugs: INQUIRY takes a 2 byte allocation_length parameter but it appears that it only looks at the low byte. If you specify 0x00ff all is well but if you specify 0x0100 it gets confused and returnes garbage data for (e.g) SupportedVPDPages. Same goes for UnitSerialNumber and probably all other inq pages as well. */ #include #include #include #include #include #include #include #include "popt.h" #define SCSI_TIMEOUT 5000 /* ms */ static char *command = NULL; static char *device = NULL; static char *key = NULL; static char *rmkey = NULL; static int scope = -1; static int type = -1; const char *sensetable[16]={ "no sense", "recovered error", "not ready", "medium error", "hardware error", "illegal request", "unit attention", "data protect", "blank check", "vendor specific", "copy aborted", "aboreted command", "unknown", "unknown", "unknown", "unknown" }; int scsi_io(int fd, unsigned char *cdb, unsigned char cdb_size, int xfer_dir, unsigned char *data, unsigned int *data_size, unsigned char *sense, unsigned int *sense_len) { sg_io_hdr_t io_hdr; memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); io_hdr.interface_id = 'S'; /* CDB */ io_hdr.cmdp = cdb; io_hdr.cmd_len = cdb_size; /* Where to store the sense_data, if there was an error */ io_hdr.sbp = sense; io_hdr.mx_sb_len = *sense_len; *sense_len=0; /* Transfer direction, either in or out. Linux does not yet support bidirectional SCSI transfers ? */ io_hdr.dxfer_direction = xfer_dir; /* Where to store the DATA IN/OUT from the device and how big the buffer is */ io_hdr.dxferp = data; io_hdr.dxfer_len = *data_size; /* SCSI timeout in ms */ io_hdr.timeout = SCSI_TIMEOUT; if(ioctl(fd, SG_IO, &io_hdr) < 0){ perror("SG_IO ioctl failed"); return -1; } /* now for the error processing */ if((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK){ if(io_hdr.sb_len_wr > 0){ *sense_len=io_hdr.sb_len_wr; return 0; } } if(io_hdr.masked_status){ printf("status=0x%x\n", io_hdr.status); printf("masked_status=0x%x\n", io_hdr.masked_status); return -2; } if(io_hdr.host_status){ printf("host_status=0x%x\n", io_hdr.host_status); return -3; } if(io_hdr.driver_status){ printf("driver_status=0x%x\n", io_hdr.driver_status); return -4; } #if 0 {int i; printf("CDB:\n"); for(i=0;istring){ if(vs->value==v){ return vs->string; } vs++; } return ""; } void print_sense_data(unsigned char *sense, int sense_len) { int i; unsigned char asc, ascq; printf("Device returned sense information\n"); if(sense[0]==0x70){ printf("filemark:%d eom:%d ili:%d sense-key:0x%02x (%s)\n", !!(sense[2]&0x80), !!(sense[2]&0x40), !!(sense[2]&0x20), sense[2]&0x0f, sensetable[sense[2]&0x0f]); printf("command specific info: 0x%02x 0x%02x 0x%02x 0x%02x\n", sense[8],sense[9],sense[10],sense[11]); asc=sense[12]; printf("additional sense code:0x%02x\n", asc); ascq=sense[13]; printf("additional sense code qualifier:0x%02x\n", ascq); printf("field replacable unit code:0x%02x\n", sense[14]); if((asc==0x20)&&(ascq==0x00)) printf("INVALID COMMAND OPERATION CODE\n"); } printf("Sense data:\n"); for(i=0;i>8)&0xff; cdb[4]=data_size&0xff; printf("Standard INQUIRY Data:\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* Peripheral Qualifier */ printf("Peripheral Qualifier:%c%c%cb\n", '0'+!!(data[0]&0x80), '0'+!!(data[0]&0x40), '0'+!!(data[0]&0x20)); /* Peripheral Device Type */ printf("Peripheral Device Type: 0x%02x (%s)\n", data[0]&0x1f, val_to_str(peripheral_device_types, data[0]&0x1f)); /* RMB */ printf("RMB: %s device\n", data[1]&0x80?"REMOVABLE":"NON-REMOVABLE"); /* SCSI Version */ printf("SCSI Version: 0x%02x (%s)\n", data[2], val_to_str(scsi_versions, data[2])); /* NormACA, HiSUP, Response Data Format */ printf("NormACA:%d HiSup:%d ResponseDataFormat:%d\n", !!(data[3]&0x20), !!(data[3]&0x10), data[3]&0x0f); switch(data[3]&0x0f){ /*SPC-2/SPC-3/SPC-4*/ case 2: /*SPC (not strictly correct but we print it like 2 anyway)*/ case 1: /* SCCS ... */ printf("SCCS:%d ACC:%d TPGS:%c%cb 3PC:%d PROTECT:%d\n", !!(data[5]&0x80), !!(data[5]&0x40), '0'+!!(data[5]&0x20), '0'+!!(data[5]&0x10), !!(data[5]&0x08), !!(data[5]&0x01)); /* Encserv ... */ printf("Encserv:%d VS:%d MultiP:%d ADDR16:%d\n", !!(data[6]&0x40), !!(data[6]&0x20), !!(data[6]&0x10), !!(data[6]&0x01)); /* WBUS16 ... */ printf("WBUS16:%d SYNC:%d CmdQue:%d VS:%d\n", !!(data[7]&0x20), !!(data[7]&0x10), !!(data[7]&0x02), !!(data[7]&0x01)); /* T10 vendor Identification */ printf("Vendor:"); for(i=0;i<8;i++)printf("%c",data[8+i]);printf("\n"); /* Product Identification */ printf("Product:"); for(i=0;i<16;i++)printf("%c",data[16+i]);printf("\n"); /* Product Revision Level */ printf("Product Revision:"); for(i=0;i<4;i++)printf("%c",data[32+i]);printf("\n"); break; } return 0; } int scsi_inquiry_supported_vpd_pages(int fd) { unsigned char cdb[]={0x12,0x01,0,0,0,0}; unsigned int data_size=0xff; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; int res, pl, i; cdb[3]=(data_size>>8)&0xff; cdb[4]=data_size&0xff; printf("INQUIRY Supported VPD Pages:\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* Page Length */ pl=data[3]; /* Pages */ for(i=4;i<(pl+4);i++){ printf("Page:%02xh (%s)\n", data[i], val_to_str(vpd_pages, data[i])); } return 0; } int scsi_inquiry_unit_serial_number(int fd) { unsigned char cdb[]={0x12,0x01,0x80,0,0,0}; unsigned int data_size=0x00ff; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; int res, pl, i; cdb[3]=(data_size>>8)&0xff; cdb[4]=data_size&0xff; printf("INQUIRY Unit Serial Number:\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* Page Length */ pl=data[3]; /* Unit Serial Number */ printf("Unit Serial Number:"); for(i=4;i<(pl+4);i++)printf("%c",data[i]&0xff);printf("\n"); return 0; } int scsi_persistent_reserve_in_read_keys(int fd) { unsigned char cdb[]={0x5e,0,0,0,0,0,0,0,0,0}; unsigned int data_size=0x00ff; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=0; int res, i; unsigned long prgeneration, additional_length; cdb[1]=service_action; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; printf("PERSISTENT RESERVE IN: READ KEYS\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* PRGeneration */ prgeneration=data[0]; prgeneration<<=8;prgeneration|=data[1]; prgeneration<<=8;prgeneration|=data[2]; prgeneration<<=8;prgeneration|=data[3]; printf("PRGeneration:%lu\n", prgeneration); /* Additional Length */ additional_length=data[4]; additional_length<<=8;additional_length|=data[5]; additional_length<<=8;additional_length|=data[6]; additional_length<<=8;additional_length|=data[7]; printf("Additional Length:%lu\n", additional_length); /* print the registered keys */ for(i=0;i>8)&0xff; cdb[8]=data_size&0xff; printf("PERSISTENT RESERVE IN: READ RESERVATION\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* PRGeneration */ prgeneration=data[0]; prgeneration<<=8;prgeneration|=data[1]; prgeneration<<=8;prgeneration|=data[2]; prgeneration<<=8;prgeneration|=data[3]; printf("PRGeneration:%lu\n", prgeneration); /* Additional Length */ additional_length=data[4]; additional_length<<=8;additional_length|=data[5]; additional_length<<=8;additional_length|=data[6]; additional_length<<=8;additional_length|=data[7]; printf("Additional Length:%lu\n", additional_length); if(additional_length==16){ printf("Key:%02x%02x%02x%02x%02x%02x%02x%02x\n", data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15]); printf("Scope:%xh Type:%xh\n",data[21]>>4,data[21]&0x0f); } return 0; } int scsi_persistent_reserve_in_report_capabilities(int fd) { unsigned char cdb[]={0x5e,0,0,0,0,0,0,0,0,0}; unsigned int data_size=0x00ff; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=2; int res; unsigned short length, type_mask; cdb[1]=service_action; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; printf("PERSISTENT RESERVE IN: REPORT CAPABILITIES\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* Length */ length=data[0]; length<<=8;length|=data[1]; printf("Length:%d\n", length); /* CRH ... */ printf("CRH:%d SIP_C:%d ATP_C:%d PTPL_C:%d\n", !!(data[2]&0x10), !!(data[2]&0x08), !!(data[2]&0x04), !!(data[2]&0x01)); /* TMV ... */ printf("TMV:%d ALLOW_COMMANDS:%c%c%cb PTPL_A:%d\n", !!(data[3]&0x80), '0'+(!!(data[3]&0x40)), '0'+(!!(data[3]&0x20)), '0'+(!!(data[3]&0x10)), !!(data[3]&0x01)); /* Persistent Reservation Type Mask */ type_mask=data[4]; type_mask<<=8;type_mask|=data[5]; printf("Persistent Reservation Type Mask:0x%04x\n", type_mask); printf("WR_EX_AR:%d EX_AC_RO:%d WR_EX_RO:%d EX_AC:%d WR_EX:%d EX_AC_AR:%d\n", !!(data[4]&0x80), !!(data[4]&0x40), !!(data[4]&0x20), !!(data[4]&0x08), !!(data[4]&0x02), !!(data[4]&0x01)); return 0; } int scsi_persistent_reserve_in_read_full_status(int fd) { unsigned char cdb[]={0x5e,0,0,0,0,0,0,0,0,0}; unsigned int data_size=0x00ff; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=3; int res; unsigned long prgeneration, additional_length; cdb[1]=service_action; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; printf("PERSISTENT RESERVE IN: READ FULL STATUS\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_FROM_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } /* PRGeneration */ prgeneration=data[0]; prgeneration<<=8;prgeneration|=data[1]; prgeneration<<=8;prgeneration|=data[2]; prgeneration<<=8;prgeneration|=data[3]; printf("PRGeneration:%lu\n", prgeneration); /* Additional Length */ additional_length=data[4]; additional_length<<=8;additional_length|=data[5]; additional_length<<=8;additional_length|=data[6]; additional_length<<=8;additional_length|=data[7]; printf("Additional Length:%lu\n", additional_length); /*XXX*/ return 0; } int scsi_persistent_reserve_out_clear(int fd) { unsigned char cdb[]={0x5f,0,0,0,0,0,0,0,0,0}; unsigned int data_size=24; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=3; int res; long long k; if (scope==-1) { printf("Must specify scope\n"); printf("scsi_io --device= --command=clear --scope= --type= --key=\n"); _exit(10); } if (type==-1) { printf("Must specify type\n"); printf("scsi_io --device= --command=clear --scope= --type= --key=\n"); _exit(10); } if (!key) { printf("Must specify key\n"); printf("scsi_io --device= --command=clear --scope= --type= --key=\n"); _exit(10); } sscanf(key, "%llx", &k); cdb[1]=service_action; cdb[2]=(scope<<4)|type; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; memset(data, 0, data_size); /* Reservation Key */ data[0]=(k>>56)&0xff; data[1]=(k>>48)&0xff; data[2]=(k>>40)&0xff; data[3]=(k>>32)&0xff; data[4]=(k>>24)&0xff; data[5]=(k>>16)&0xff; data[6]=(k>> 8)&0xff; data[7]=(k )&0xff; /* Service Action Key */ data[8]=0; data[9]=0; data[10]=0; data[11]=0; data[12]=0; data[13]=0; data[14]=0; data[15]=0; /* Spec_ip_ti=0 all_tg_pt=1 aptpl=0 */ data[20]=0x04; printf("PERSISTENT RESERVE IN: CLEAR\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_TO_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } return 0; } int scsi_persistent_reserve_out_reserve(int fd) { unsigned char cdb[]={0x5f,0,0,0,0,0,0,0,0,0}; unsigned int data_size=24; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=1; int res; long long k; if (scope==-1) { printf("Must specify scope\n"); printf("scsi_io --device= --command=reserve --scope= --type= --key=\n"); _exit(10); } if (type==-1) { printf("Must specify type\n"); printf("scsi_io --device= --command=reserve --scope= --type= --key=\n"); _exit(10); } if (!key) { printf("Must specify key\n"); printf("scsi_io --device= --command=reserve --scope= --type= --key=\n"); _exit(10); } sscanf(key, "%llx", &k); cdb[1]=service_action; cdb[2]=(scope<<4)|type; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; memset(data, 0, data_size); /* Reservation Key */ data[0]=(k>>56)&0xff; data[1]=(k>>48)&0xff; data[2]=(k>>40)&0xff; data[3]=(k>>32)&0xff; data[4]=(k>>24)&0xff; data[5]=(k>>16)&0xff; data[6]=(k>> 8)&0xff; data[7]=(k )&0xff; /* Service Action Key */ data[8]=0; data[9]=0; data[10]=0; data[11]=0; data[12]=0; data[13]=0; data[14]=0; data[15]=0; /* Spec_ip_ti=0 all_tg_pt=1 aptpl=0 */ data[20]=0x04; printf("PERSISTENT RESERVE IN: RESERVE\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_TO_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } return 0; } int scsi_persistent_reserve_out_preempt(int fd) { unsigned char cdb[]={0x5f,0,0,0,0,0,0,0,0,0}; unsigned int data_size=24; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=4; int res; long long k; if (scope==-1) { printf("Must specify scope\n"); printf("scsi_io --device= --command=preempt --scope= --type= --key= --rmkey=\n"); _exit(10); } if (type==-1) { printf("Must specify type\n"); printf("scsi_io --device= --command=preempt --scope= --type= --key= --rmkey=\n"); _exit(10); } if (!key) { printf("Must specify key\n"); printf("scsi_io --device= --command=preempt --scope= --type= --key= --rmkey=\n"); _exit(10); } if (!rmkey) { printf("Must specify rmkey\n"); printf("scsi_io --device= --command=preempt --scope= --type= --key= --rmkey=\n"); _exit(10); } cdb[1]=service_action; cdb[2]=(scope<<4)|type; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; memset(data, 0, data_size); /* Reservation Key */ sscanf(key, "%llx", &k); data[0]=(k>>56)&0xff; data[1]=(k>>48)&0xff; data[2]=(k>>40)&0xff; data[3]=(k>>32)&0xff; data[4]=(k>>24)&0xff; data[5]=(k>>16)&0xff; data[6]=(k>> 8)&0xff; data[7]=(k )&0xff; /* Service Action Key */ sscanf(rmkey, "%llx", &k); data[8] =(k>>56)&0xff; data[9] =(k>>48)&0xff; data[10]=(k>>40)&0xff; data[11]=(k>>32)&0xff; data[12]=(k>>24)&0xff; data[13]=(k>>16)&0xff; data[14]=(k>> 8)&0xff; data[15]=(k )&0xff; /* Spec_ip_ti=0 all_tg_pt=1 aptpl=0 */ data[20]=0x04; printf("PERSISTENT RESERVE IN: RESERVE\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_TO_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } return 0; } int scsi_persistent_reserve_out_register_and_ignore_existing_key(int fd) { unsigned char cdb[]={0x5f,0,0,0,0,0,0,0,0,0}; unsigned int data_size=24; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=6; int res; long long k; if (scope==-1) { printf("Must specify scope\n"); printf("scsi_io --device= --command=registerkey --scope= --type= --key=\n"); _exit(10); } if (type==-1) { printf("Must specify type\n"); printf("scsi_io --device= --command=registerkey --scope= --type= --key=\n"); _exit(10); } if (!key) { printf("Must specify key\n"); printf("scsi_io --device= --command=registerkey --scope= --type= --key=\n"); _exit(10); } sscanf(key, "%llx", &k); cdb[1]=service_action; cdb[2]=(scope<<4)|type; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; memset(data, 0, data_size); /* Reservation Key */ data[0]=0; data[1]=0; data[2]=0; data[3]=0; data[4]=0; data[5]=0; data[6]=0; data[7]=0; /* Service Action Key */ data[8] =(k>>56)&0xff; data[9] =(k>>48)&0xff; data[10]=(k>>40)&0xff; data[11]=(k>>32)&0xff; data[12]=(k>>24)&0xff; data[13]=(k>>16)&0xff; data[14]=(k>> 8)&0xff; data[15]=(k )&0xff; /* Spec_ip_ti=0 all_tg_pt=1 aptpl=0 */ data[20]=0x04; printf("PERSISTENT RESERVE IN: REGISTER AND IGNORE EXISTING KEY\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_TO_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } return 0; } int scsi_persistent_reserve_out_unregister_key(int fd) { unsigned char cdb[]={0x5f,0,0,0,0,0,0,0,0,0}; unsigned int data_size=24; unsigned char data[data_size]; unsigned int sense_len=32; unsigned char sense[sense_len]; unsigned char service_action=6; int res; long long k; if (scope==-1) { printf("Must specify scope\n"); printf("scsi_io --device= --command=unregisterkey --scope= --type= --key=\n"); _exit(10); } if (type==-1) { printf("Must specify type\n"); printf("scsi_io --device= --command=unregisterkey --scope= --type= --key=\n"); _exit(10); } if (!key) { printf("Must specify key\n"); printf("scsi_io --device= --command=unregisterkey --scope= --type= --key=\n"); _exit(10); } sscanf(key, "%llx", &k); cdb[1]=service_action; cdb[2]=(scope<<4)|type; cdb[7]=(data_size>>8)&0xff; cdb[8]=data_size&0xff; memset(data, 0, data_size); /* Reservation Key */ data[0]=(k>>56)&0xff; data[1]=(k>>48)&0xff; data[2]=(k>>40)&0xff; data[3]=(k>>32)&0xff; data[4]=(k>>24)&0xff; data[5]=(k>>16)&0xff; data[6]=(k>> 8)&0xff; data[7]=(k )&0xff; /* Service Action Key */ data[8]=0; data[9]=0; data[10]=0; data[11]=0; data[12]=0; data[13]=0; data[14]=0; data[15]=0; /* Spec_ip_ti=0 all_tg_pt=1 aptpl=0 */ data[20]=0x04; printf("PERSISTENT RESERVE IN: UNREGISTER KEY\n"); res=scsi_io(fd, cdb, sizeof(cdb), SG_DXFER_TO_DEV, data, &data_size, sense, &sense_len); if(res){ printf("SCSI_IO failed\n"); return -1; } if(sense_len){ print_sense_data(sense, sense_len); return -1; } return 0; } int open_scsi_device(const char *dev) { int fd, vers; if((fd=open(dev, O_RDWR))<0){ printf("ERROR could not open device %s\n", dev); return -1; } if ((ioctl(fd, SG_GET_VERSION_NUM, &vers) < 0) || (vers < 30000)) { printf("/dev is not an sg device, or old sg driver\n"); close(fd); return -1; } return fd; } typedef int (*scsi_func_t)(int fd); typedef struct _cmds_t { const char *cmd; scsi_func_t func; const char *comment; } cmds_t; cmds_t cmds[] = { {"inq", scsi_inquiry, "Standard INQUIRY output"}, {"vpd", scsi_inquiry_supported_vpd_pages, "Supported VPD Pages"}, {"usn", scsi_inquiry_unit_serial_number, "Unit serial number"}, {"readkeys", scsi_persistent_reserve_in_read_keys, "Read SCSI Reservation Keys"}, {"readrsvr", scsi_persistent_reserve_in_read_reservation, "Read SCSI Reservation Data"}, {"reportcap", scsi_persistent_reserve_in_report_capabilities, "Report reservation Capabilities"}, {"registerkey", scsi_persistent_reserve_out_register_and_ignore_existing_key, "Register and ignore existing key"}, {"unregisterkey", scsi_persistent_reserve_out_unregister_key, "Unregister a key"}, {"clear", scsi_persistent_reserve_out_clear, "Clear all reservations and registrations"}, {"reserve", scsi_persistent_reserve_out_reserve, "Reserve"}, {"preempt", scsi_persistent_reserve_out_preempt, "Preempt (remove someone elses registration)"}, }; void usage(void) { int i; printf("Usage: scsi_io --command --device \n"); printf("Commands:\n"); for (i=0;i