From eb4ff87544853a0c0ac7545ef0747189fc6ec476 Mon Sep 17 00:00:00 2001 From: gibbs Date: Tue, 15 Sep 1998 06:43:02 +0000 Subject: Camcontrol - A utility for configuring/manipulating the CAM subsystem Submitted by: "Kenneth D. Merry" --- sbin/camcontrol/Makefile | 9 + sbin/camcontrol/camcontrol.8 | 437 +++++++++ sbin/camcontrol/camcontrol.c | 2048 ++++++++++++++++++++++++++++++++++++++++++ sbin/camcontrol/camcontrol.h | 54 ++ sbin/camcontrol/modeedit.c | 500 +++++++++++ sbin/camcontrol/util.c | 500 +++++++++++ 6 files changed, 3548 insertions(+) create mode 100644 sbin/camcontrol/Makefile create mode 100644 sbin/camcontrol/camcontrol.8 create mode 100644 sbin/camcontrol/camcontrol.c create mode 100644 sbin/camcontrol/camcontrol.h create mode 100644 sbin/camcontrol/modeedit.c create mode 100644 sbin/camcontrol/util.c (limited to 'sbin/camcontrol') diff --git a/sbin/camcontrol/Makefile b/sbin/camcontrol/Makefile new file mode 100644 index 0000000..e11a011 --- /dev/null +++ b/sbin/camcontrol/Makefile @@ -0,0 +1,9 @@ +PROG= camcontrol +SRCS= camcontrol.c modeedit.c +MAN8= camcontrol.8 + +CFLAGS+= -I${.CURDIR}/../../sys +DPADD= ${LIBCAM} +LDADD+= -lcam + +.include diff --git a/sbin/camcontrol/camcontrol.8 b/sbin/camcontrol/camcontrol.8 new file mode 100644 index 0000000..43eee6c --- /dev/null +++ b/sbin/camcontrol/camcontrol.8 @@ -0,0 +1,437 @@ +.\" +.\" Copyright (c) 1998 Kenneth D. Merry. +.\" 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 the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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$ +.\" +.Dd September 14, 1998 +.Dt CAMCONTROL 8 +.Os FreeBSD 3.0 +.Sh NAME +.Nm camcontrol +.Nd CAM control program +.Sh SYNOPSIS +.Nm camcontrol +.Aq command +.Op generic args +.Op command args +.Nm camcontrol +devlist +.Op Fl v +.Nm camcontrol +periphlist +.Op Fl n Ar dev_name +.Op Fl u Ar unit_number +.Nm camcontrol +tur +.Op generic args +.Nm camcontrol +inquiry +.Op generic args +.Op Fl D +.Op Fl S +.Op Fl R +.Nm camcontrol +start +.Op generic args +.Nm camcontrol +stop +.Op generic args +.Nm camcontrol +eject +.Op generic args +.Nm camcontrol +rescan +.Aq bus Ns Op :target:lun +.Nm camcontrol +defects +.Op generic args +.Aq Fl f Ar format +.Op Fl P +.Op Fl G +.Nm camcontrol +modepage +.Op generic args +.Aq Fl m Ar page +.Op Fl P Ar pagectl +.Op Fl e +.Op Fl d +.Nm camcontrol +cmd +.Op generic args +.Aq Fl c Ar cmd Op args +.Op Fl i Ar len Ar fmt +.Bk -words +.Op Fl o Ar len Ar fmt Op args +.Ek +.Nm camcontrol +debug +.Op Fl I +.Op Fl T +.Op Fl S +.Aq all|off|bus Ns Op :target Ns Op :lun +.Sh DESCRIPTION +.Nm camcontrol +is a utility designed to provide a way for users to access and control the +.Tn FreeBSD +CAM subsystem. +.Pp +.Nm camcontrol +can cause a loss of data and/or system crashes if used improperly. Even +expert users are encouraged to excercise caution when using this command. +Novice users should stay away from this utility. +.Pp +.Nm camcontrol +has a number of primary functions, most of which take some generic +arguments: +.Bl -tag -width 01234567890123 +.It Fl C Ar count +SCSI command retry count. In order for this to work, error recovery +.Po +.Fl E +.Pc +must be turned on. +.It Fl E +Instruct the kernel to perform generic SCSI error recovery for the given +command. This is needed in order for the retry count +.Po +.Fl C +.Pc +to be honored. Other than retrying commands, the generic error recovery in +the code will generally attempt to spin up drives that are not spinning. +It may take some other actions, depending upon the sense code returned from +the command. +.It Fl n Ar dev_name +Specify the device type to operate on. The default is +.Em da . +.It Fl t Ar timeout +SCSI command timeout in seconds. This overrides the default timeout for +any given command. +.It Fl u Ar unit_number +Specify the device unit number. The default is 0. +.It Fl v +Be verbose, print out sense information for failed SCSI commands. +.El +.Pp +Primay command functions: +.Bl -tag -width periphlist +.It devlist +List all physical devices (logical units) attached to the CAM subsystem. +This also includes a list of peripheral drivers attached to each device. +With the +.Fl v +argument, SCSI bus number, adapter name and unit numbers are printed as +well. +.It periphlist +List all peripheral drivers attached to a given physical device (logical +unit). +.It tur +Send the SCSI test unit ready (0x00) command to the given device. +.Nm camcontrol +will report whether the device is ready or not. +.It inquiry +Send a SCSI inquiry command (0x12) to a device. By default, +.Nm camcontrol +will print out the standard inquiry data, device serial number, and +transfer rate information. The user can specify that only certain types of +inquiry data be printed: +.Bl -tag -width 1234 +.It Fl D +Get the standard inquiry data. +.It Fl S +Print out the serial number. If this flag is the only one specified, +.Nm camcontrol +will not print out "Serial Number" before the value returned by the drive. +This is to aid in script writing. +.It Fl R +Print out transfer rate information. +.El +.It start +Send the SCSI Start/Stop Unit (0x1B) command to the given device with the +start bit set. +.It stop +Send the SCSI Start/Stop Unit (0x1B) command to the given device with the +start bit cleared. +.It eject +Send the SCSI Start/Stop Unit (0x1B) command to the given device with the +start bit cleared and the eject bit set. +.It rescan +Tell the kernel to scan the given bus (XPT_SCAN_BUS), or bus:target:lun +(XPT_SCAN_LUN) for new devices or devices that have gone away. The user +may only specify a bus to scan, or a lun. Scanning all luns on a target +isn't supported. +.It defects +Send the SCSI READ DEFECT DATA (10) command (0x37) to the given device, and +print out any combination of: the total number of defects, the primary +defect list (PLIST), and the grown defect list (GLIST). +.Bl -tag -width 01234567890 +.It Fl f Ar format +The three format options are: +.Em block , +to print out the list as logical blocks, +.Em bfi , +to print out the list in bytes from index format, and +.Em phys , +to print out the list in physical sector format. The format argument is +required. Most drives support the physical sector format. Some drives +support the logical block format. Many drives, if they don't support the +requested format, return the data in an alternate format, along with sense +information indicating that the requested data format isn't supported. +.Nm camcontrol +attempts to detect this, and print out whatever format the drive returns. +If the drive uses a non-standard sense code to report that it doesn't +support the requested format, +.Nm camcontrol +will probably see the error as a failure to complete the request. +.It Fl G +Print out the grown defect list. This is a list of bad blocks that have +been remapped since the disk left the factory. +.It Fl P +Print out the primary defect list. +.El +.Pp +If neither +.Fl P +nor +.Fl G +is specified, +.Nm camcontrol +will print out the number of defects given in the READ DEFECT DATA header +returned from the drive. +.It modepage +Allows the user to display and optionally edit a SCSI mode page. The mode +page formats are located in +.Pa /usr/share/misc/scsi_modes . +This can be overridden by specifying a different file in the +.Ev SCSI_MODES +environment variable. The modepage command takes several arguments: +.Bl -tag -width 012345678901 +.It Fl B +Disable block descriptors for mode sense. +.It Fl e +This flag allows the user to edit values in the mode page. +.It Fl m Ar mode_page +This specifies the number of the mode page the user would like to view +and/or edit. This argument is mandatory. +.It Fl P page_ctl +This allows the user to specify the page control field. Possible values are: +.Bl -tag -width xxx -compact +.It 1 +Current values +.It 2 +Changeable values +.It 3 +Default values +.It 4 +Saved values +.El +.El +.It cmd +Allows the user to send an arbitrary SCSI CDB to any device. The cmd +function requires the +.Fl c +argument to specify the CDB. Other arguments are optional, depending on +the command type. The command and data specification syntax is documented +in +.Xr cam 3 . +NOTE: If the CDB specified causes data to be transfered to or from the +SCSI device in question, you MUST specify either +.Fl i +or +.Fl o . +.Bl -tag -width 01234567890123456 +.It Fl c Ar cmd Op args +This specifies the SCSI CDB. CDBs may be 6, 10, 12 or 16 bytes. +.It Fl i Ar len Ar fmt +This specifies the amount of data to read, and how it should be displayed. +If the format is +.Sq - , +.Ar len +bytes of data will be read from the device and written to standard output. +.It Fl o Ar len Ar fmt Op args +This specifies the amount of data to be written to a device, and the data +that is to be written. If the format is +.Sq - , +.Ar len +bytes of data will be read from standard input and written to the device. +.El +.It debug +Turn on CAM debugging printfs in the kernel. This requires options CAMDEBUG +in your kernel config file. WARNING: enabling debugging printfs currently +causes an EXTREME number of kernel printfs. You may have difficulty +turning off the debugging printfs once they start, since the kernel will be +busy printing messages and unable to service other requests quickly. +The debug function takes a number of arguments: +.Bl -tag -width 012345678901234567 +.It Fl I +Enable CAM_DEBUG_INFO printfs. +.It Fl T +Enable CAM_DEBUG_TRACE printfs. +.It Fl S +Enable CAM_DEBUG_SUBTRACE printfs. +.It all +Enable debugging for all devices. +.It off +Turn off debugging for all devices +.It bus Ns Op :target Ns Op :lun +Turn on debugging for the given bus, target or lun. If the lun or target +and lun are not specified, they are wildcarded. (i.e., just specifying a +bus turns on debugging printfs for all devices on that bus.) +.El +.El +.Sh ENVIRONMENT +The +.Ev SCSI_MODES +variable allows the user to specify an alternate mode page format file. +.Pp +The +.Ev EDITOR +variable determines which text editor +.Nm camcontrol +starts when editing mode pages. +.Sh FILES +.Bl -tag -width /usr/share/misc/scsi_modes -compact +.It Pa /usr/share/misc/scsi_modes +is the SCSI mode format database. +.It Pa /dev/xpt0 +is the transport layer device. +.It Pa /dev/pass* +are the CAM application passthrough devices. +.El +.Sh EXAMPLES +.Dl camcontrol eject -n cd -u 1 -v +.Pp +Eject the CD from cd1, and print SCSI sense information if the command +fails. +.Pp +.Dl camcontrol tur +.Pp +Send the SCSI test unit ready command to da0. +.Nm camcontrol +will report whether the disk is ready, but will not display sense +information if the command fails since the +.Fl v +switch was not specified. +.Pp +.Bd -literal -offset foobar +camcontrol tur -n da -u 1 -E -C 4 -t 50 -v +.Ed +.Pp +Send a test unit ready command to da1. Enable kernel error recovery. +Specify a retry count of 4, and a timeout of 50 seconds. Enable sense +printing (with the +.Fl v +flag) if the command fails. Since error recovery is turned on, the +disk will be spun up if it is not currently spinning. +.Nm camcontrol +will report whether or the disk is ready. +.Bd -literal -offset foobar +camcontrol cmd -n cd -u 1 -v -c "3C 00 00 00 00 00 00 00 0e 00" \e + -i 0xe "s1 i3 i1 i1 i1 i1 i1 i1 i1 i1 i1 i1" +.Ed +.Pp +Issue a READ BUFFER command (0x3C) to cd1. Display the buffer size of cd1, +and display the first 10 bytes from the cache on cd1. Display SCSI sense +information if the command fails. +.Pp +.Bd -literal -offset foobar +camcontrol cmd -n cd -u u -v -c "3B 00 00 00 00 00 00 00 0e 00" \e + -o 14 "00 00 00 00 1 2 3 4 5 6 v v v v" 7 8 9 8 +.Ed +.Pp +Issue a WRITE BUFFER (0x3B) command to cd1. Write out 10 bytes of data, +not including the (reserved) 4 byte header. Print out sense information if +the command fails. Be very careful with this command, improper use may +cause data corruption. +.Pp +.Dl camcontrol modepage -n da -u 3 -m 1 -e -P 3 +.Pp +Edit mode page 1 (the Read-Write Error Recover page) for da3, and save the +settings on the drive. Mode page 1 contains a disk drive's auto read and +write reallocation settings, among other things. +.Pp +.Dl camcontrol rescan 0 +.Pp +Rescan SCSI bus 0 for devices that have been added, removed or changed. +.Pp +.Dl camcontrol rescan 0:1:0 +.Pp +Rescan SCSI bus 0, target 1, lun 0 to see if it has been added, removed, or +changed. +.Sh SEE ALSO +.Xr cam 3 , +.Xr pass 4 , +.Xr cam 9 , +.Xr xpt 9 +.Sh HISTORY +The +.Nm camcontrol +command first appeared in +.Fx 3.0 . +.Pp +The mode page editing code and arbitrary SCSI command code are based upon +code in the old +.Xr scsi 8 +utility and +.Xr scsi 3 +library, written by Julian Ellischer and Peter Dufault. The +.Xr scsi 8 +first appeared in 386BSD 0.1.2.4, and first appeared in +.Tn FreeBSD +in +.Fx 2.0.5 . +.Sh AUTHORS +.An Kenneth Merry Aq ken@FreeBSD.ORG +.Sh BUGS +Most of the man page cross references don't exist yet. This will be fixed +soon. +.Pp +The code that parses the generic command line arguments doesn't know that +some of the subcommands take multiple arguments. So if, for instance, you +tried something like this: +.Bd -literal -offset foobar +camcontrol -n da -u 1 -c "00 00 00 00 00 v" 0x00 -v +.Ed +.Pp +The sense information from the test unit ready command would not get +printed out, since the first +.Xr getopt 3 +call in +.Nm camcontrol +bails out when it sees the second argument to +.Fl c +.Po +0x00 +.Pc , +above. Fixing this behavior would take some gross code, or changes to the +.Xr getopt 3 +interface. The best way to circumvent this problem is to always make sure +to specify generic +.Nm camcontrol +arguments before any command-specific arguments. +.Pp +It might be nice to add a way to allow users to specify devices by +bus/target/lun or by device string (e.g. "da1"). diff --git a/sbin/camcontrol/camcontrol.c b/sbin/camcontrol/camcontrol.c new file mode 100644 index 0000000..6f17f8d --- /dev/null +++ b/sbin/camcontrol/camcontrol.c @@ -0,0 +1,2048 @@ +/* + * Copyright (c) 1997, 1998 Kenneth D. Merry + * 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 the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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$ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include "camcontrol.h" + +#define DEFAULT_DEVICE "da" +#define DEFAULT_UNIT 0 + +typedef enum { + CAM_ARG_NONE = 0x00000000, + CAM_ARG_DEVLIST = 0x00000001, + CAM_ARG_TUR = 0x00000002, + CAM_ARG_INQUIRY = 0x00000003, + CAM_ARG_STARTSTOP = 0x00000004, + CAM_ARG_RESCAN = 0x00000005, + CAM_ARG_READ_DEFECTS = 0x00000006, + CAM_ARG_MODE_PAGE = 0x00000007, + CAM_ARG_SCSI_CMD = 0x00000008, + CAM_ARG_DEVTREE = 0x00000009, + CAM_ARG_USAGE = 0x0000000a, + CAM_ARG_DEBUG = 0x0000000b, + CAM_ARG_OPT_MASK = 0x0000000f, + CAM_ARG_VERBOSE = 0x00000010, + CAM_ARG_DEVICE = 0x00000020, + CAM_ARG_BUS = 0x00000040, + CAM_ARG_TARGET = 0x00000080, + CAM_ARG_LUN = 0x00000100, + CAM_ARG_EJECT = 0x00000200, + CAM_ARG_UNIT = 0x00000400, + CAM_ARG_FORMAT_BLOCK = 0x00000800, + CAM_ARG_FORMAT_BFI = 0x00001000, + CAM_ARG_FORMAT_PHYS = 0x00002000, + CAM_ARG_PLIST = 0x00004000, + CAM_ARG_GLIST = 0x00008000, + CAM_ARG_GET_SERIAL = 0x00010000, + CAM_ARG_GET_STDINQ = 0x00020000, + CAM_ARG_GET_XFERRATE = 0x00040000, + CAM_ARG_INQ_MASK = 0x00070000, + CAM_ARG_MODE_EDIT = 0x00080000, + CAM_ARG_PAGE_CNTL = 0x00100000, + CAM_ARG_TIMEOUT = 0x00200000, + CAM_ARG_CMD_IN = 0x00400000, + CAM_ARG_CMD_OUT = 0x00800000, + CAM_ARG_DBD = 0x01000000, + CAM_ARG_ERR_RECOVER = 0x02000000, + CAM_ARG_RETRIES = 0x04000000, + CAM_ARG_START_UNIT = 0x08000000, + CAM_ARG_DEBUG_INFO = 0x10000000, + CAM_ARG_DEBUG_TRACE = 0x20000000, + CAM_ARG_DEBUG_SUBTRACE = 0x40000000, + CAM_ARG_FLAG_MASK = 0xfffffff0 +} cam_argmask; + +struct camcontrol_opts { + char *optname; + cam_argmask argnum; + const char *subopt; +}; + +extern int optreset; + +static const char scsicmd_opts[] = "c:i:o:"; +static const char readdefect_opts[] = "f:GP"; + +struct camcontrol_opts option_table[] = { + {"tur", CAM_ARG_TUR, NULL}, + {"inquiry", CAM_ARG_INQUIRY, "DSR"}, + {"start", CAM_ARG_STARTSTOP | CAM_ARG_START_UNIT, NULL}, + {"stop", CAM_ARG_STARTSTOP, NULL}, + {"eject", CAM_ARG_STARTSTOP | CAM_ARG_EJECT, NULL}, + {"rescan", CAM_ARG_RESCAN, NULL}, + {"cmd", CAM_ARG_SCSI_CMD, scsicmd_opts}, + {"command", CAM_ARG_SCSI_CMD, scsicmd_opts}, + {"defects", CAM_ARG_READ_DEFECTS, readdefect_opts}, + {"defectlist", CAM_ARG_READ_DEFECTS, readdefect_opts}, + {"devlist", CAM_ARG_DEVTREE, NULL}, + {"periphlist", CAM_ARG_DEVLIST, NULL}, + {"modepage", CAM_ARG_MODE_PAGE, "dem:P:"}, + {"debug", CAM_ARG_DEBUG, "ITS"}, + {"help", CAM_ARG_USAGE, NULL}, + {"-?", CAM_ARG_USAGE, NULL}, + {"-h", CAM_ARG_USAGE, NULL}, + {NULL, 0, NULL} +}; + +typedef enum { + CC_OR_NOT_FOUND, + CC_OR_AMBIGUOUS, + CC_OR_FOUND +} camcontrol_optret; + +cam_argmask arglist; + + +camcontrol_optret getoption(char *arg, cam_argmask *argnum, char **subopt); +static int getdevlist(struct cam_device *device); +static int getdevtree(void); +static int testunitready(struct cam_device *device, int retry_count, + int timeout); +static int scsistart(struct cam_device *device, int startstop, int loadeject, + int retry_count, int timeout); +static int scsidoinquiry(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout); +static int scsiinquiry(struct cam_device *device, int retry_count, int timeout); +static int scsiserial(struct cam_device *device, int retry_count, int timeout); +static int scsixferrate(struct cam_device *device); +static int dorescan(int argc, char **argv); +static int rescanbus(int bus); +static int scanlun(int bus, int target, int lun); +static int readdefects(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout); +static void modepage(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout); +static int scsicmd(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout); + +camcontrol_optret +getoption(char *arg, cam_argmask *argnum, char **subopt) +{ + struct camcontrol_opts *opts; + int num_matches = 0; + + for (opts = option_table; (opts != NULL) && (opts->optname != NULL); + opts++) { + if (strncmp(opts->optname, arg, strlen(arg)) == 0) { + *argnum = opts->argnum; + *subopt = (char *)opts->subopt; + if (++num_matches > 1) + return(CC_OR_AMBIGUOUS); + } + } + + if (num_matches > 0) + return(CC_OR_FOUND); + else + return(CC_OR_NOT_FOUND); +} + +static int +getdevlist(struct cam_device *device) +{ + union ccb *ccb; + char status[32]; + int error = 0; + + ccb = cam_getccb(device); + + ccb->ccb_h.func_code = XPT_GDEVLIST; + ccb->ccb_h.flags = CAM_DIR_NONE; + ccb->ccb_h.retry_count = 1; + ccb->cgdl.index = 0; + ccb->cgdl.status = CAM_GDEVLIST_MORE_DEVS; + while (ccb->cgdl.status == CAM_GDEVLIST_MORE_DEVS) { + if (cam_send_ccb(device, ccb) < 0) { + perror("error getting device list"); + cam_freeccb(ccb); + return(1); + } + + status[0] = '\0'; + + switch (ccb->cgdl.status) { + case CAM_GDEVLIST_MORE_DEVS: + strcpy(status, "MORE"); + break; + case CAM_GDEVLIST_LAST_DEVICE: + strcpy(status, "LAST"); + break; + case CAM_GDEVLIST_LIST_CHANGED: + strcpy(status, "CHANGED"); + break; + case CAM_GDEVLIST_ERROR: + strcpy(status, "ERROR"); + error = 1; + break; + } + + fprintf(stdout, "%s%d: generation: %d index: %d status: %s\n", + ccb->cgdl.periph_name, + ccb->cgdl.unit_number, + ccb->cgdl.generation, + ccb->cgdl.index, + status); + + /* + * If the list has changed, we need to start over from the + * beginning. + */ + if (ccb->cgdl.status == CAM_GDEVLIST_LIST_CHANGED) + ccb->cgdl.index = 0; + } + + cam_freeccb(ccb); + + return(error); +} + +static int +getdevtree(void) +{ + union ccb ccb; + int bufsize, i, fd; + int need_close = 0; + int error = 0; + + if ((fd = open(XPT_DEVICE, O_RDWR)) == -1) { + warn("couldn't open %s", XPT_DEVICE); + return(1); + } + + bzero(&(&ccb.ccb_h)[1], sizeof(struct ccb_dev_match)); + + ccb.ccb_h.func_code = XPT_DEV_MATCH; + bufsize = sizeof(struct dev_match_result) * 100; + ccb.cdm.match_buf_len = bufsize; + ccb.cdm.matches = (struct dev_match_result *)malloc(bufsize); + ccb.cdm.num_matches = 0; + + /* + * We fetch all nodes, since we display most of them in the default + * case, and all in the verbose case. + */ + ccb.cdm.num_patterns = 0; + ccb.cdm.pattern_buf_len = 0; + + /* + * We do the ioctl multiple times if necessary, in case there are + * more than 100 nodes in the EDT. + */ + do { + if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { + warn("error sending CAMIOCOMMAND ioctl"); + error = 1; + break; + } + + if ((ccb.ccb_h.status != CAM_REQ_CMP) + || ((ccb.cdm.status != CAM_DEV_MATCH_LAST) + && (ccb.cdm.status != CAM_DEV_MATCH_MORE))) { + fprintf(stderr, "got CAM error %#x, CDM error %d\n", + ccb.ccb_h.status, ccb.cdm.status); + error = 1; + break; + } + + for (i = 0; i < ccb.cdm.num_matches; i++) { + switch(ccb.cdm.matches[i].type) { + case DEV_MATCH_BUS: { + struct bus_match_result *bus_result; + + /* + * Only print the bus information if the + * user turns on the verbose flag. + */ + if ((arglist & CAM_ARG_VERBOSE) == 0) + break; + + bus_result = + &ccb.cdm.matches[i].result.bus_result; + + if (need_close) { + fprintf(stdout, ")\n"); + need_close = 0; + } + + fprintf(stdout, "scbus%d on %s%d bus %d:\n", + bus_result->path_id, + bus_result->dev_name, + bus_result->unit_number, + bus_result->bus_id); + break; + } + case DEV_MATCH_DEVICE: { + struct device_match_result *dev_result; + char vendor[16], product[48], revision[16]; + char tmpstr[256]; + + dev_result = + &ccb.cdm.matches[i].result.device_result; + + cam_strvis(vendor, dev_result->inq_data.vendor, + sizeof(dev_result->inq_data.vendor), + sizeof(vendor)); + cam_strvis(product, + dev_result->inq_data.product, + sizeof(dev_result->inq_data.product), + sizeof(product)); + cam_strvis(revision, + dev_result->inq_data.revision, + sizeof(dev_result->inq_data.revision), + sizeof(revision)); + sprintf(tmpstr, "<%s %s %s>", vendor, product, + revision); + if (need_close) { + fprintf(stdout, ")\n"); + need_close = 0; + } + + fprintf(stdout, "%-33s at scbus%d " + "target %d lun %d (", + tmpstr, + dev_result->path_id, + dev_result->target_id, + dev_result->target_lun); + break; + } + case DEV_MATCH_PERIPH: { + struct periph_match_result *periph_result; + + periph_result = + &ccb.cdm.matches[i].result.periph_result; + + if (need_close) + fprintf(stdout, ","); + + fprintf(stdout, "%s%d", + periph_result->periph_name, + periph_result->unit_number); + + need_close = 1; + break; + } + default: + fprintf(stdout, "unknown match type\n"); + break; + } + } + + } while ((ccb.ccb_h.status == CAM_REQ_CMP) + && (ccb.cdm.status == CAM_DEV_MATCH_MORE)); + + if (need_close) + fprintf(stdout, ")\n"); + + close(fd); + + return(error); +} + +static int +testunitready(struct cam_device *device, int retry_count, int timeout) +{ + int error = 0; + union ccb *ccb; + + ccb = cam_getccb(device); + + scsi_test_unit_ready(&ccb->csio, + /* retries */ retry_count, + /* cbfcnp */ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (arglist & CAM_ARG_ERR_RECOVER) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + perror("error sending test unit ready"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + + cam_freeccb(ccb); + return(1); + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) + fprintf(stdout, "Unit is ready\n"); + else { + fprintf(stdout, "Unit is not ready\n"); + error = 1; + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + } + + cam_freeccb(ccb); + + return(error); +} + +static int +scsistart(struct cam_device *device, int startstop, int loadeject, + int retry_count, int timeout) +{ + union ccb *ccb; + int error = 0; + + if ((startstop < 0) || (startstop > 1)) { + warnx("SCSI start/stop argument must be 0 or 1"); + return(1); + } + + ccb = cam_getccb(device); + + scsi_start_stop(&ccb->csio, + /* retries */ retry_count, + /* cbfcnp */ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* start/stop */ startstop, + /* load_eject */ loadeject, + /* immediate */ 0, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 120000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (arglist & CAM_ARG_ERR_RECOVER) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + perror("error sending start unit"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + + cam_freeccb(ccb); + return(1); + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) + if (startstop) { + fprintf(stdout, "Unit started successfully"); + if (loadeject) + fprintf(stdout,", Media loaded\n"); + else + fprintf(stdout,"\n"); + } else { + fprintf(stdout, "Unit stopped successfully"); + if (loadeject) + fprintf(stdout, ", Media ejected\n"); + else + fprintf(stdout, "\n"); + } + else { + error = 1; + if (startstop) + fprintf(stdout, + "Error received from start unit command\n"); + else + fprintf(stdout, + "Error received from stop unit command\n"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + } + + cam_freeccb(ccb); + + return(error); +} + +static int +scsidoinquiry(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout) +{ + int c; + int error = 0; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch(c) { + case 'D': + arglist |= CAM_ARG_GET_STDINQ; + break; + case 'R': + arglist |= CAM_ARG_GET_XFERRATE; + break; + case 'S': + arglist |= CAM_ARG_GET_SERIAL; + break; + default: + break; + } + } + + /* + * If the user didn't specify any inquiry options, he wants all of + * them. + */ + if ((arglist & CAM_ARG_INQ_MASK) == 0) + arglist |= CAM_ARG_INQ_MASK; + + if (arglist & CAM_ARG_GET_STDINQ) + error = scsiinquiry(device, retry_count, timeout); + + if (error != 0) + return(error); + + if (arglist & CAM_ARG_GET_SERIAL) + scsiserial(device, retry_count, timeout); + + if (error != 0) + return(error); + + if (arglist & CAM_ARG_GET_XFERRATE) + error = scsixferrate(device); + + return(error); +} + +static int +scsiinquiry(struct cam_device *device, int retry_count, int timeout) +{ + union ccb *ccb; + struct scsi_inquiry_data *inq_buf; + int error = 0; + + ccb = cam_getccb(device); + + if (ccb == NULL) { + warnx("couldn't allocate CCB"); + return(1); + } + + /* cam_getccb cleans up the header, caller has to zero the payload */ + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio)); + + inq_buf = (struct scsi_inquiry_data *)malloc( + sizeof(struct scsi_inquiry_data)); + + if (inq_buf == NULL) { + cam_freeccb(ccb); + warnx("can't malloc memory for inquiry\n"); + return(1); + } + bzero(inq_buf, sizeof(*inq_buf)); + + scsi_inquiry(&ccb->csio, + /* retries */ retry_count, + /* cbfcnp */ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* inq_buf */ (u_int8_t *)inq_buf, + /* inq_len */ sizeof(struct scsi_inquiry_data), + /* evpd */ 0, + /* page_code */ 0, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (arglist & CAM_ARG_ERR_RECOVER) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + perror("error sending SCSI inquiry"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + + cam_freeccb(ccb); + return(1); + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + error = 1; + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + } + + cam_freeccb(ccb); + + if (error != 0) { + free(inq_buf); + return(error); + } + + scsi_print_inquiry(inq_buf); + + free(inq_buf); + + if (arglist & CAM_ARG_GET_SERIAL) + fprintf(stdout, "Serial Number "); + + return(0); +} + +static int +scsiserial(struct cam_device *device, int retry_count, int timeout) +{ + union ccb *ccb; + struct scsi_vpd_unit_serial_number *serial_buf; + char serial_num[SVPD_SERIAL_NUM_SIZE + 1]; + int error = 0; + + ccb = cam_getccb(device); + + if (ccb == NULL) { + warnx("couldn't allocate CCB"); + return(1); + } + + /* cam_getccb cleans up the header, caller has to zero the payload */ + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio)); + + serial_buf = (struct scsi_vpd_unit_serial_number *) + malloc(sizeof(*serial_buf)); + + if (serial_buf == NULL) { + cam_freeccb(ccb); + warnx("can't malloc memory for serial number"); + return(1); + } + + scsi_inquiry(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* inq_buf */ (u_int8_t *)serial_buf, + /* inq_len */ sizeof(*serial_buf), + /* evpd */ 1, + /* page_code */ SVPD_UNIT_SERIAL_NUMBER, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (arglist & CAM_ARG_ERR_RECOVER) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + if (cam_send_ccb(device, ccb) < 0) { + warn("error getting serial number"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + + cam_freeccb(ccb); + free(serial_buf); + return(1); + } + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + error = 1; + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + } + + cam_freeccb(ccb); + + if (error != 0) { + free(serial_buf); + return(error); + } + + bcopy(serial_buf->serial_num, serial_num, serial_buf->length); + serial_num[serial_buf->length] = '\0'; + + if (((arglist & CAM_ARG_GET_STDINQ) == 0) + && (arglist & CAM_ARG_GET_XFERRATE)) + fprintf(stdout, "Serial Number "); + + fprintf(stdout, "%.60s\n", serial_num); + + free(serial_buf); + + return(0); +} + +static int +scsixferrate(struct cam_device *device) +{ + u_int32_t freq; + u_int32_t speed; + + if (device->sync_period != 0) + freq = scsi_calc_syncsrate(device->sync_period); + else + freq = 0; + + speed = freq; + speed *= (0x01 << device->bus_width); + fprintf(stdout, "%d.%dMB/s transfers ", speed / 1000, speed % 1000); + + if (device->sync_period != 0) + fprintf(stdout, "(%d.%dMHz, offset %d", freq / 1000, + freq % 1000, device->sync_offset); + + if (device->bus_width != 0) { + if (device->sync_period == 0) + fprintf(stdout, "("); + else + fprintf(stdout, ", "); + fprintf(stdout, "%dbit)", 8 * (0x01 << device->bus_width)); + } else if (device->sync_period != 0) + fprintf(stdout, ")"); + + if (device->inq_data.flags & SID_CmdQue) + fprintf(stdout, ", Tagged Queueing Enabled"); + + fprintf(stdout, "\n"); + + return(0); +} + +static int +dorescan(int argc, char **argv) +{ + int error = 0; + int bus = -1, target = -1, lun = -1; + char *tstr, *tmpstr = NULL; + + if (argc < 3) { + warnx("you must specify a bus, or a bus:target:lun to rescan"); + return(1); + } + /* + * Parse out a bus, or a bus, target and lun in the following + * format: + * bus + * bus:target:lun + * It is an error to specify a bus and target, but not a lun. + */ + tstr = argv[optind]; + + while (isspace(*tstr) && (*tstr != '\0')) + tstr++; + + tmpstr = (char *)strtok(tstr, ":"); + if ((tmpstr != NULL) && (*tmpstr != '\0')){ + bus = strtol(tmpstr, NULL, 0); + arglist |= CAM_ARG_BUS; + tmpstr = (char *)strtok(NULL, ":"); + if ((tmpstr != NULL) && (*tmpstr != '\0')){ + target = strtol(tmpstr, NULL, 0); + arglist |= CAM_ARG_TARGET; + tmpstr = (char *)strtok(NULL, ":"); + if ((tmpstr != NULL) && (*tmpstr != '\0')){ + lun = strtol(tmpstr, NULL, 0); + arglist |= CAM_ARG_LUN; + } else { + error = 1; + warnx("you must specify either a bus or"); + warnx("a bus, target and lun for rescanning"); + } + } + } else { + error = 1; + warnx("you must at least specify a bus to rescan"); + } + + + if (error == 0) { + if ((arglist & CAM_ARG_BUS) + && (arglist & CAM_ARG_TARGET) + && (arglist & CAM_ARG_LUN)) + error = scanlun(bus, target, lun); + else if (arglist & CAM_ARG_BUS) + error = rescanbus(bus); + else { + error = 1; + warnx("you must specify either a bus or"); + warnx("a bus, target and lun for rescanning"); + } + } + return(error); +} + +static int +rescanbus(int bus) +{ + union ccb ccb; + int fd; + + if (bus < 0) { + warnx("invalid bus number %d", bus); + return(1); + } + + if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) { + warnx("error opening tranport layer device %s", XPT_DEVICE); + warn("%s", XPT_DEVICE); + return(1); + } + + ccb.ccb_h.func_code = XPT_SCAN_BUS; + ccb.ccb_h.path_id = bus; + ccb.ccb_h.target_id = CAM_TARGET_WILDCARD; + ccb.ccb_h.target_lun = CAM_LUN_WILDCARD; + ccb.crcn.flags = CAM_FLAG_NONE; + + /* run this at a low priority */ + ccb.ccb_h.pinfo.priority = 5; + + if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { + warn("CAMIOCOMMAND ioctl failed"); + close(fd); + return(1); + } + + close(fd); + + if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + fprintf(stdout, "Re-scan of bus %d was successful\n", bus); + return(0); + } else { + fprintf(stdout, "Re-scan of bus %d returned error %#x\n", + bus, ccb.ccb_h.status & CAM_STATUS_MASK); + return(1); + } +} + +static int +scanlun(int bus, int target, int lun) +{ + union ccb ccb; + int fd; + + if (bus < 0) { + warnx("invalid bus number %d", bus); + return(1); + } + + if (target < 0) { + warnx("invalid target number %d", target); + return(1); + } + + if (lun < 0) { + warnx("invalid lun number %d", lun); + return(1); + } + + if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) { + warnx("error opening tranport layer device %s\n", + XPT_DEVICE); + warn("%s", XPT_DEVICE); + return(1); + } + + ccb.ccb_h.func_code = XPT_SCAN_LUN; + ccb.ccb_h.path_id = bus; + ccb.ccb_h.target_id = target; + ccb.ccb_h.target_lun = lun; + ccb.crcn.flags = CAM_FLAG_NONE; + + /* run this at a low priority */ + ccb.ccb_h.pinfo.priority = 5; + + if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) { + warn("CAMIOCOMMAND ioctl failed"); + close(fd); + return(1); + } + + close(fd); + + if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) { + fprintf(stdout, "Re-scan of %d:%d:%d was successful\n", + bus, target, lun); + return(0); + } else { + fprintf(stdout, "Re-scan of %d:%d:%d returned error %#x\n", + bus, target, lun, ccb.ccb_h.status & CAM_STATUS_MASK); + return(1); + } +} + +static int +readdefects(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout) +{ + union ccb *ccb = NULL; + struct scsi_read_defect_data_10 *rdd_cdb; + u_int8_t *defect_list = NULL; + u_int32_t dlist_length = 65000; + u_int32_t returned_length = 0; + u_int32_t num_returned = 0; + u_int8_t returned_format; + register int i; + int c, error = 0; + int lists_specified = 0; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch(c){ + case 'f': + { + char *tstr; + tstr = optarg; + while (isspace(*tstr) && (*tstr != '\0')) + tstr++; + if (strcmp(tstr, "block") == 0) + arglist |= CAM_ARG_FORMAT_BLOCK; + else if (strcmp(tstr, "bfi") == 0) + arglist |= CAM_ARG_FORMAT_BFI; + else if (strcmp(tstr, "phys") == 0) + arglist |= CAM_ARG_FORMAT_PHYS; + else { + error = 1; + warnx("invalid defect format %s", tstr); + goto defect_bailout; + } + break; + } + case 'G': + arglist |= CAM_ARG_GLIST; + break; + case 'P': + arglist |= CAM_ARG_PLIST; + break; + default: + break; + } + } + + ccb = cam_getccb(device); + + /* + * Hopefully 65000 bytes is enough to hold the defect list. If it + * isn't, the disk is probably dead already. We'd have to go with + * 12 byte command (i.e. alloc_length is 32 bits instead of 16) + * to hold them all. + */ + defect_list = malloc(dlist_length); + + rdd_cdb =(struct scsi_read_defect_data_10 *)&ccb->csio.cdb_io.cdb_bytes; + + /* + * cam_getccb() zeros the CCB header only. So we need to zero the + * payload portion of the ccb. + */ + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio)); + + cam_fill_csio(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*flags*/ CAM_DIR_IN | (arglist & CAM_ARG_ERR_RECOVER) ? + CAM_PASS_ERR_RECOVER : 0, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*data_ptr*/ defect_list, + /*dxfer_len*/ dlist_length, + /*sense_len*/ SSD_FULL_SIZE, + /*cdb_len*/ sizeof(struct scsi_read_defect_data_10), + /*timeout*/ timeout ? timeout : 5000); + + rdd_cdb->opcode = READ_DEFECT_DATA_10; + if (arglist & CAM_ARG_FORMAT_BLOCK) + rdd_cdb->format = SRDD10_BLOCK_FORMAT; + else if (arglist & CAM_ARG_FORMAT_BFI) + rdd_cdb->format = SRDD10_BYTES_FROM_INDEX_FORMAT; + else if (arglist & CAM_ARG_FORMAT_PHYS) + rdd_cdb->format = SRDD10_PHYSICAL_SECTOR_FORMAT; + else { + error = 1; + warnx("no defect list format specified"); + goto defect_bailout; + } + if (arglist & CAM_ARG_PLIST) { + rdd_cdb->format |= SRDD10_PLIST; + lists_specified++; + } + + if (arglist & CAM_ARG_GLIST) { + rdd_cdb->format |= SRDD10_GLIST; + lists_specified++; + } + + scsi_ulto2b(dlist_length, rdd_cdb->alloc_length); + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (cam_send_ccb(device, ccb) < 0) { + perror("error reading defect list"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + + error = 1; + goto defect_bailout; + } + + if (arglist & CAM_ARG_VERBOSE) + scsi_sense_print(device, &ccb->csio, stderr); + + returned_length = scsi_2btoul(((struct + scsi_read_defect_data_hdr_10 *)defect_list)->length); + + returned_format = ((struct scsi_read_defect_data_hdr_10 *) + defect_list)->format; + + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + struct scsi_sense_data *sense; + int error_code, sense_key, asc, ascq; + + sense = &ccb->csio.sense_data; + scsi_extract_sense(sense, &error_code, &sense_key, &asc, &ascq); + + /* + * According to the SCSI spec, if the disk doesn't support + * the requested format, it will generally return a sense + * key of RECOVERED ERROR, and an additional sense code + * of "DEFECT LIST NOT FOUND". So, we check for that, and + * also check to make sure that the returned length is + * greater than 0, and then print out whatever format the + * disk gave us. + */ + if ((sense_key == SSD_KEY_RECOVERED_ERROR) + && (asc == 0x1c) && (ascq == 0x00) + && (returned_length > 0)) { + warnx("requested defect format not available"); + switch(returned_format & SRDDH10_DLIST_FORMAT_MASK) { + case SRDD10_BLOCK_FORMAT: + warnx("Device returned block format"); + break; + case SRDD10_BYTES_FROM_INDEX_FORMAT: + warnx("Device returned bytes from index" + " format"); + break; + case SRDD10_PHYSICAL_SECTOR_FORMAT: + warnx("Device returned physical sector format"); + break; + default: + error = 1; + warnx("Device returned unknown defect" + " data format %#x", returned_format); + goto defect_bailout; + break; /* NOTREACHED */ + } + } else { + error = 1; + warnx("Error returned from read defect data command"); + goto defect_bailout; + } + } + + /* + * XXX KDM I should probably clean up the printout format for the + * disk defects. + */ + switch (returned_format & SRDDH10_DLIST_FORMAT_MASK){ + case SRDDH10_PHYSICAL_SECTOR_FORMAT: + { + struct scsi_defect_desc_phys_sector *dlist; + + dlist = (struct scsi_defect_desc_phys_sector *) + (defect_list + + sizeof(struct scsi_read_defect_data_hdr_10)); + + num_returned = returned_length / + sizeof(struct scsi_defect_desc_phys_sector); + + fprintf(stderr, "Got %d defect", num_returned); + + if ((lists_specified == 0) || (num_returned == 0)) { + fprintf(stderr, "s.\n"); + break; + } else if (num_returned == 1) + fprintf(stderr, ":\n"); + else + fprintf(stderr, "s:\n"); + + for (i = 0; i < num_returned; i++) { + fprintf(stdout, "%d:%d:%d\n", + scsi_3btoul(dlist[i].cylinder), + dlist[i].head, + scsi_4btoul(dlist[i].sector)); + } + break; + } + case SRDDH10_BYTES_FROM_INDEX_FORMAT: + { + struct scsi_defect_desc_bytes_from_index *dlist; + + dlist = (struct scsi_defect_desc_bytes_from_index *) + (defect_list + + sizeof(struct scsi_read_defect_data_hdr_10)); + + num_returned = returned_length / + sizeof(struct scsi_defect_desc_bytes_from_index); + + fprintf(stderr, "Got %d defect", num_returned); + + if ((lists_specified == 0) || (num_returned == 0)) { + fprintf(stderr, "s.\n"); + break; + } else if (num_returned == 1) + fprintf(stderr, ":\n"); + else + fprintf(stderr, "s:\n"); + + for (i = 0; i < num_returned; i++) { + fprintf(stdout, "%d:%d:%d\n", + scsi_3btoul(dlist[i].cylinder), + dlist[i].head, + scsi_4btoul(dlist[i].bytes_from_index)); + } + break; + } + case SRDDH10_BLOCK_FORMAT: + { + struct scsi_defect_desc_block *dlist; + + dlist = (struct scsi_defect_desc_block *)(defect_list + + sizeof(struct scsi_read_defect_data_hdr_10)); + + num_returned = returned_length / + sizeof(struct scsi_defect_desc_block); + + fprintf(stderr, "Got %d defect", num_returned); + + if ((lists_specified == 0) || (num_returned == 0)) { + fprintf(stderr, "s.\n"); + break; + } else if (num_returned == 1) + fprintf(stderr, ":\n"); + else + fprintf(stderr, "s:\n"); + + for (i = 0; i < num_returned; i++) + fprintf(stdout, "%u\n", + scsi_4btoul(dlist[i].address)); + break; + } + default: + fprintf(stderr, "Unknown defect format %d\n", + returned_format & SRDDH10_DLIST_FORMAT_MASK); + error = 1; + break; + } +defect_bailout: + + if (defect_list != NULL) + free(defect_list); + + if (ccb != NULL) + cam_freeccb(ccb); + + return(error); +} + +#if 0 +void +reassignblocks(struct cam_device *device, u_int32_t *blocks, int num_blocks) +{ + union ccb *ccb; + + ccb = cam_getccb(device); + + cam_freeccb(ccb); +} +#endif + +void +mode_sense(struct cam_device *device, int mode_page, int page_control, + int dbd, int retry_count, int timeout, u_int8_t *data, int datalen) +{ + union ccb *ccb; + int retval; + + ccb = cam_getccb(device); + + if (ccb == NULL) + errx(1, "mode_sense: couldn't allocate CCB"); + + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio)); + + scsi_mode_sense(&ccb->csio, + /* retries */ retry_count, + /* cbfcnp */ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* dbd */ dbd, + /* page_code */ page_control << 6, + /* page */ mode_page, + /* param_buf */ data, + /* param_len */ datalen, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + if (arglist & CAM_ARG_ERR_RECOVER) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (((retval = cam_send_ccb(device, ccb)) < 0) + || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + cam_freeccb(ccb); + cam_close_device(device); + if (retval < 0) + err(1, "error sending mode sense command"); + else + errx(1, "error sending mode sense command"); + } + + cam_freeccb(ccb); +} + +void +mode_select(struct cam_device *device, int save_pages, int retry_count, + int timeout, u_int8_t *data, int datalen) +{ + union ccb *ccb; + int retval; + + ccb = cam_getccb(device); + + if (ccb == NULL) + errx(1, "mode_select: couldn't allocate CCB"); + + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio)); + + scsi_mode_select(&ccb->csio, + /* retries */ retry_count, + /* cbfcnp */ NULL, + /* tag_action */ MSG_SIMPLE_Q_TAG, + /* scsi_page_fmt */ 1, + /* save_pages */ save_pages, + /* param_buf */ data, + /* param_len */ datalen, + /* sense_len */ SSD_FULL_SIZE, + /* timeout */ timeout ? timeout : 5000); + + if (arglist & CAM_ARG_ERR_RECOVER) + ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; + + /* Disable freezing the device queue */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + + if (((retval = cam_send_ccb(device, ccb)) < 0) + || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + cam_freeccb(ccb); + cam_close_device(device); + + if (retval < 0) + err(1, "error sending mode select command"); + else + errx(1, "error sending mode select command"); + + } + + cam_freeccb(ccb); +} + +void +modepage(struct cam_device *device, int argc, char **argv, char *combinedopt, + int retry_count, int timeout) +{ + int c, mode_page = -1, page_control = 0; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch(c) { + case 'd': + arglist |= CAM_ARG_DBD; + break; + case 'e': + arglist |= CAM_ARG_MODE_EDIT; + break; + case 'm': + mode_page = strtol(optarg, NULL, 0); + if (mode_page < 0) + errx(1, "invalid mode page %d", mode_page); + break; + case 'P': + page_control = strtol(optarg, NULL, 0); + if ((page_control < 0) || (page_control > 3)) + errx(1, "invalid page control field %d", + page_control); + arglist |= CAM_ARG_PAGE_CNTL; + break; + default: + break; + } + } + + if (mode_page == -1) + errx(1, "you must specify a mode page!"); + + mode_edit(device, mode_page, page_control, arglist & CAM_ARG_DBD, + arglist & CAM_ARG_MODE_EDIT, retry_count, timeout); +} + +static int +scsicmd(struct cam_device *device, int argc, char **argv, char *combinedopt, + int retry_count, int timeout) +{ + union ccb *ccb; + u_int32_t flags = CAM_DIR_NONE; + u_int8_t *data_ptr = NULL; + u_int8_t cdb[20]; + struct get_hook hook; + int c, data_bytes = 0; + int cdb_len = 0; + char *datastr = NULL, *tstr; + int error = 0; + int fd_data = 0; + int retval; + + ccb = cam_getccb(device); + + if (ccb == NULL) { + warnx("scsicmd: error allocating ccb"); + return(1); + } + + bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_scsiio)); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch(c) { + case 'c': + tstr = optarg; + while (isspace(*tstr) && (*tstr != '\0')) + tstr++; + hook.argc = argc - optind; + hook.argv = argv + optind; + hook.got = 0; + buff_encode_visit(cdb, sizeof(cdb), tstr, + iget, &hook); + /* + * Increment optind by the number of arguments the + * encoding routine processed. After each call to + * getopt(3), optind points to the argument that + * getopt should process _next_. In this case, + * that means it points to the first command string + * argument, if there is one. Once we increment + * this, it should point to either the next command + * line argument, or it should be past the end of + * the list. + */ + optind += hook.got; + break; + case 'i': + if (arglist & CAM_ARG_CMD_OUT) { + warnx("command must either be " + "read or write, not both"); + error = 1; + goto scsicmd_bailout; + } + arglist |= CAM_ARG_CMD_IN; + flags = CAM_DIR_IN; + data_bytes = strtol(optarg, NULL, 0); + if (data_bytes <= 0) { + warnx("invalid number of input bytes %d", + data_bytes); + error = 1; + goto scsicmd_bailout; + } + hook.argc = argc - optind; + hook.argv = argv + optind; + hook.got = 0; + optind++; + datastr = cget(&hook, NULL); + /* + * If the user supplied "-" instead of a format, he + * wants the data to be written to stdout. + */ + if ((datastr != NULL) + && (datastr[0] == '-')) + fd_data = 1; + + data_ptr = (u_int8_t *)malloc(data_bytes); + break; + case 'o': + if (arglist & CAM_ARG_CMD_IN) { + warnx("command must either be " + "read or write, not both"); + error = 1; + goto scsicmd_bailout; + } + arglist |= CAM_ARG_CMD_OUT; + flags = CAM_DIR_OUT; + data_bytes = strtol(optarg, NULL, 0); + if (data_bytes <= 0) { + warnx("invalid number of output bytes %d", + data_bytes); + error = 1; + goto scsicmd_bailout; + } + hook.argc = argc - optind; + hook.argv = argv + optind; + hook.got = 0; + datastr = cget(&hook, NULL); + data_ptr = (u_int8_t *)malloc(data_bytes); + /* + * If the user supplied "-" instead of a format, he + * wants the data to be read from stdin. + */ + if ((datastr != NULL) + && (datastr[0] == '-')) + fd_data = 1; + else + buff_encode_visit(data_ptr, data_bytes, datastr, + iget, &hook); + optind += hook.got; + break; + default: + break; + } + } + + /* + * If fd_data is set, and we're writing to the device, we need to + * read the data the user wants written from stdin. + */ + if ((fd_data == 1) && (arglist & CAM_ARG_CMD_OUT)) { + size_t amt_read; + int amt_to_read = data_bytes; + u_int8_t *buf_ptr = data_ptr; + + for (amt_read = 0; amt_to_read > 0; + amt_read = read(0, buf_ptr, amt_to_read)) { + if (amt_read == -1) { + warn("error reading data from stdin"); + error = 1; + goto scsicmd_bailout; + } + amt_to_read -= amt_read; + buf_ptr += amt_read; + } + } + + if (arglist & CAM_ARG_ERR_RECOVER) + flags |= CAM_PASS_ERR_RECOVER; + + /* Disable freezing the device queue */ + flags |= CAM_DEV_QFRZDIS; + + /* + * This is taken from the SCSI-3 draft spec. + * (T10/1157D revision 0.3) + * The top 3 bits of an opcode are the group code. The next 5 bits + * are the command code. + * Group 0: six byte commands + * Group 1: ten byte commands + * Group 2: ten byte commands + * Group 3: reserved + * Group 4: sixteen byte commands + * Group 5: twelve byte commands + * Group 6: vendor specific + * Group 7: vendor specific + */ + switch((cdb[0] >> 5) & 0x7) { + case 0: + cdb_len = 6; + break; + case 1: + case 2: + cdb_len = 10; + break; + case 3: + case 6: + case 7: + cdb_len = 1; + break; + case 4: + cdb_len = 16; + break; + case 5: + cdb_len = 12; + break; + } + + /* + * We should probably use csio_build_visit or something like that + * here, but it's easier to encode arguments as you go. The + * alternative would be skipping the CDB argument and then encoding + * it here, since we've got the data buffer argument by now. + */ + bcopy(cdb, &ccb->csio.cdb_io.cdb_bytes, cdb_len); + + cam_fill_csio(&ccb->csio, + /*retries*/ retry_count, + /*cbfcnp*/ NULL, + /*flags*/ flags, + /*tag_action*/ MSG_SIMPLE_Q_TAG, + /*data_ptr*/ data_ptr, + /*dxfer_len*/ data_bytes, + /*sense_len*/ SSD_FULL_SIZE, + /*cdb_len*/ cdb_len, + /*timeout*/ timeout ? timeout : 5000); + + if (((retval = cam_send_ccb(device, ccb)) < 0) + || ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { + if (retval < 0) + warn("error sending command"); + else + warnx("error sending command"); + + if (arglist & CAM_ARG_VERBOSE) { + if ((ccb->ccb_h.status & CAM_STATUS_MASK) == + CAM_SCSI_STATUS_ERROR) + scsi_sense_print(device, &ccb->csio, stderr); + else + fprintf(stderr, "CAM status is %#x\n", + ccb->ccb_h.status); + } + + error = 1; + goto scsicmd_bailout; + } + + + if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) + && (arglist & CAM_ARG_CMD_IN) + && (data_bytes > 0)) { + if (fd_data == 0) { + buff_decode_visit(data_ptr, data_bytes, datastr, + arg_put, NULL); + fprintf(stdout, "\n"); + } else { + size_t amt_written; + int amt_to_write = data_bytes; + u_int8_t *buf_ptr = data_ptr; + + for (amt_written = 0; (amt_to_write > 0) && + (amt_written =write(1, buf_ptr,amt_to_write))> 0;){ + amt_to_write -= amt_written; + buf_ptr += amt_written; + } + if (amt_written == -1) { + warn("error writing data to stdout"); + error = 1; + goto scsicmd_bailout; + } else if ((amt_written == 0) + && (amt_to_write > 0)) { + warnx("only wrote %u bytes out of %u", + data_bytes - amt_to_write, data_bytes); + } + } + } + +scsicmd_bailout: + + if ((data_bytes > 0) && (data_ptr != NULL)) + free(data_ptr); + + cam_freeccb(ccb); + + return(error); +} + +static int +camdebug(int argc, char **argv, char *combinedopt) +{ + int c, fd; + int bus = -1, target = -1, lun = -1; + char *tstr, *tmpstr = NULL; + union ccb ccb; + int error = 0; + + bzero(&ccb, sizeof(union ccb)); + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch(c) { + case 'I': + arglist |= CAM_ARG_DEBUG_INFO; + ccb.cdbg.flags |= CAM_DEBUG_INFO; + break; + case 'S': + arglist |= CAM_ARG_DEBUG_TRACE; + ccb.cdbg.flags |= CAM_DEBUG_TRACE; + break; + case 'T': + arglist |= CAM_ARG_DEBUG_SUBTRACE; + ccb.cdbg.flags |= CAM_DEBUG_SUBTRACE; + break; + default: + break; + } + } + + if ((fd = open(XPT_DEVICE, O_RDWR)) < 0) { + warnx("error opening transport layer device %s", XPT_DEVICE); + warn("%s", XPT_DEVICE); + return(1); + } + argc -= optind; + argv += optind; + + if (argc <= 0) { + warnx("you must specify \"off\", \"all\" or a bus,"); + warnx("bus:target, or bus:target:lun"); + close(fd); + return(1); + } + + tstr = *argv; + + while (isspace(*tstr) && (*tstr != '\0')) + tstr++; + + if (strncmp(tstr, "off", 3) == 0) { + ccb.cdbg.flags = CAM_DEBUG_NONE; + arglist &= ~(CAM_ARG_DEBUG_INFO|CAM_ARG_DEBUG_TRACE| + CAM_ARG_DEBUG_SUBTRACE); + } else if (strncmp(tstr, "all", 3) != 0) { + tmpstr = (char *)strtok(tstr, ":"); + if ((tmpstr != NULL) && (*tmpstr != '\0')){ + bus = strtol(tmpstr, NULL, 0); + arglist |= CAM_ARG_BUS; + tmpstr = (char *)strtok(NULL, ":"); + if ((tmpstr != NULL) && (*tmpstr != '\0')){ + target = strtol(tmpstr, NULL, 0); + arglist |= CAM_ARG_TARGET; + tmpstr = (char *)strtok(NULL, ":"); + if ((tmpstr != NULL) && (*tmpstr != '\0')){ + lun = strtol(tmpstr, NULL, 0); + arglist |= CAM_ARG_LUN; + } + } + } else { + error = 1; + warnx("you must specify \"all\", \"off\", or a bus,"); + warnx("bus:target, or bus:target:lun to debug"); + } + } + + if (error == 0) { + + ccb.ccb_h.func_code = XPT_DEBUG; + ccb.ccb_h.path_id = bus; + ccb.ccb_h.target_id = target; + ccb.ccb_h.target_lun = lun; + + if (ioctl(fd, CAMIOCOMMAND, &ccb) == -1) { + warn("CAMIOCOMMAND ioctl failed"); + error = 1; + } + + if (error == 0) { + if ((ccb.ccb_h.status & CAM_STATUS_MASK) == + CAM_FUNC_NOTAVAIL) { + warnx("CAM debugging not available"); + warnx("you need to put options CAMDEBUG in" + " your kernel config file!"); + error = 1; + } else if ((ccb.ccb_h.status & CAM_STATUS_MASK) != + CAM_REQ_CMP) { + warnx("XPT_DEBUG CCB failed with status %#x", + ccb.ccb_h.status); + error = 1; + } else { + if (ccb.cdbg.flags == CAM_DEBUG_NONE) { + fprintf(stderr, + "Debugging turned off\n"); + } else { + fprintf(stderr, + "Debugging enabled for " + "%d:%d:%d\n", + bus, target, lun); + } + } + } + close(fd); + } + + return(error); +} + +void +usage(void) +{ + fprintf(stderr, +"usage: camcontrol [ generic args ] [ command args ]\n" +" camcontrol devlist [-v]\n" +" camcontrol periphlist [-n dev_name] [-u unit]\n" +" camcontrol tur [generic args]\n" +" camcontrol inquiry [generic args] [-D] [-S] [-R]\n" +" camcontrol start [generic args]\n" +" camcontrol stop [generic args]\n" +" camcontrol eject [generic args]\n" +" camcontrol rescan \n" +" camcontrol defects [generic args] <-f format> [-P][-G]\n" +" camcontrol modepage [generic args] <-m page> [-P pagectl][-e][-d]\n" +" camcontrol cmd [generic args] <-c cmd [args]> \n" +" [-i len fmt|-o len fmt [args]]\n" +" camcontrol debug [-I][-T][-S] \n" +"Specify one of the following options:\n" +"devlist list all CAM devices\n" +"periphlist list all CAM peripheral drivers attached to a device\n" +"tur send a test unit ready to the named device\n" +"inquiry send a SCSI inquiry command to the named device\n" +"start send a Start Unit command to the device\n" +"stop send a Stop Unit command to the device\n" +"eject send a Stop Unit command to the device with the eject bit set\n" +"rescan rescan the given bus, or bus:target:lun\n" +"defects read the defect list of the specified device\n" +"modepage display or edit (-e) the given mode page\n" +"cmd send the given scsi command, may need -i or -o as well\n" +"debug turn debugging on/off for a bus, target, or lun, or all devices\n" +"Generic arguments:\n" +"-v be verbose, print out sense information\n" +"-t timeout command timeout in seconds, overrides default timeout\n" +"-n dev_name specify device name (default is %s)\n" +"-u unit specify unit number (default is %d)\n" +"-E have the kernel attempt to perform SCSI error recovery\n" +"-C count specify the SCSI command retry count (needs -E to work)\n" +"modepage arguments:\n" +"-e edit the specified mode page\n" +"-B disable block descriptors for mode sense\n" +"-P pgctl page control field 0-3\n" +"defects arguments:\n" +"-f format specify defect list format (block, bfi or phys)\n" +"-G get the grown defect list\n" +"-P get the permanant defect list\n" +"inquiry arguments:\n" +"-D get the standard inquiry data\n" +"-S get the serial number\n" +"-R get the transfer rate, etc.\n" +"cmd arguments:\n" +"-c cdb [args] specify the SCSI CDB\n" +"-i len fmt specify input data and input data format\n" +"-o len fmt [args] specify output data and output data fmt\n" +"debug arguments:\n" +"-I CAM_DEBUG_INFO -- scsi commands, errors, data\n" +"-T CAM_DEBUG_TRACE -- routine flow tracking\n" +"-S CAM_DEBUG_SUBTRACE -- internal routine command flow\n", +DEFAULT_DEVICE, DEFAULT_UNIT); +} + +int +main(int argc, char **argv) +{ + int c; + char *device = NULL; + int unit = 0; + struct cam_device *cam_dev = NULL; + int timeout = 0, retry_count = 1; + camcontrol_optret optreturn; + char *tstr; + char *mainopt = "C:En:t:u:v"; + char *subopt = NULL; + char combinedopt[256]; + int error = 0; + + arglist = CAM_ARG_NONE; + + if (argc < 2) { + usage(); + exit(1); + } + + /* + * Get the base option. + */ + optreturn = getoption(argv[1], &arglist, &subopt); + + if (optreturn == CC_OR_AMBIGUOUS) { + warnx("ambiguous option %s", argv[1]); + usage(); + exit(1); + } else if (optreturn == CC_OR_NOT_FOUND) { + warnx("option %s not found", argv[1]); + usage(); + exit(1); + } + + /* + * Ahh, getopt(3) is a pain. + * + * This is a gross hack. There really aren't many other good + * options (excuse the pun) for parsing options in a situation like + * this. getopt is kinda braindead, so you end up having to run + * through the options twice, and give each invocation of getopt + * the option string for the other invocation. + * + * You would think that you could just have two groups of options. + * The first group would get parsed by the first invocation of + * getopt, and the second group would get parsed by the second + * invocation of getopt. It doesn't quite work out that way. When + * the first invocation of getopt finishes, it leaves optind pointing + * to the argument _after_ the first argument in the second group. + * So when the second invocation of getopt comes around, it doesn't + * recognize the first argument it gets and then bails out. + * + * A nice alternative would be to have a flag for getopt that says + * "just keep parsing arguments even when you encounter an unknown + * argument", but there isn't one. So there's no real clean way to + * easily parse two sets of arguments without having one invocation + * of getopt know about the other. + * + * Without this hack, the first invocation of getopt would work as + * long as the generic arguments are first, but the second invocation + * (in the subfunction) would fail in one of two ways. In the case + * where you don't set optreset, it would fail because optind may be + * pointing to the argument after the one it should be pointing at. + * In the case where you do set optreset, and reset optind, it would + * fail because getopt would run into the first set of options, which + * it doesn't understand. + * + * All of this would "sort of" work if you could somehow figure out + * whether optind had been incremented one option too far. The + * mechanics of that, however, are more daunting than just giving + * both invocations all of the expect options for either invocation. + * + * Needless to say, I wouldn't mind if someone invented a better + * (non-GPL!) command line parsing interface than getopt. I + * wouldn't mind if someone added more knobs to getopt to make it + * work better. Who knows, I may talk myself into doing it someday, + * if the standards weenies let me. As it is, it just leads to + * hackery like this and causes people to avoid it in some cases. + * + * KDM, September 8th, 1998 + */ + if (subopt != NULL) + sprintf(combinedopt, "%s%s", mainopt, subopt); + else + sprintf(combinedopt, "%s", mainopt); + + /* + * Start getopt processing at argv[2], since we've already accepted + * argv[1] as the command name. + */ + optind = 2; + + /* + * Now we run through the argument list looking for generic + * options, and ignoring options that possibly belong to + * subfunctions. + */ + while ((c = getopt(argc, argv, combinedopt))!= -1){ + switch(c) { + case 'C': + retry_count = strtol(optarg, NULL, 0); + if (retry_count < 0) + errx(1, "retry count %d is < 0", + retry_count); + arglist |= CAM_ARG_RETRIES; + break; + case 'E': + arglist |= CAM_ARG_ERR_RECOVER; + break; + case 'n': + arglist |= CAM_ARG_DEVICE; + tstr = optarg; + while (isspace(*tstr) && (*tstr != '\0')) + tstr++; + device = (char *)strdup(tstr); + break; + case 't': + timeout = strtol(optarg, NULL, 0); + if (timeout < 0) + errx(1, "invalid timeout %d", timeout); + /* Convert the timeout from seconds to ms */ + timeout *= 1000; + arglist |= CAM_ARG_TIMEOUT; + break; + case 'u': + arglist |= CAM_ARG_UNIT; + unit = strtol(optarg, NULL, 0); + break; + case 'v': + arglist |= CAM_ARG_VERBOSE; + break; + default: + break; + } + } + + if ((arglist & CAM_ARG_DEVICE) == 0) + device = (char *)strdup(DEFAULT_DEVICE); + + if ((arglist & CAM_ARG_UNIT) == 0) + unit = DEFAULT_UNIT; + + /* + * For most commands we'll want to open the passthrough device + * associated with the specified device. In the case of the rescan + * commands, we don't use a passthrough device at all, just the + * transport layer device. + */ + if (((arglist & CAM_ARG_OPT_MASK) != CAM_ARG_RESCAN) + && ((arglist & CAM_ARG_OPT_MASK) != CAM_ARG_DEVTREE) + && ((arglist & CAM_ARG_OPT_MASK) != CAM_ARG_USAGE) + && ((arglist & CAM_ARG_OPT_MASK) != CAM_ARG_DEBUG)) { + + if ((cam_dev = cam_open_spec_device(device,unit,O_RDWR, + NULL))== NULL) + errx(1,"%s", cam_errbuf); + } + + /* + * Reset optind to 2, and reset getopt, so these routines cam parse + * the arguments again. + */ + optind = 2; + optreset = 1; + + switch(arglist & CAM_ARG_OPT_MASK) { + case CAM_ARG_DEVLIST: + error = getdevlist(cam_dev); + break; + case CAM_ARG_DEVTREE: + error = getdevtree(); + break; + case CAM_ARG_TUR: + error = testunitready(cam_dev, retry_count, timeout); + break; + case CAM_ARG_INQUIRY: + error = scsidoinquiry(cam_dev, argc, argv, combinedopt, + retry_count, timeout); + break; + case CAM_ARG_STARTSTOP: + error = scsistart(cam_dev, arglist & CAM_ARG_START_UNIT, + arglist & CAM_ARG_EJECT, retry_count, + timeout); + break; + case CAM_ARG_RESCAN: + error = dorescan(argc, argv); + break; + case CAM_ARG_READ_DEFECTS: + error = readdefects(cam_dev, argc, argv, combinedopt, + retry_count, timeout); + break; + case CAM_ARG_MODE_PAGE: + modepage(cam_dev, argc, argv, combinedopt, + retry_count, timeout); + break; + case CAM_ARG_SCSI_CMD: + error = scsicmd(cam_dev, argc, argv, combinedopt, + retry_count, timeout); + break; + case CAM_ARG_DEBUG: + error = camdebug(argc, argv, combinedopt); + break; + default: + usage(); + error = 1; + break; + } + + if (cam_dev != NULL) + cam_close_device(cam_dev); + + exit(error); +} diff --git a/sbin/camcontrol/camcontrol.h b/sbin/camcontrol/camcontrol.h new file mode 100644 index 0000000..8fb64a7 --- /dev/null +++ b/sbin/camcontrol/camcontrol.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 1998 Kenneth D. Merry. + * 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 the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 THE AUTHOR OR CONTRIBUTORS 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$ + */ + +#ifndef _CAMCONTROL_H +#define _CAMCONTROL_H +/* + * get_hook: Structure for evaluating args in a callback. + */ +struct get_hook +{ + int argc; + char **argv; + int got; +}; + +void mode_sense(struct cam_device *device, int mode_page, int page_control, + int dbd, int retry_count, int timeout, u_int8_t *data, + int datalen); +void mode_select(struct cam_device *device, int save_pages, int retry_count, + int timeout, u_int8_t *data, int datalen); +void mode_edit(struct cam_device *device, int page, int page_control, int dbd, + int edit, int retry_count, int timeout); +char *cget(void *hook, char *name); +int iget(void *hook, char *name); +void arg_put(void *hook, int letter, void *arg, int count, char *name); +void usage(void); +#endif /* _CAMCONTROL_H */ diff --git a/sbin/camcontrol/modeedit.c b/sbin/camcontrol/modeedit.c new file mode 100644 index 0000000..131624f --- /dev/null +++ b/sbin/camcontrol/modeedit.c @@ -0,0 +1,500 @@ +/* + * 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. + */ +/* + * Taken from the original scsi(8) program. + * from: scsi.c,v 1.17 1998/01/12 07:57:57 charnier Exp $"; + */ +#ifndef lint +static const char rcsid[] = + "$Id$"; +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "camcontrol.h" + +int verbose = 0; + +/* 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(); + exit(1); + } + 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(); + exit(1); + } + 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; + + p = malloc(count + 1); + + bzero(p, count +1); + 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); + + free(p); + } + + break; + + default: + printf("Unknown format letter: '%c'\n", letter); + } + if (verbose) + putchar('\n'); +} + +#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[4096]; /* 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) + errx(1, "expected %c", START_ENTRY); + + match = 1; + while (match != 0) { + c = getc(modes); + if (c == EOF) { + warnx("expected %c", END_ENTRY); + } + + if (c == START_ENTRY) { + match++; + } + if (c == END_ENTRY) { + match--; + if (match == 0) + break; + } + if (found && c != '\n') { + if (next >= sizeof(fmt)) + errx(1, "buffer overflow"); + + 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)) + warn("%s", edit_name); + if (unlink(edit_name)) + warn("%s", edit_name); + } +} + +static void +edit_init(void) +{ + edit_rewind(); + if (tmpnam(edit_name) == 0) + errx(1, "tmpnam failed"); + if ((edit_file = fopen(edit_name, "w")) == 0) + err(1, "%s", edit_name); + 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') + errx(1, "can't edit format %c", letter); + + if (editind >= sizeof(editinfo) / sizeof(editinfo[0])) + errx(1, "edit table overflow"); + + 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') + errx(1, "can't edit format %c", letter); + + 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') + errx(1, "can't report format %c", letter); + + fprintf(edit_file, "%s: %d\n", name, (int)arg); + } + + editind++; +} + +static int +edit_get(void *hook, char *name) +{ + int arg = editinfo[editind].default_value; + + if (editinfo[editind].can_edit) { + char line[80]; + if (fgets(line, sizeof(line), edit_file) == 0) + err(1, "fgets"); + + line[strlen(line) - 1] = 0; + + if (strncmp(name, line, strlen(name)) != 0) + errx(1, "expected \"%s\" and read \"%s\"", name, line); + + 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) + err(1, "%s", edit_name); +} + +void +mode_edit(struct cam_device *device, int page, int page_control, int dbd, + int edit, int retry_count, int timeout) +{ + 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) + errx(1, "can't edit without a format"); + + if (page_control != 0 && page_control != 3) + errx(1, "it only makes sense to edit page 0 " + "(current) or page 3 (saved values)"); + + verbose = 1; + + mode_sense(device, page, 1, dbd, retry_count, timeout, + data, sizeof(data)); + + mh = (struct mode_header *)data; + mph = (struct mode_page_header *) + (((char *)mh) + sizeof(*mh) + mh->bdl); + + mode_pars = (char *)mph + sizeof(*mph); + + edit_init(); + buff_decode_visit(mode_pars, mh->mdl, fmt, edit_check, 0); + + mode_sense(device, page, 0, dbd, retry_count, timeout, + data, sizeof(data)); + + edit_rewind(); + buff_decode_visit(mode_pars, mh->mdl, fmt, edit_defaults, 0); + + edit_rewind(); + buff_decode_visit(mode_pars, mh->mdl, fmt, edit_report, 0); + + edit_edit(); + + edit_rewind(); + 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(); + 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(device, (page_control == 3), retry_count, + timeout, (u_int8_t *)mh, sizeof(*mh) + mh->bdl + + sizeof(*mph) + mph->page_length); + + return; + } + + mode_sense(device, page, page_control, dbd, retry_count, timeout, + data, sizeof(data)); + + /* 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; + buff_decode_visit(mode_pars, mh->mdl, fmt, arg_put, NULL); + } +} diff --git a/sbin/camcontrol/util.c b/sbin/camcontrol/util.c new file mode 100644 index 0000000..131624f --- /dev/null +++ b/sbin/camcontrol/util.c @@ -0,0 +1,500 @@ +/* + * 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. + */ +/* + * Taken from the original scsi(8) program. + * from: scsi.c,v 1.17 1998/01/12 07:57:57 charnier Exp $"; + */ +#ifndef lint +static const char rcsid[] = + "$Id$"; +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "camcontrol.h" + +int verbose = 0; + +/* 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(); + exit(1); + } + 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(); + exit(1); + } + 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; + + p = malloc(count + 1); + + bzero(p, count +1); + 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); + + free(p); + } + + break; + + default: + printf("Unknown format letter: '%c'\n", letter); + } + if (verbose) + putchar('\n'); +} + +#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[4096]; /* 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) + errx(1, "expected %c", START_ENTRY); + + match = 1; + while (match != 0) { + c = getc(modes); + if (c == EOF) { + warnx("expected %c", END_ENTRY); + } + + if (c == START_ENTRY) { + match++; + } + if (c == END_ENTRY) { + match--; + if (match == 0) + break; + } + if (found && c != '\n') { + if (next >= sizeof(fmt)) + errx(1, "buffer overflow"); + + 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)) + warn("%s", edit_name); + if (unlink(edit_name)) + warn("%s", edit_name); + } +} + +static void +edit_init(void) +{ + edit_rewind(); + if (tmpnam(edit_name) == 0) + errx(1, "tmpnam failed"); + if ((edit_file = fopen(edit_name, "w")) == 0) + err(1, "%s", edit_name); + 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') + errx(1, "can't edit format %c", letter); + + if (editind >= sizeof(editinfo) / sizeof(editinfo[0])) + errx(1, "edit table overflow"); + + 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') + errx(1, "can't edit format %c", letter); + + 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') + errx(1, "can't report format %c", letter); + + fprintf(edit_file, "%s: %d\n", name, (int)arg); + } + + editind++; +} + +static int +edit_get(void *hook, char *name) +{ + int arg = editinfo[editind].default_value; + + if (editinfo[editind].can_edit) { + char line[80]; + if (fgets(line, sizeof(line), edit_file) == 0) + err(1, "fgets"); + + line[strlen(line) - 1] = 0; + + if (strncmp(name, line, strlen(name)) != 0) + errx(1, "expected \"%s\" and read \"%s\"", name, line); + + 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) + err(1, "%s", edit_name); +} + +void +mode_edit(struct cam_device *device, int page, int page_control, int dbd, + int edit, int retry_count, int timeout) +{ + 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) + errx(1, "can't edit without a format"); + + if (page_control != 0 && page_control != 3) + errx(1, "it only makes sense to edit page 0 " + "(current) or page 3 (saved values)"); + + verbose = 1; + + mode_sense(device, page, 1, dbd, retry_count, timeout, + data, sizeof(data)); + + mh = (struct mode_header *)data; + mph = (struct mode_page_header *) + (((char *)mh) + sizeof(*mh) + mh->bdl); + + mode_pars = (char *)mph + sizeof(*mph); + + edit_init(); + buff_decode_visit(mode_pars, mh->mdl, fmt, edit_check, 0); + + mode_sense(device, page, 0, dbd, retry_count, timeout, + data, sizeof(data)); + + edit_rewind(); + buff_decode_visit(mode_pars, mh->mdl, fmt, edit_defaults, 0); + + edit_rewind(); + buff_decode_visit(mode_pars, mh->mdl, fmt, edit_report, 0); + + edit_edit(); + + edit_rewind(); + 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(); + 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(device, (page_control == 3), retry_count, + timeout, (u_int8_t *)mh, sizeof(*mh) + mh->bdl + + sizeof(*mph) + mph->page_length); + + return; + } + + mode_sense(device, page, page_control, dbd, retry_count, timeout, + data, sizeof(data)); + + /* 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; + buff_decode_visit(mode_pars, mh->mdl, fmt, arg_put, NULL); + } +} -- cgit v1.1