diff options
Diffstat (limited to 'examples')
28 files changed, 4622 insertions, 0 deletions
diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 00000000..d8156744 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,127 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?) +# the default C compiler is clang. Swap the comment marks (lines starting +# with '#') on the next 4 (non-blank) lines. +CC = gcc +# CC = clang + +LD = gcc +# LD = clang + + +EXECS = sg_simple1 sg_simple2 sg_simple3 sg_simple4 sg_simple16 \ + scsi_inquiry sg_excl sg_simple5 sg__sat_identify \ + sg__sat_phy_event sg__sat_set_features sg_sat_chk_power \ + sg_sat_smart_rd_data + +EXTRAS = sgq_dd + +BSG_EXTRAS = + + +MAN_PGS = +MAN_PREF = man8 + +LARGE_FILE_FLAGS = -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 + +CPPFLAGS = -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) +# CPPFLAGS = -iquote ../include -D_REENTRANT $(LARGE_FILE_FLAGS) -DDEBUG + +CFLAGS = -g -O2 -W -Wall +# CFLAGS = -g -O2 -Wall -DSG_KERNEL_INCLUDES +# CFLAGS = -g -O2 -Wall -pedantic + +LDFLAGS = + +LIBFILESOLD = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_io_linux.o +LIBFILESNEW = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_pt_common.o ../lib/sg_pt_linux.o ../lib/sg_pt_linux_nvme.o + +all: $(EXECS) + +extras: $(EXTRAS) + +bsg: $(BSG_EXTRAS) + + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) $(BSG_EXTRAS) core .depend + +sg_simple1: sg_simple1.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_simple2: sg_simple2.o + $(LD) -o $@ $(LDFLAGS) $^ + +sg_simple3: sg_simple3.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_simple4: sg_simple4.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_simple16: sg_simple16.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +scsi_inquiry: scsi_inquiry.o + $(LD) -o $@ $(LDFLAGS) $^ + +sg_excl: sg_excl.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_simple5: sg_simple5.o $(LIBFILESNEW) + $(LD) -o $@ $(LDFLAGS) $^ + +sg__sat_identify: sg__sat_identify.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg__sat_phy_event: sg__sat_phy_event.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg__sat_set_features: sg__sat_set_features.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_sat_chk_power: sg_sat_chk_power.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sg_sat_smart_rd_data: sg_sat_smart_rd_data.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +sgq_dd: sgq_dd.o $(LIBFILESOLD) + $(LD) -o $@ $(LDFLAGS) $^ + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +# Linux uses GNU make and FreeBSD uses Berkely make. The following lines +# only work in Linux. Possible solutions in FreeBSD: +# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq' +# c) build with 'make -f Makefile.freebsd' +# In Linux one can install bmake (but that won't help here). +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/examples/Makefile.freebsd b/examples/Makefile.freebsd new file mode 100644 index 00000000..d983736d --- /dev/null +++ b/examples/Makefile.freebsd @@ -0,0 +1,82 @@ +SHELL = /bin/sh + +PREFIX=/usr/local +INSTDIR=$(DESTDIR)/$(PREFIX)/bin +MANDIR=$(DESTDIR)/$(PREFIX)/man + +# In Linux the default C compiler is GCC while in FreeBSD (since release 10 ?) +# the default C compiler is clang. Swap the comment marks (lines starting +# with '#') on the next 4 (non-blank) lines. +# CC = gcc +# CC = clang + +# LD = gcc +# LD = clang + + +EXECS = sg_simple5 + +# EXTRAS = sgq_dd + +MAN_PGS = +MAN_PREF = man8 + +OS_FLAGS = -DSG_LIB_FREEBSD +EXTRA_FLAGS = $(OS_FLAGS) + +# CFLAGS = -O2 -Wall -W $(EXTRA_FLAGS) -I ../include +CFLAGS = -g -O2 -Wall -W $(EXTRA_FLAGS) -I ../include +# CFLAGS = -g -O2 -Wall -W -pedantic -std=c99 $(EXTRA_FLAGS) -I ../include + +CFLAGS_PTHREADS = -D_REENTRANT + +# there is no rule to make the following in the parent directory, +# it is assumed they are already built. +D_FILES = ../lib/sg_lib.o ../lib/sg_lib_data.o ../lib/sg_cmds_basic.o ../lib/sg_pt_common.o ../lib/sg_pt_freebsd.o + +LDFLAGS = -lcam + +all: $(EXECS) + +extras: $(EXTRAS) + + +depend dep: + for i in *.c; do $(CC) $(INCLUDES) $(CFLAGS) -M $$i; \ + done > .depend + +clean: + /bin/rm -f *.o $(EXECS) $(EXTRAS) core .depend + +sg_simple5: sg_simple5.o $(D_FILES) + $(CC) -o $@ $(LDFLAGS) $@.o $(D_FILES) + + +install: $(EXECS) + install -d $(INSTDIR) + for name in $^; \ + do install -s -o root -g root -m 755 $$name $(INSTDIR); \ + done + install -d $(MANDIR)/$(MAN_PREF) + for mp in $(MAN_PGS); \ + do install -o root -g root -m 644 $$mp $(MANDIR)/$(MAN_PREF); \ + gzip -9f $(MANDIR)/$(MAN_PREF)/$$mp; \ + done + +uninstall: + dists="$(EXECS)"; \ + for name in $$dists; do \ + rm -f $(INSTDIR)/$$name; \ + done + for mp in $(MAN_PGS); do \ + rm -f $(MANDIR)/$(MAN_PREF)/$$mp.gz; \ + done + +# Linux uses GNU make and FreeBSD uses Berkely make. The following lines +# only work in Linux. Possible solutions in FreeBSD: +# a) use 'gmake'; b) comment out the next 3 lines, starting with 'ifeq' +# c) build with 'make -f Makefile.freebsd' +# In Linux one can install bmake (but that won't help here). +# ifeq (.depend,$(wildcard .depend)) +# include .depend +# endif diff --git a/examples/README b/examples/README new file mode 100644 index 00000000..7a5ae285 --- /dev/null +++ b/examples/README @@ -0,0 +1,17 @@ +Building files in this directory depends on several files being already +built in the ../lib directory. So to build files here, the ./configure +needs to be executed in the parent directory followed by changing +directory to the lib directory and calling 'make' there. +Another way is to do a top level 'make' after the ./configure which +will make the libraries followed by all the utilities in the src/ +directory. To make them in FreeBSD use 'make -f Makefile.freebsd' . + +There is an brief explanation of each example in the README file in +the main (i.e. this directory's parent) directory. There are also +some notes at the top of each source file. + +Some files that were previously in this directory have been moved to +the 'testing' directory. + +Douglas Gilbert +19th January 2018 diff --git a/examples/reassign_addr.txt b/examples/reassign_addr.txt new file mode 100644 index 00000000..9d48d005 --- /dev/null +++ b/examples/reassign_addr.txt @@ -0,0 +1,11 @@ +# This file is an example for the sg_reassign utility. +# That utility can take one or more logical block addresses from stdin when +# either the '--address=-" or "-a -" option is given on the command line. + +# To see logical block addresses placed in the command parameter block +# without executing them command try something like: +# 'sg_reassign --address=- --dummy -vv /dev/sda < reassign_addr.txt + +1,34,0x33,0X444 0x89abcde 0xdeadbeef # 6 lba's + +# dpg 20070130 diff --git a/examples/scsi_inquiry.c b/examples/scsi_inquiry.c new file mode 100644 index 00000000..99497490 --- /dev/null +++ b/examples/scsi_inquiry.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 1999-2018 D. Gilbert + * 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 2, or (at your option) + * any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg") + * device driver. + * This program does a SCSI inquiry command on the given device and + * outputs some of the result. This program highlights the use of the + * SCSI_IOCTL_SEND_COMMAND ioctl. This should be able to be applied to + * any SCSI device file descriptor (not just one related to sg). [Whether + * this is a good idea on a disk while it is mounted is debatable. + * No detrimental effects when this was tested ...] + * + * Version 0.16 20181207 + */ + +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <scsi/scsi.h> +/* #include <scsi/scsi_ioctl.h> */ /* glibc hides this file sometimes */ + +typedef struct my_scsi_ioctl_command { + unsigned int inlen; /* _excluding_ scsi command length */ + unsigned int outlen; + unsigned char data[1]; /* was 0 but that's not ISO C!! */ + /* on input, scsi command starts here then opt. data */ +} My_Scsi_Ioctl_Command; + +#define OFF (2 * sizeof(unsigned int)) + +#ifndef SCSI_IOCTL_SEND_COMMAND +#define SCSI_IOCTL_SEND_COMMAND 1 +#endif + +#define INQUIRY_CMD 0x12 +#define INQUIRY_CMDLEN 6 +#define INQUIRY_REPLY_LEN 96 + + +int main(int argc, char * argv[]) +{ + int s_fd, res, k, to; + unsigned char inq_cdb [INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, + INQUIRY_REPLY_LEN, 0}; + unsigned char * inqBuff = (unsigned char *) + malloc(OFF + sizeof(inq_cdb) + 512); + unsigned char * buffp = inqBuff + OFF; + My_Scsi_Ioctl_Command * ishp = (My_Scsi_Ioctl_Command *)inqBuff; + char * file_name = 0; + int do_nonblock = 0; + int oflags = 0; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-n")) + do_nonblock = 1; + else if (*argv[k] != '-') + file_name = argv[k]; + else { + printf("Unrecognized argument '%s'\n", argv[k]); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'scsi_inquiry [-n] <scsi_device>'\n"); + printf(" where: -n open device in non-blocking mode\n"); + printf(" Examples: scsi_inquiry /dev/sda\n"); + printf(" scsi_inquiry /dev/sg0\n"); + printf(" scsi_inquiry -n /dev/scd0\n"); + return 1; + } + + if (do_nonblock) + oflags = O_NONBLOCK; + s_fd = open(file_name, oflags | O_RDWR); + if (s_fd < 0) { + if ((EROFS == errno) || (EACCES == errno)) { + s_fd = open(file_name, oflags | O_RDONLY); + if (s_fd < 0) { + perror("scsi_inquiry: open error"); + return 1; + } + } + else { + perror("scsi_inquiry: open error"); + return 1; + } + } + /* Don't worry, being very careful not to write to a none-scsi file ... */ + res = ioctl(s_fd, SCSI_IOCTL_GET_BUS_NUMBER, &to); + if (res < 0) { + /* perror("ioctl on scsi device, error"); */ + printf("scsi_inquiry: not a scsi device\n"); + return 1; + } + + ishp->inlen = 0; + ishp->outlen = INQUIRY_REPLY_LEN; + memcpy(buffp, inq_cdb, INQUIRY_CMDLEN); + res = ioctl(s_fd, SCSI_IOCTL_SEND_COMMAND, inqBuff); + if (0 == res) { + to = (int)*(buffp + 7); + printf(" %.8s %.16s %.4s, byte_7=0x%x\n", buffp + 8, + buffp + 16, buffp + 32, to); + } + else if (res < 0) + perror("scsi_inquiry: SCSI_IOCTL_SEND_COMMAND err"); + else + printf("scsi_inquiry: SCSI_IOCTL_SEND_COMMAND status=0x%x\n", res); + + res = close(s_fd); + if (res < 0) { + perror("scsi_inquiry: close error"); + return 1; + } + return 0; +} diff --git a/examples/sdiag_sas_p0_cjtpat.txt b/examples/sdiag_sas_p0_cjtpat.txt new file mode 100644 index 00000000..72810920 --- /dev/null +++ b/examples/sdiag_sas_p0_cjtpat.txt @@ -0,0 +1,12 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 0 of the +# given device into CJTPAT (jitter pattern) generation mode. +# Physical transmission speed is 3 Gbps +# N.B. This will turn the receiver off on phy id 0. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,0,1,2,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p0_prbs9.txt b/examples/sdiag_sas_p0_prbs9.txt new file mode 100644 index 00000000..1b96f99d --- /dev/null +++ b/examples/sdiag_sas_p0_prbs9.txt @@ -0,0 +1,12 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 0 of the +# given device into PRBS9 (jitter pattern) generation mode. +# Physical transmission speed is 22.5 Gbps +# N.B. This will turn the receiver off on phy id 0. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,0,1,3,c, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_cjtpat.txt b/examples/sdiag_sas_p1_cjtpat.txt new file mode 100644 index 00000000..c82a558e --- /dev/null +++ b/examples/sdiag_sas_p1_cjtpat.txt @@ -0,0 +1,13 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 1 of the +# given device into CJTPAT (jitter pattern) generation mode. +# Physical transmission speed is 3 Gbps +# See sdiag_sas_p1_stop.txt to turn off this test pattern. +# N.B. This will turn the receiver off on phy id 1. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,1,2,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_idle.txt b/examples/sdiag_sas_p1_idle.txt new file mode 100644 index 00000000..39091ced --- /dev/null +++ b/examples/sdiag_sas_p1_idle.txt @@ -0,0 +1,14 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 1 of the +# given device into IDLE (continuously transmit idle dwords) mode. +# Physical transmission speed is 3 Gbps (last number on first +# active line can be 8 for 1.5Gbps, 9 for 3Gbps and 10 for 6Gbps). +# See sdiag_sas_p1_stop.txt to turn off this test pattern. +# N.B. This will turn the receiver off on phy id 1. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,1,12,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_prbs15.txt b/examples/sdiag_sas_p1_prbs15.txt new file mode 100644 index 00000000..1248ab3a --- /dev/null +++ b/examples/sdiag_sas_p1_prbs15.txt @@ -0,0 +1,12 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to put phy identifier 1 of the +# given device into PRBS15 (jitter pattern) generation mode. +# Physical transmission speed is 22.5 Gbps +# N.B. This will turn the receiver off on phy id 1. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,1,4,c, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sdiag_sas_p1_stop.txt b/examples/sdiag_sas_p1_stop.txt new file mode 100644 index 00000000..55b95c34 --- /dev/null +++ b/examples/sdiag_sas_p1_stop.txt @@ -0,0 +1,11 @@ +# This is the hex for a SAS protocol specific diagnostic +# page. It will attempt to stop phy identifier 1 of the +# given device producing a test pattern. +# N.B. This should make phy id 1 usable for SAS protocols again. +# +# Usage example: 'sg_senddiag --pf --raw=- /dev/sg2 < {this_file}' +# +3f,6,0,1c,1,0,2,9, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0 diff --git a/examples/sg__sat_identify.c b/examples/sg__sat_identify.c new file mode 100644 index 00000000..c70eec06 --- /dev/null +++ b/examples/sg__sat_identify.c @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * 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. + * + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program uses a ATA PASS-THROUGH (16) SCSI command to package + an ATA IDENTIFY DEVICE (A1h) command. If the '-p' option is given, + it will package an ATA IDENTIFY PACKET DEVICE (Ech) instead (for + ATAPI device like cd/dvd drives) See http://www.t10.org + SAT draft at time of writing: sat-r08a.pdf + + Invocation: sg__sat_identify [-p] [-v] [-V] <device> + + With SAT, the user can find out whether a device is an ATA disk or + an ATAPI device. The ATA Information VPD page contains a "command + code" field in byte 56. Its values are either ECh for a (s/p)ATA + disk, A1h for a (s/p)ATAPI device, or 0 for unknown. + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ + +#define ATA_IDENTIFY_DEVICE 0xec +#define ATA_IDENTIFY_PACKET_DEVICE 0xa1 +#define ID_RESPONSE_LEN 512 + +#define EBUFF_SZ 256 + +static char * version_str = "1.04 20180220"; + +static void usage() +{ + fprintf(stderr, "Usage: " + "sg__sat_identify [-p] [-v] [-V] <device>\n" + " where: -p do IDENTIFY PACKET DEVICE (def: IDENTIFY " + "DEVICE) command\n" + " -v increase verbosity\n" + " -V print version string and exit\n\n" + "Performs a IDENTIFY (PACKET) DEVICE ATA command via a SAT " + "pass through\n"); +} + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[ID_RESPONSE_LEN]; + uint8_t sense_buffer[32]; + int do_packet = 0; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 4; /* PIO data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * cucp; + + memset(inBuff, 0, sizeof(inBuff)); + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-p")) + ++do_packet; + else if (0 == strcmp(argv[k], "-v")) + ++verbose; + else if (0 == strcmp(argv[k], "-vv")) + verbose += 2; + else if (0 == strcmp(argv[k], "-vvv")) + verbose += 3; + else if (0 == strcmp(argv[k], "-V")) { + fprintf(stderr, "version: %s\n", version_str); + exit(0); + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + usage(); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg__sat_identify: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[6] = 1; /* sector count */ + apt_cdb[14] = (do_packet ? ATA_IDENTIFY_PACKET_DEVICE : + ATA_IDENTIFY_DEVICE); + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | + (byte_block << 2) | t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = ID_RESPONSE_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg__sat_identify: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + if (verbose) + sg_chk_n_print3(">>> ATA_16 command", &io_hdr, 1); + /* check for ATA Return Descriptor */ + cucp = sg_scsi_sense_desc_find(io_hdr.sbp, io_hdr.sb_len_wr, + SAT_ATA_RETURN_DESC); + if (cucp && (cucp[3])) { + if (cucp[3] & 0x4) { + printf("error in returned FIS: aborted command\n"); + printf(" try again with%s '-p' option\n", + (do_packet ? "out" : "")); + break; + } + } + ok = 1; /* not sure what is happening so output response */ + if (0 == verbose) { + printf(">>> Recovered error on ATA_16, may have failed\n"); + printf(" Add '-v' for more information\n"); + } + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + printf("Response for IDENTIFY %sDEVICE ATA command:\n", + (do_packet ? "PACKET " : "")); + dWordHex((const unsigned short *)inBuff, 256, 0, + sg_is_big_endian()); + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg__sat_phy_event.c b/examples/sg__sat_phy_event.c new file mode 100644 index 00000000..40f38e19 --- /dev/null +++ b/examples/sg__sat_phy_event.c @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * 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. + * + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program uses a ATA PASS-THROUGH (16) SCSI command defined + by SAT to package an ATA READ LOG EXT (2Fh) command to fetch + log page 11h. That page contains SATA phy event counters. + For SAT see http://www.t10.org [draft prior to standard: sat-r09.pdf] + For ATA READ LOG EXT command see ATA-8/ACS at www.t13.org . + For SATA phy counter definitions see SATA 2.5 . + + Invocation: sg_sat_phy_event [-v] [-V] <device> + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return Descriptor */ + +#define ATA_READ_LOG_EXT 0x2f +#define SATA_PHY_EVENT_LPAGE 0x11 +#define READ_LOG_EXT_RESPONSE_LEN 512 + +#define EBUFF_SZ 256 + +static const char * version_str = "1.03 20180220"; + +static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"hex", no_argument, 0, 'H'}, + {"ignore", no_argument, 0, 'i'}, + {"raw", no_argument, 0, 'r'}, + {"reset", no_argument, 0, 'R'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +static void usage() +{ + fprintf(stderr, "Usage: " + "sg_sat_phy_event [--help] [--hex] [--raw] [--reset] [--verbose]\n" + " [--version] DEVICE\n" + " where:\n" + " --help|-h print this usage message then exit\n" + " --hex|-H output response in hex bytes, use twice for\n" + " hex words\n" + " --ignore|-i ignore identifier names, output id value " + "instead\n" + " --raw|-r output response in binary to stdout\n" + " --reset|-R reset counters (after read)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string then exit\n\n" + "Sends an ATA READ LOG EXT command via a SAT pass through to " + "fetch\nlog page 11h which contains SATA phy event counters\n"); +} + +struct phy_event_t { + int id; + const char * desc; +}; + +static struct phy_event_t phy_event_arr[] = { + {0x1, "Command failed and ICRC error bit set in Error register"}, + {0x2, "R_ERR(p) response for data FIS"}, + {0x3, "R_ERR(p) response for device-to-host data FIS"}, + {0x4, "R_ERR(p) response for host-to-device data FIS"}, + {0x5, "R_ERR(p) response for non-data FIS"}, + {0x6, "R_ERR(p) response for device-to-host non-data FIS"}, + {0x7, "R_ERR(p) response for host-to-device non-data FIS"}, + {0x8, "Device-to-host non-data FIS retries"}, + {0x9, "Transition from drive PHYRDY to drive PHYRDYn"}, + {0xa, "Signature device-to-host register FISes due to COMRESET"}, + {0xb, "CRC errors within host-to-device FIS"}, + {0xd, "non CRC errors within host-to-device FIS"}, + {0xf, "R_ERR(p) response for host-to-device data FIS, CRC"}, + {0x10, "R_ERR(p) response for host-to-device data FIS, non-CRC"}, + {0x12, "R_ERR(p) response for host-to-device non-data FIS, CRC"}, + {0x13, "R_ERR(p) response for host-to-device non-data FIS, non-CRC"}, + {0xc00, "PM: host-to-device non-data FIS, R_ERR(p) due to collision"}, + {0xc01, "PM: signature register - device-to-host FISes"}, + {0xc02, "PM: corrupts CRC propagation of device-to-host FISes"}, + {0x0, NULL}, +}; + +static const char * find_phy_desc(int id) +{ + const struct phy_event_t * pep; + + for (pep = phy_event_arr; pep->desc; ++pep) { + if ((id & 0xfff) == pep->id) + return pep->desc; + } + return NULL; +} + +static void dStrRaw(const uint8_t * str, int len) +{ + int k; + + for (k = 0 ; k < len; ++k) + printf("%c", str[k]); +} + +int main(int argc, char * argv[]) +{ + int sg_fd, c, k, j, ok, res, id, len, vendor; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * device_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[READ_LOG_EXT_RESPONSE_LEN]; + uint8_t sense_buffer[64]; + int hex = 0; + int ignore = 0; + int raw = 0; + int reset = 0; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 4; /* PIO data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * cucp; + int ret = 0; + uint64_t ull; + const char * cp; + + memset(inBuff, 0, sizeof(inBuff)); + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "hHirRvV", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + usage(); + exit(0); + case 'H': + ++hex; + break; + case 'i': + ++ignore; + break; + case 'r': + ++raw; + break; + case 'R': + ++reset; + break; + case 'v': + ++verbose; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + exit(0); + default: + fprintf(stderr, "unrecognised option code %c [0x%x]\n", c, c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if (NULL == device_name) { + device_name = argv[optind]; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (0 == device_name) { + fprintf(stderr, "no DEVICE name detected\n"); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + + if ((sg_fd = open(device_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_phy_event: error opening file: %s", device_name); + perror(ebuff); + return SG_LIB_FILE_ERROR; + } + + /* Prepare SCSI ATA PASS-THROUGH COMMAND (16) command */ + if (reset > 0) + apt_cdb[4] = 1; /* features (7:0) */ + apt_cdb[6] = 1; /* sector count */ + apt_cdb[8] = SATA_PHY_EVENT_LPAGE; /* lba_low (7:0) */ + apt_cdb[14] = ATA_READ_LOG_EXT; /* command */ + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) | + t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = READ_LOG_EXT_RESPONSE_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_phy_event: SG_IO ioctl error"); + close(sg_fd); + return SG_LIB_CAT_OTHER; + } + + /* now for the error processing */ + ok = 0; + ret = sg_err_category3(&io_hdr); + switch (ret) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + if (verbose) + sg_chk_n_print3(">>> ATA_16 command", &io_hdr, 1); + /* check for ATA Return Descriptor */ + cucp = sg_scsi_sense_desc_find(io_hdr.sbp, io_hdr.sb_len_wr, + SAT_ATA_RETURN_DESC); + if (cucp && (cucp[3])) { + if (cucp[3] & 0x4) { + fprintf(stderr, "error in returned FIS: aborted command\n"); + break; + } + } + ret = 0; + ok = 1; /* not sure what is happening so output response */ + if (0 == verbose) { + fprintf(stderr, ">>> Recovered error on ATA_16, may have " + "failed\n"); + fprintf(stderr, " Add '-v' for more information\n"); + } + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + if (raw > 0) + dStrRaw(inBuff, 512); + else { + if (verbose && hex) + fprintf(stderr, "Response to READ LOG EXT (page=11h):\n"); + if (1 == hex) + hex2stdout(inBuff, 512, 0); + else if (hex > 1) + dWordHex((const unsigned short *)inBuff, 256, 0, + sg_is_big_endian()); + else { + printf("SATA phy event counters:\n"); + for (k = 4; k < 512; k += (len + 2)) { + id = (inBuff[k + 1] << 8) + inBuff[k]; + if (0 == id) + break; + len = ((id >> 12) & 0x7) * 2; + vendor = !!(id & 0x8000); + id = id & 0xfff; + ull = 0; + for (j = len - 1; j >= 0; --j) { + if (j < (len - 1)) + ull <<= 8; + ull |= inBuff[k + 2 + j]; + } + cp = NULL; + if ((0 == vendor) && (0 == ignore)) + cp = find_phy_desc(id); + if (cp) + printf(" %s: %" PRIu64 "\n", cp, ull); + else + printf(" id=0x%x, vendor=%d, data_len=%d, " + "val=%" PRIu64 "\n", id, vendor, len, ull); + } + } + } + } + res = close(sg_fd); + if (res < 0) { + fprintf(stderr, "close error: %s\n", safe_strerror(-res)); + if (0 == ret) + return SG_LIB_FILE_ERROR; + } + return (ret >= 0) ? ret : SG_LIB_CAT_OTHER; +} diff --git a/examples/sg__sat_set_features.c b/examples/sg__sat_set_features.c new file mode 100644 index 00000000..0b200477 --- /dev/null +++ b/examples/sg__sat_set_features.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2006-2020 Douglas Gilbert. + * 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. + * + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program performs a ATA PASS-THROUGH (16) SCSI command in order + to perform an ATA SET FEATURES command. See http://www.t10.org + SAT draft at time of writing: sat-r09.pdf + + Invocation: + sg_sat_set_features [-c <n>] [-f <n>] [-h] [-L <n>] [-v] [-V] <device> + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ + +#define ATA_SET_FEATURES 0xef + +#define EBUFF_SZ 512 + +static char * version_str = "1.06 20201125"; + +static struct option long_options[] = { + {"count", required_argument, 0, 'c'}, + {"chk_cond", no_argument, 0, 'C'}, + {"feature", required_argument, 0, 'f'}, + {"help", no_argument, 0, 'h'}, + {"lba", required_argument, 0, 'L'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, +}; + +void usage() +{ + fprintf(stderr, "Usage: " + "sg_sat_set_features [--count=C] [--chk_cond] [--feature=F] " + "[--help]\n" + " [-lba=LBA] [--verbose] [--version] " + "DEVICE\n" + " where:\n" + " --count=C|-c C count field contents (def: 0)\n" + " --chk_cond|-C set chk_cond field in pass-through " + "(def: 0)\n" + " --feature=F|-f F feature field contents (def: 0)\n" + " --help|-h output this usage message\n" + " --lba=LBA| -L LBA LBA field contents (def: 0)\n" + " --verbose|-v increase verbosity\n" + " --version|-V print version string and exit\n\n" + "Sends an ATA SET FEATURES command via a SAT pass through.\n" + "Primary feature code is placed in '--feature=F' with '--count=C' " + "and\n" + "'--lba=LBA' being auxiliaries for some features. The arguments C, " + "F and LBA\n" + "are decimal unless prefixed by '0x' or have a trailing 'h'.\n" + "Example enabling write cache: 'sg_sat_set_feature --feature=2 " + "/dev/sdc'\n"); +} + +int main(int argc, char * argv[]) +{ + int sg_fd, c, k; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char device_name[256]; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[64]; + int count = 0; + int feature = 0; + int lba = 0; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 3; /* non-data data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * bp = NULL; + + memset(device_name, 0, sizeof(device_name)); + while (1) { + int option_index = 0; + + c = getopt_long(argc, argv, "c:Cf:hL:vV", long_options, + &option_index); + if (c == -1) + break; + + switch (c) { + case 'c': + count = sg_get_num(optarg); + if ((count < 0) || (count > 255)) { + fprintf(stderr, "bad argument for '--count'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'C': + chk_cond = 1; + break; + case 'f': + feature = sg_get_num(optarg); + if ((feature < 0) || (feature > 255)) { + fprintf(stderr, "bad argument for '--feature'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'h': + case '?': + usage(); + return 0; + case 'L': + lba = sg_get_num(optarg); + if ((lba < 0) || (lba > 255)) { + fprintf(stderr, "bad argument for '--lba'\n"); + return SG_LIB_SYNTAX_ERROR; + } + break; + case 'v': + ++verbose; + break; + case 'V': + fprintf(stderr, "version: %s\n", version_str); + return 0; + default: + fprintf(stderr, "unrecognised option code 0x%x ??\n", c); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + if (optind < argc) { + if ('\0' == device_name[0]) { + strncpy(device_name, argv[optind], sizeof(device_name) - 1); + device_name[sizeof(device_name) - 1] = '\0'; + ++optind; + } + if (optind < argc) { + for (; optind < argc; ++optind) + fprintf(stderr, "Unexpected extra argument: %s\n", + argv[optind]); + usage(); + return SG_LIB_SYNTAX_ERROR; + } + } + + if ('\0' == device_name[0]) { + fprintf(stderr, "missing device name!\n"); + usage(); + return 1; + } + + if ((sg_fd = open(device_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_set_features: error opening file: %s", device_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[14] = ATA_SET_FEATURES; + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) | + t_length; + apt_cdb[4] = feature; + apt_cdb[6] = count; + apt_cdb[8] = lba & 0xff; + apt_cdb[10] = (lba >> 8) & 0xff; + apt_cdb[12] = (lba >> 16) & 0xff; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.dxfer_len = 0; + io_hdr.dxferp = NULL; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_set_features: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* error processing: N.B. expect check condition, no sense ... !! */ + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: /* sat-r09 uses this sk */ + case SG_LIB_CAT_NO_SENSE: /* earlier SAT drafts used this */ + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) { + if (verbose > 1) + printf("ATA Return Descriptor expected in sense but not " + "found\n"); + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + } else if (verbose) + sg_chk_n_print3("ATA Return Descriptor", &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + default: + fprintf(stderr, "unexpected SCSI sense category\n"); + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + else if (verbose) + sg_chk_n_print3("ATA Return Descriptor, as expected", + &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg_compare_and_write.txt b/examples/sg_compare_and_write.txt new file mode 100644 index 00000000..86b166b3 --- /dev/null +++ b/examples/sg_compare_and_write.txt @@ -0,0 +1,67 @@ +# sg_compare_and_write.txt +# This file provides a usage example of sg_compare_and_write. +# sg_compare_and_write accepts a buffer containing 2 logical instances: +# - the verify instance: used to match the current content of the LBA range +# - the write instance: used to write to the LBA if the verify succeeds +# +# In case of failure to verify the data, the command will return with check +# condition with the sense code set to MISCOMPARE DURING VERIFY OPERATION. +# +# The following example shows initialization, successful and unsuccessful +# compare and write using sg3_utils. I am using caw_buf_zero2one and +# caw_buf_one2zero as shown below. + +$ hexdump /tmp/caw_buf_zero2one +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000200 1111 1111 1111 1111 1111 1111 1111 1111 +* +0000400 + +$ hexdump /tmp/caw_buf_one2zero +0000000 1111 1111 1111 1111 1111 1111 1111 1111 +* +0000200 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000400 + +$ sg_map -i -x +/dev/sg0 0 0 0 0 0 /dev/sda ATA ST3320613AS CC2H +/dev/sg1 3 0 0 0 5 /dev/scd0 HL-DT-ST DVD-RAM GH22NS30 1.01 +/dev/sg2 5 0 0 0 0 /dev/sdb KMNRIO K2 0000 +/dev/sg3 5 0 0 1 0 /dev/sdc KMNRIO K2 0000 + +# First I zero out the volume to make sure that the first compare and write +# will succeed +$ sg_write_same --16 -i /dev/zero -n 0x200000 -x 512 /dev/sdc + +$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000200 + +$ ./sg_compare_and_write --in=/tmp/caw_buf_zero2one --lba=100 --xferlen=1024 /dev/sdc + +# contents of LBA 100 are a block of ones +$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump +0000000 1111 1111 1111 1111 1111 1111 1111 1111 +* +0000200 + +# We repeat the same compare and write command (zero2one input buffer). +# compare and write fails since the verify failed (compared the zero block to +# the actual 1 block in LBA 100 +$ ./sg_compare_and_write --in=/tmp/caw_buf_zero2one --lba=100 --xferlen=1024 /dev/sdc +COMPARE AND WRITE: Fixed format, current; Sense key: Miscompare + Additional sense: Miscompare during verify operation +sg_compare_and_write: SCSI COMPARE AND WRITE failed + +# Now we use the second buffer (one2zero) +$ ./sg_compare_and_write --in=/tmp/caw_buf_one2zero --lba=100 --xferlen=1024 /dev/sdc + +# operation succeeded, contents of LBA 100 are back to zero +$ dd if=/dev/sdc bs=512 count=1 skip=100 2>/dev/null | hexdump +0000000 0000 0000 0000 0000 0000 0000 0000 0000 +* +0000200 + diff --git a/examples/sg_excl.c b/examples/sg_excl.c new file mode 100644 index 00000000..7e589b23 --- /dev/null +++ b/examples/sg_excl.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2003-2018 D. Gilbert + * 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 2, or (at your option) + * any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This is a simple program that tests the O_EXCL flag in sg while + * executing a SCSI INQUIRY command and a + * TEST UNIT READY command using the SCSI generic (sg) driver + * + * Invocation: sg_excl [-x] <sg_device> + * + * Version 3.62 (20181227) + * + * 6 byte INQUIRY command: + * [0x12][ |lu][pg cde][res ][al len][cntrl ] + * + * 6 byte TEST UNIT READY command: + * [0x00][ |lu][res ][res ][res ][res ] + * + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +#define ME "sg_excl: " + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok /*, sg_fd2 */; + uint8_t inq_cdb [INQ_CMD_LEN] = {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t tur_cdb [TUR_CMD_LEN] = {0x00, 0, 0, 0, 0, 0}; + uint8_t inqBuff[INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_excl [-x] <sg_device>'\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDWR | O_EXCL | O_NONBLOCK)) < 0) { + snprintf(ebuff, EBUFF_SZ, ME "error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf(ME "%s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } +#if 0 + if ((sg_fd2 = open(file_name, O_RDWR | O_EXCL)) < 0) { + snprintf(ebuff, EBUFF_SZ, + ME "error opening file: %s a second time", file_name); + perror(ebuff); + return 1; + } else { + printf(ME "second open of %s in violation of O_EXCL\n", file_name); + close(sg_fd2); + } +#endif + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + io_hdr.dxferp = inqBuff; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror(ME "Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror(ME "Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + printf("Wait for 60 seconds with O_EXCL help on %s\n", file_name); + sleep(60); + close(sg_fd); + return 0; +} diff --git a/examples/sg_persist_tst.sh b/examples/sg_persist_tst.sh new file mode 100755 index 00000000..a75f9973 --- /dev/null +++ b/examples/sg_persist_tst.sh @@ -0,0 +1,130 @@ +#!/bin/sh +# This script is meant as an example of using the sg_persist utility +# in the sg3_utils package. This script works as expected on the +# author's Fujitsu MAM3184, Seagate ST373455 and ST9146803SS disks. +# +# Version 2.0 20171104 + +# N.B. make sure the device name is correct for your environment. + +key="123abc" +key2="333aaa" +kk=${key} +rtype="1" +verbose="" + +usage() +{ + echo "Usage: sg_persist_tst.sh [-e] [-h] [-s] [-v] <device>" + echo " where:" + echo " -e, --exclusive exclusive access (def: write " \ + "exclusive)" + echo " -h, --help print usage message" + echo " -s, --second use second key" + echo " -v, --verbose more verbose output" + echo " -vv even more verbose output" + echo " -vvv even more verbose output" + echo "" + echo "Test SCSI Persistent Reservations with sg_persist utility." + echo "Default key is ${key} and alternate, second key is ${key2} ." + echo "Should be harmless (unless one of those keys is already in use)." + echo "The APTPL bit is not set in the PR register so a power cycle" + echo "on the device will clear the reservation if this script stops" + echo "(or is stopped) before clearing it. Tape drives only seem to " + echo "support 'exclusive access' type (so use '-e')." +} + +opt="$1" +while test ! -z "$opt" -a -z "${opt##-*}"; do + opt=${opt#-} + case "$opt" in + e|-exclusive) rtype="3" ;; + h|-help) usage ; exit 0 ;; + s|-second) kk=${key2} ;; + vvv) verbose="-vvv" ;; + vv) verbose="-vv" ;; + v|-verbose) verbose="-v" ;; + *) echo "Unknown option: -$opt " ; exit 1 ;; + esac + shift + opt="$1" +done + +if [ $# -lt 1 ] + then + usage + exit 1 +fi + +echo ">>> try to report capabilities:" +sg_persist -c ${verbose} "$1" +res=$? +case "$res" in + 0) ;; + 1) echo " syntax error" ;; + 2) echo " not ready" ;; + 3) echo " medium error" ;; + 5) echo " illegal request, report capabilities not supported?" ;; + 6) echo " unit attention" ;; + 9) echo " illegal request, Persistent Reserve (In) not supported" ;; + 11) echo " aborted command" ;; + 15) echo " file error with $1 " ;; + 20) echo " no sense" ;; + 21) echo " recovered error" ;; + 33) echo " timeout" ;; + 97) echo " response fails sanity" ;; + 98) echo " other SCSI error" ;; + 99) echo " other error" ;; + *) echo " unknown exit status for sg_persist: $res" ;; +esac +echo "" +sleep 1 + +echo ">>> check if any keys are registered:" +sg_persist --no-inquiry --read-keys ${verbose} "$1" +sleep 1 + +echo +echo ">>> register a key:" +sg_persist -n --out --register --param-sark=${kk} ${verbose} "$1" +sleep 1 + +echo +echo ">>> now key ${kk} should be registered:" +sg_persist -n --read-keys ${verbose} "$1" +sleep 1 + +echo +echo ">>> reserve the device (based on key ${kk}):" +sg_persist -n --out --reserve --param-rk=${kk} --prout-type=${rtype} ${verbose} "$1" +sleep 1 + +echo +echo ">>> check if the device is reserved (it should be now):" +sg_persist -n --read-reservation ${verbose} "$1" +sleep 1 + +echo +echo ">>> try to 'read full status' (may not be supported):" +sg_persist -n --read-full-status ${verbose} "$1" +sleep 1 + +echo +echo ">>> now release reservation:" +sg_persist -n --out --release --param-rk=${kk} --prout-type=${rtype} ${verbose} "$1" +sleep 1 + +echo +echo ">>> check if the device is reserved (it should _not_ be now):" +sg_persist -n --read-reservation ${verbose} "$1" +sleep 1 + +echo +echo ">>> unregister key ${kk}:" +sg_persist -n --out --register --param-rk=${kk} ${verbose} "$1" +sleep 1 + +echo +echo ">>> now key ${kk} should not be registered:" +sg_persist -n -k ${verbose} "$1" +sleep 1 diff --git a/examples/sg_sat_chk_power.c b/examples/sg_sat_chk_power.c new file mode 100644 index 00000000..ca24f665 --- /dev/null +++ b/examples/sg_sat_chk_power.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * 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. + * + * 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. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "sg_lib.h" +#include "sg_pr2serr.h" +#include "sg_io_linux.h" + +/* This program performs a ATA PASS-THROUGH (16) SCSI command in order + to perform an ATA CHECK POWER MODE command. See http://www.t10.org + SAT draft at time of writing: sat-r09.pdf + + Invocation: sg_sat_chk_power [-v] [-V] <device> + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ +#define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d + +#define ATA_CHECK_POWER_MODE 0xe5 + +#define EBUFF_SZ 256 + +static const char * version_str = "1.08 20181207"; + + +#if 0 +/* Returns length of decoded fixed format sense for SAT ATA pass-through + * command, else returns 0. If returns 0 (expected sense data not found) + * then '\0' placed in first byte of bp. */ +static int +sg_sat_decode_fixed_sense(const uint8_t * sp, int slen, char * bp, + int max_blen, int verbose) +{ + int n; + + if ((NULL == bp) || (NULL == sp) || (max_blen < 1) || (slen < 14)) + return 0; + bp[0] = '\0'; + if ((0x70 != (0x7f & sp[0])) || + (SPC_SK_RECOVERED_ERROR != (0xf & sp[2])) || + (0 != sp[12]) || (ASCQ_ATA_PT_INFO_AVAILABLE != sp[13])) + return 0; + n = sg_scnpr(bp, max_blen, "error=0x%x, status=0x%x, device=0x%x, " + "sector_count(7:0)=0x%x%c\n", sp[3], sp[4], sp[5], sp[6], + ((0x40 & sp[8]) ? '+' : ' ')); + if (n >= max_blen) + return max_blen - 1; + n += sg_scnpr(bp + n, max_blen - n, "extend=%d, log_index=0x%x, " + "lba_high,mid,low(7:0)=0x%x,0x%x,0x%x%c\n", + (!!(0x80 & sp[8])), (0xf & sp[8]), sp[9], sp[10], sp[11], + ((0x20 & sp[8]) ? '+' : ' ')); + if (n >= max_blen) + return max_blen - 1; + if (verbose) + n += sg_scnpr(bp + n, max_blen - n, " sector_count_upper_nonzero=" + "%d, lba_upper_nonzero=%d\n", !!(0x40 & sp[8]), + !!(0x20 & sp[8])); + return (n >= max_blen) ? max_blen - 1 : n; +} +#endif + +int main(int argc, char * argv[]) +{ + int sg_fd, k; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t sense_buffer[64]; + int verbose = 0; + int extend = 0; + int chk_cond = 1; /* set to 1 to read register(s) back */ + int protocol = 3; /* non-dat data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 0; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * bp = NULL; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-v")) + ++verbose; + else if (0 == strcmp(argv[k], "-vv")) + verbose += 2; + else if (0 == strcmp(argv[k], "-vvv")) + verbose += 3; + else if (0 == strcmp(argv[k], "-V")) { + fprintf(stderr, "version: %s\n", version_str); + exit(0); + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_sat_chk_power [-v] [-V] <device>'\n"); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_chk_power: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[14] = ATA_CHECK_POWER_MODE; + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | + (byte_block << 2) | t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.dxfer_len = 0; + io_hdr.dxferp = NULL; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_chk_power: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* error processing: N.B. expect check condition, no sense ... !! */ + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: /* sat-r09 (latest) uses this sk */ + case SG_LIB_CAT_NO_SENSE: /* earlier SAT drafts used this */ + /* XXX: Until the spec decides which one to go with. 20060607 */ + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) { + if (verbose > 1) + printf("ATA Return Descriptor expected in sense but not " + "found\n"); + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + } else if (verbose) + sg_chk_n_print3("ATA Return Descriptor, as expected", + &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + default: + fprintf(stderr, "unexpected SCSI sense category\n"); + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + else if (verbose) + sg_chk_n_print3("ATA Return Descriptor, as expected", + &io_hdr, 1); + if (bp && bp[3]) { + if (bp[3] & 0x4) + printf("error in returned FIS: aborted command\n"); + else + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + } + break; + } + + if (bp) { + switch (bp[5]) { /* sector_count (7:0) */ + case 0xff: + printf("In active mode or idle mode\n"); + break; + case 0x80: + printf("In idle mode\n"); + break; + case 0x41: + printf("In NV power mode and spindle is spun or spinning up\n"); + break; + case 0x40: + printf("In NV power mode and spindle is spun or spinning down\n"); + break; + case 0x0: + printf("In standby mode\n"); + break; + default: + printf("unknown power mode (sector count) value=0x%x\n", bp[5]); + break; + } + } else + fprintf(stderr, "Expecting a ATA Return Descriptor in sense and " + "didn't receive it\n"); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_sat_smart_rd_data.c b/examples/sg_sat_smart_rd_data.c new file mode 100644 index 00000000..223da2a7 --- /dev/null +++ b/examples/sg_sat_smart_rd_data.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2006-2018 Douglas Gilbert. + * 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. + * + * 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. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program performs a ATA PASS-THROUGH (16) SCSI command in order + to perform an ATA SMART/READ DATA command. See http://www.t10.org + SAT draft at time of writing: sat-r08.pdf + + Invocation: sg_sat_smart_rd_data [-v] [-V] <device> + +*/ + +#define SAT_ATA_PASS_THROUGH16 0x85 +#define SAT_ATA_PASS_THROUGH16_LEN 16 +#define SAT_ATA_RETURN_DESC 9 /* ATA Return (sense) Descriptor */ + +#define ATA_SMART 0xb0 +#define ATA_SMART_READ_DATA 0xd0 +#define SMART_READ_DATA_RESPONSE_LEN 512 + +#define EBUFF_SZ 256 + +static char * version_str = "1.05 20181207"; + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] = + {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[SMART_READ_DATA_RESPONSE_LEN]; + uint8_t sense_buffer[32]; + int verbose = 0; + int extend = 0; + int chk_cond = 0; /* set to 1 to read register(s) back */ + int protocol = 4; /* PIO data-in */ + int t_dir = 1; /* 0 -> to device, 1 -> from device */ + int byte_block = 1; /* 0 -> bytes, 1 -> 512 byte blocks */ + int t_length = 2; /* 0 -> no data transferred, 2 -> sector count */ + const uint8_t * bp = NULL; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp(argv[k], "-v")) + ++verbose; + else if (0 == strcmp(argv[k], "-vv")) + verbose += 2; + else if (0 == strcmp(argv[k], "-vvv")) + verbose += 3; + else if (0 == strcmp(argv[k], "-V")) { + fprintf(stderr, "version: %s\n", version_str); + exit(0); + } else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_sat_smart_rd_data [-v] [-V] <device>'\n"); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_sat_smart_rd_data: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + + /* Prepare ATA PASS-THROUGH COMMAND (16) command */ + apt_cdb[4] = ATA_SMART_READ_DATA; /* feature (7:0) */ + apt_cdb[6] = 1; /* number of block (sector count) */ + apt_cdb[10] = 0x4f; /* lba_mid (7:0) */ + apt_cdb[12] = 0xc2; /* lba_high (7:0) */ + apt_cdb[14] = ATA_SMART; + apt_cdb[1] = (protocol << 1) | extend; + apt_cdb[2] = (chk_cond << 5) | (t_dir << 3) | (byte_block << 2) | + t_length; + if (verbose) { + fprintf(stderr, " ata pass through(16) cdb: "); + for (k = 0; k < SAT_ATA_PASS_THROUGH16_LEN; ++k) + fprintf(stderr, "%02x ", apt_cdb[k]); + fprintf(stderr, "\n"); + } + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(apt_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = SMART_READ_DATA_RESPONSE_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = apt_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_sat_smart_rd_data: SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + bp = sg_scsi_sense_desc_find(sense_buffer, sizeof(sense_buffer), + SAT_ATA_RETURN_DESC); + if (NULL == bp) { + if (verbose > 1) + printf("ATA Return Descriptor expected in sense but not " + "found\n"); + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + } else if (verbose) + sg_chk_n_print3("ATA Return Descriptor", &io_hdr, 1); + if (bp && bp[3]) + printf("error=0x%x, status=0x%x\n", bp[3], bp[13]); + else + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("ATA_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + printf("Response:\n"); + dWordHex((const unsigned short *)inBuff, 256, 0, + sg_is_big_endian()); + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple1.c b/examples/sg_simple1.c new file mode 100644 index 00000000..b8ef3491 --- /dev/null +++ b/examples/sg_simple1.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 1999-2018 D. Gilbert + * 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 2, or (at your option) + * any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This is a simple program executing a SCSI INQUIRY command and a + * TEST UNIT READY command using the SCSI generic (sg) driver + * There is another variant of this program called "sg_simple2" + * which does not include the sg_lib.h header and logic and so has + * simpler but more primitive error processing. + * In the lk 2.6 series devices nodes such as /dev/sda also support + * the SG_IO ioctl. + * + * Invocation: sg_simple1 [-x] <scsi_device> + * + * Version 3.60 (20181207) + * + * 6 byte INQUIRY command: + * [0x12][ |lu][pg cde][res ][al len][cntrl ] + * + * 6 byte TEST UNIT READY command: + * [0x00][ |lu][res ][res ][res ][res ] + * + */ + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + unsigned char inq_cdb [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char tur_cdb [TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple1 [-x] <sg_device>'\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple1: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple1: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + io_hdr.dxferp = inqBuff; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple1: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple1: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple16.c b/examples/sg_simple16.c new file mode 100644 index 00000000..e119ca34 --- /dev/null +++ b/examples/sg_simple16.c @@ -0,0 +1,122 @@ +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This program performs a READ_16 command as scsi mid-level support + 16 byte commands from lk 2.4.15 + +* Copyright (C) 2001-2018 D. Gilbert +* 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 2, or (at your option) +* any later version. + + Invocation: sg_simple16 <scsi_device> + + Version 1.04 (20180218) + +*/ + +#define READ16_REPLY_LEN 512 +#define READ16_CMD_LEN 16 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + uint8_t r16_cdb [READ16_CMD_LEN] = + {0x88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + uint8_t inBuff[READ16_REPLY_LEN]; + uint8_t sense_buffer[32]; + + for (k = 1; k < argc; ++k) { + if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple16 <sg_device>'\n"); + return 1; + } + + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple16: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple16: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare READ_16 command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(r16_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = READ16_REPLY_LEN; + io_hdr.dxferp = inBuff; + io_hdr.cmdp = r16_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple16: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on READ_16, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("READ_16 command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + printf("READ_16 duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple2.c b/examples/sg_simple2.c new file mode 100644 index 00000000..a0710be5 --- /dev/null +++ b/examples/sg_simple2.c @@ -0,0 +1,197 @@ +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_linux_inc.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver. + There is another variant of this program called "sg_simple1" + which includes the sg_lib.h header and logic and so has more + advanced error processing. + This version demonstrates the "sg3" interface. + In the lk 2.6 series devices nodes such as /dev/sda also support + the SG_IO ioctl. + +* Copyright (C) 1999-2016 D. Gilbert +* 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 2, or (at your option) +* any later version. + + Invocation: sg_simple2 [-x] <scsi_device> + + Version 03.59 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#define INQ_REPLY_LEN 96 /* logic assumes >= sizeof(inq_cdb) */ +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + + +int main(int argc, char * argv[]) +{ + int sg_fd, k; + unsigned char inq_cdb[INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char tur_cdb[TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + unsigned char inqBuff[INQ_REPLY_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple2 [-x] <sg_device>'\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple2: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple2: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + io_hdr.dxferp = inqBuff; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple2: Inquiry SG_IO ioctl error"); + close(sg_fd); + 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) { + printf("INQUIRY sense data: "); + for (k = 0; k < io_hdr.sb_len_wr; ++k) { + if ((k > 0) && (0 == (k % 10))) + printf("\n "); + printf("0x%02x ", sense_buffer[k]); + } + printf("\n"); + } + if (io_hdr.masked_status) + printf("INQUIRY SCSI status=0x%x\n", io_hdr.status); + if (io_hdr.host_status) + printf("INQUIRY host_status=0x%x\n", io_hdr.host_status); + if (io_hdr.driver_status) + printf("INQUIRY driver_status=0x%x\n", io_hdr.driver_status); + } + else { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + } + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple2: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + 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) { + printf("TEST UNIT READY sense data: "); + for (k = 0; k < io_hdr.sb_len_wr; ++k) { + if ((k > 0) && (0 == (k % 10))) + printf("\n "); + printf("0x%02x ", sense_buffer[k]); + } + printf("\n"); + } + else if (io_hdr.masked_status) + printf("TEST UNIT READY SCSI status=0x%x\n", io_hdr.status); + else if (io_hdr.host_status) + printf("TEST UNIT READY host_status=0x%x\n", io_hdr.host_status); + else if (io_hdr.driver_status) + printf("TEST UNIT READY driver_status=0x%x\n", + io_hdr.driver_status); + else + printf("TEST UNIT READY unexpected error\n"); + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + } + else + printf("Test Unit Ready successful so unit is ready!\n"); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple3.c b/examples/sg_simple3.c new file mode 100644 index 00000000..4927a923 --- /dev/null +++ b/examples/sg_simple3.c @@ -0,0 +1,205 @@ +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver. + There is another variant of this program called "sg_simple1". + This variant demonstrates using the scatter gather facility in + the sg_io_hdr interface to break an INQUIRY response into its + component parts. + +* Copyright (C) 1999-2016 D. Gilbert +* 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 2, or (at your option) +* any later version. + + Invocation: sg_simple3 [-x] <sg_device> + + Version 03.59 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#define INQ_REPLY_BASE_LEN 8 +#define INQ_REPLY_VID_LEN 8 +#define INQ_REPLY_PID_LEN 16 +#define INQ_REPLY_PREV_LEN 4 +#define INQ_REPLY_IOVEC_COUNT 4 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + unsigned char inq_cdb[INQ_CMD_LEN] = {0x12, 0, 0, 0, + INQ_REPLY_BASE_LEN + INQ_REPLY_VID_LEN + + INQ_REPLY_PID_LEN + INQ_REPLY_PREV_LEN, 0}; + unsigned char tur_cdb[TUR_CMD_LEN] = {0x00, 0, 0, 0, 0, 0}; + sg_iovec_t iovec[INQ_REPLY_IOVEC_COUNT]; + unsigned char inqBaseBuff[INQ_REPLY_BASE_LEN]; + char inqVidBuff[INQ_REPLY_VID_LEN]; + char inqPidBuff[INQ_REPLY_PID_LEN]; + char inqPRevBuff[INQ_REPLY_PREV_LEN]; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple3 [-x] <sg_device>'\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple3: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000)) { + printf("sg_simple3: %s doesn't seem to be an new sg device\n", + file_name); + close(sg_fd); + return 1; + } + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + io_hdr.iovec_count = INQ_REPLY_IOVEC_COUNT; + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_BASE_LEN + INQ_REPLY_VID_LEN + + INQ_REPLY_PID_LEN + INQ_REPLY_PREV_LEN; + iovec[0].iov_base = inqBaseBuff; + iovec[0].iov_len = INQ_REPLY_BASE_LEN; + iovec[1].iov_base = inqVidBuff; + iovec[1].iov_len = INQ_REPLY_VID_LEN; + iovec[2].iov_base = inqPidBuff; + iovec[2].iov_len = INQ_REPLY_PID_LEN; + iovec[3].iov_base = inqPRevBuff; + iovec[3].iov_len = INQ_REPLY_PREV_LEN; + io_hdr.dxferp = iovec; + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + /* io_hdr.flags = 0; */ /* take defaults: indirect IO, etc */ + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple3: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBaseBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", inqVidBuff, inqPidBuff, inqPRevBuff); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple3: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", io_hdr.duration, io_hdr.resid, + (int)io_hdr.msg_status); + + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple4.c b/examples/sg_simple4.c new file mode 100644 index 00000000..95a5023b --- /dev/null +++ b/examples/sg_simple4.c @@ -0,0 +1,238 @@ +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include "sg_lib.h" +#include "sg_io_linux.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic (sg) driver + This variant shows mmap-ed IO being used to read the data returned + by the INQUIRY command. + +* Copyright (C) 2001-2016 D. Gilbert +* 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 2, or (at your option) +* any later version. + + Invocation: sg_simple4 [-x] <sg_device> + + Version 1.02 (20160528) + +6 byte INQUIRY command: +[0x12][ |lu][pg cde][res ][al len][cntrl ] + +6 byte TEST UNIT READY command: +[0x00][ |lu][res ][res ][res ][res ] + +*/ + +#ifndef SG_FLAG_MMAP_IO +#define SG_FLAG_MMAP_IO 4 +#endif /* since /usr/include/scsi/sg.h doesn't know about this yet */ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define EBUFF_SZ 256 + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok; + unsigned char inq_cdb[INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + unsigned char tur_cdb[TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + unsigned char * inqBuff; + unsigned char * inqBuff2; + sg_io_hdr_t io_hdr; + char * file_name = 0; + char ebuff[EBUFF_SZ]; + unsigned char sense_buffer[32]; + int do_extra = 0; + + for (k = 1; k < argc; ++k) { + if (0 == memcmp("-x", argv[k], 2)) + do_extra = 1; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple4 [-x] <sg_device>'\n"); + return 1; + } + + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if ((sg_fd = open(file_name, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sg_simple4: error opening file: %s", file_name); + perror(ebuff); + return 1; + } + /* Just to be safe, check we have a new sg device by trying an ioctl */ + if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30122)) { + printf("sg_simple4: %s needs sg driver version >= 3.1.22\n", + file_name); + close(sg_fd); + return 1; + } + + /* since I know this program will only read from inqBuff then I use + PROT_READ rather than PROT_READ | PROT_WRITE */ + inqBuff = (unsigned char *)mmap(NULL, 8000, PROT_READ | PROT_WRITE, + MAP_SHARED, sg_fd, 0); + if (MAP_FAILED == inqBuff) { + snprintf(ebuff, EBUFF_SZ, "sg_simple4: error using mmap() on " + "file: %s", file_name); + perror(ebuff); + return 1; + } + if (inqBuff[0]) + printf("non-null char at inqBuff[0]\n"); + if (inqBuff[5000]) + printf("non-null char at inqBuff[5000]\n"); + + /* Prepare INQUIRY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(inq_cdb); + /* io_hdr.iovec_count = 0; */ /* memset takes care of this */ + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = INQ_REPLY_LEN; + /* io_hdr.dxferp = inqBuff; // ignored in mmap-ed IO */ + io_hdr.cmdp = inq_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + io_hdr.flags = SG_FLAG_MMAP_IO; + /* io_hdr.pack_id = 0; */ + /* io_hdr.usr_ptr = NULL; */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple4: Inquiry SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on INQUIRY, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("INQUIRY command error", &io_hdr, 1); + break; + } + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + int f = (int)*(p + 7); + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32); + printf("[wide=%d sync=%d cmdque=%d sftre=%d]\n", + !!(f & 0x20), !!(f & 0x10), !!(f & 2), !!(f & 1)); + /* Extra info, not necessary to look at */ + if (do_extra) + printf("INQUIRY duration=%u millisecs, resid=%d, msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + } + + + /* Prepare TEST UNIT READY command */ + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(tur_cdb); + io_hdr.mx_sb_len = sizeof(sense_buffer); + io_hdr.dxfer_direction = SG_DXFER_NONE; + io_hdr.cmdp = tur_cdb; + io_hdr.sbp = sense_buffer; + io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */ + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("sg_simple4: Test Unit Ready SG_IO ioctl error"); + close(sg_fd); + return 1; + } + + /* now for the error processing */ + ok = 0; + switch (sg_err_category3(&io_hdr)) { + case SG_LIB_CAT_CLEAN: + ok = 1; + break; + case SG_LIB_CAT_RECOVERED: + printf("Recovered error on Test Unit Ready, continuing\n"); + ok = 1; + break; + default: /* won't bother decoding other categories */ + sg_chk_n_print3("Test Unit Ready command error", &io_hdr, 1); + break; + } + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + if (do_extra) + printf("TEST UNIT READY duration=%u millisecs, resid=%d, " + "msg_status=%d\n", + io_hdr.duration, io_hdr.resid, (int)io_hdr.msg_status); + + /* munmap(inqBuff, 8000); */ + /* could call munmap(inqBuff, INQ_REPLY_LEN) here but following close() + causes this too happen anyway */ +#if 1 + inqBuff2 = (unsigned char *)mmap(NULL, 8000, PROT_READ | PROT_WRITE, + MAP_SHARED, sg_fd, 0); + if (MAP_FAILED == inqBuff2) { + snprintf(ebuff, EBUFF_SZ, "sg_simple4: error using mmap() 2 on " + "file: %s", file_name); + perror(ebuff); + return 1; + } + if (inqBuff2[0]) + printf("non-null char at inqBuff2[0]\n"); + if (inqBuff2[5000]) + printf("non-null char at inqBuff2[5000]\n"); + { + pid_t pid; + pid = fork(); + if (pid) { + inqBuff2[5000] = 33; + munmap(inqBuff, 8000); + sleep(3); + } + else { + inqBuff[5000] = 0xaa; + munmap(inqBuff, 8000); + sleep(1); + } + } +#endif + close(sg_fd); + return 0; +} diff --git a/examples/sg_simple5.c b/examples/sg_simple5.c new file mode 100644 index 00000000..47bf7a05 --- /dev/null +++ b/examples/sg_simple5.c @@ -0,0 +1,236 @@ +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "sg_lib.h" +#include "sg_pt.h" + +/* This is a simple program executing a SCSI INQUIRY command and a + TEST UNIT READY command using the SCSI generic pass through + interface. This allows this example program to be ported to + OSes other than linux. + +* Copyright (C) 2006-20018 D. Gilbert +* 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 2, or (at your option) +* any later version. + + Invocation: sg_simple5 [-x] <scsi_device> + + Version 1.03 (20180220) + +*/ + +#define INQ_REPLY_LEN 96 +#define INQ_CMD_LEN 6 +#define TUR_CMD_LEN 6 + +#define CMD_TIMEOUT_SECS 60 + + +int main(int argc, char * argv[]) +{ + int sg_fd, k, ok, dsize, res, duration, resid, cat, got, slen; + uint8_t inq_cdb [INQ_CMD_LEN] = + {0x12, 0, 0, 0, INQ_REPLY_LEN, 0}; + uint8_t tur_cdb [TUR_CMD_LEN] = + {0x00, 0, 0, 0, 0, 0}; + uint8_t inqBuff[INQ_REPLY_LEN]; + char * file_name = 0; + char b[512]; + uint8_t sense_b[32]; + int verbose = 0; + struct sg_pt_base * ptvp; + + for (k = 1; k < argc; ++k) { + if (0 == strcmp("-v", argv[k])) + verbose = 1; + else if (0 == strcmp("-vv", argv[k])) + verbose = 2; + else if (0 == strcmp("-vvv", argv[k])) + verbose = 3; + else if (*argv[k] == '-') { + printf("Unrecognized switch: %s\n", argv[k]); + file_name = 0; + break; + } + else if (0 == file_name) + file_name = argv[k]; + else { + printf("too many arguments\n"); + file_name = 0; + break; + } + } + if (0 == file_name) { + printf("Usage: 'sg_simple5 [-v|-vv|-vvv] <device>'\n"); + return 1; + } + + sg_fd = scsi_pt_open_device(file_name, 1 /* ro */, 0); + /* N.B. An access mode of O_RDWR is required for some SCSI commands */ + if (sg_fd < 0) { + fprintf(stderr, "error opening file: %s: %s\n", + file_name, safe_strerror(-sg_fd)); + return 1; + } + + dsize = sizeof(inqBuff); + ok = 0; + + ptvp = construct_scsi_pt_obj(); /* one object per command */ + if (NULL == ptvp) { + fprintf(stderr, "sg_simple5: out of memory\n"); + return -1; + } + set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + set_scsi_pt_data_in(ptvp, inqBuff, dsize); + res = do_scsi_pt(ptvp, sg_fd, CMD_TIMEOUT_SECS, verbose); + if (res < 0) { + fprintf(stderr, " pass through os error: %s\n", + safe_strerror(-res)); + goto finish_inq; + } else if (SCSI_PT_DO_BAD_PARAMS == res) { + fprintf(stderr, " bad pass through setup\n"); + goto finish_inq; + } else if (SCSI_PT_DO_TIMEOUT == res) { + fprintf(stderr, " pass through timeout\n"); + goto finish_inq; + } + if ((verbose > 1) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + fprintf(stderr, " duration=%d ms\n", duration); + resid = get_scsi_pt_resid(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + got = dsize - resid; + if (verbose && (resid > 0)) + fprintf(stderr, " requested %d bytes but " + "got %d bytes)\n", dsize, got); + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + if (verbose) { + sg_get_scsi_status_str(get_scsi_pt_status_response(ptvp), + sizeof(b), b); + fprintf(stderr, " scsi status: %s\n", b); + } + goto finish_inq; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptvp); + if (verbose) { + sg_get_sense_str("", sense_b, slen, (verbose > 1), + sizeof(b), b); + fprintf(stderr, "%s", b); + } + if (verbose && (resid > 0)) { + got = dsize - resid; + if ((verbose) || (got > 0)) + fprintf(stderr, " requested %d bytes but " + "got %d bytes\n", dsize, got); + } + goto finish_inq; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " transport: %s", b); + } + goto finish_inq; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " os: %s", b); + } + goto finish_inq; + default: + fprintf(stderr, " unknown pass through result " + "category (%d)\n", cat); + goto finish_inq; + } + + ok = 1; +finish_inq: + destruct_scsi_pt_obj(ptvp); + + if (ok) { /* output result if it is available */ + char * p = (char *)inqBuff; + + printf("Some of the INQUIRY command's results:\n"); + printf(" %.8s %.16s %.4s\n", p + 8, p + 16, p + 32); + } + ok = 0; + + + /* Now prepare TEST UNIT READY command */ + ptvp = construct_scsi_pt_obj(); /* one object per command */ + if (NULL == ptvp) { + fprintf(stderr, "sg_simple5: out of memory\n"); + return -1; + } + set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb)); + set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b)); + /* no data in or out */ + res = do_scsi_pt(ptvp, sg_fd, CMD_TIMEOUT_SECS, verbose); + if (res < 0) { + fprintf(stderr, " pass through os error: %s\n", + safe_strerror(-res)); + goto finish_inq; + } else if (SCSI_PT_DO_BAD_PARAMS == res) { + fprintf(stderr, " bad pass through setup\n"); + goto finish_inq; + } else if (SCSI_PT_DO_TIMEOUT == res) { + fprintf(stderr, " pass through timeout\n"); + goto finish_inq; + } + if ((verbose > 1) && ((duration = get_scsi_pt_duration_ms(ptvp)) >= 0)) + fprintf(stderr, " duration=%d ms\n", duration); + resid = get_scsi_pt_resid(ptvp); + switch ((cat = get_scsi_pt_result_category(ptvp))) { + case SCSI_PT_RESULT_GOOD: + break; + case SCSI_PT_RESULT_STATUS: /* other than GOOD and CHECK CONDITION */ + if (verbose) { + sg_get_scsi_status_str(get_scsi_pt_status_response(ptvp), + sizeof(b), b); + fprintf(stderr, " scsi status: %s\n", b); + } + goto finish_tur; + case SCSI_PT_RESULT_SENSE: + slen = get_scsi_pt_sense_len(ptvp); + if (verbose) { + sg_get_sense_str("", sense_b, slen, (verbose > 1), + sizeof(b), b); + fprintf(stderr, "%s", b); + } + goto finish_tur; + case SCSI_PT_RESULT_TRANSPORT_ERR: + if (verbose) { + get_scsi_pt_transport_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " transport: %s", b); + } + goto finish_tur; + case SCSI_PT_RESULT_OS_ERR: + if (verbose) { + get_scsi_pt_os_err_str(ptvp, sizeof(b), b); + fprintf(stderr, " os: %s", b); + } + goto finish_tur; + default: + fprintf(stderr, " unknown pass through result " + "category (%d)\n", cat); + goto finish_tur; + } + + ok = 1; +finish_tur: + destruct_scsi_pt_obj(ptvp); + + if (ok) + printf("Test Unit Ready successful so unit is ready!\n"); + else + printf("Test Unit Ready failed so unit may _not_ be ready!\n"); + + scsi_pt_close_device(sg_fd); + return 0; +} diff --git a/examples/sg_unmap_example.txt b/examples/sg_unmap_example.txt new file mode 100644 index 00000000..2b07dbae --- /dev/null +++ b/examples/sg_unmap_example.txt @@ -0,0 +1,40 @@ +# sg_unmap_example.txt +# This is an example of the contents of a file that can be given to sg_unmap +# For example, assume the /dev/sdc is a scratch disk (e.g. one made by the +# scsi_debug kernel module) then: +# sg_unmap --in=sg_unmap_example.txt /dev/sdc + +0x12345677,1 # unmap LBA 0x12345677 +0x12345678 2 # unmap LBA 0x12345678 and 0x12345679 +0x12340000 3333 # unmap 3333 blocks starting at LBA 0x12340000 + +0X5a5a5a5a5a 0 # unmaps 0 blocks (i.e. does nothing) + + a5a5a5h +7 # unmap 7 blocks starting at LBA 0xa5a5a5 + +# Note that there can be leading and trailing whitespace and whitespace +# (plus comma) can be a separator. +# +# Example invocation: +# $ sg_unmap --in=../examples/sg_unmap_example.txt -vv /dev/sdc +# open /dev/sg2 with flags=0x802 +# unmap cdb: 42 00 00 00 00 00 00 00 58 00 +# unmap parameter list: +# 00 56 00 50 00 00 00 00 00 00 00 00 12 34 56 77 +# 00 00 00 01 00 00 00 00 00 00 00 00 12 34 56 78 +# 00 00 00 02 00 00 00 00 00 00 00 00 12 34 00 00 +# 00 00 0d 05 00 00 00 00 00 00 00 5a 5a 5a 5a 5a +# 00 00 00 00 00 00 00 00 00 00 00 00 00 a5 a5 a5 +# 00 00 00 07 00 00 00 00 +# sg_cmds_process_resp: slen=18 +# unmap: Fixed format, current; Sense key: Illegal Request +# Additional sense: Invalid command operation code +# Raw sense data (in hex): +# 70 00 05 00 00 00 00 0a 00 00 00 00 20 00 00 00 +# 00 00 +# UNMAP not supported +# +# -------------------------------------------------------- +# Notice the 8 byte header then 5 descriptors in the parameter +# list diff --git a/examples/sgq_dd.c b/examples/sgq_dd.c new file mode 100644 index 00000000..80e8817a --- /dev/null +++ b/examples/sgq_dd.c @@ -0,0 +1,1230 @@ +/* + * A utility program for the Linux OS SCSI generic ("sg") device driver. + * Copyright (C) 1999-2010 D. Gilbert and P. Allworth + * 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 2, or (at your option) + * any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is a specialization of the Unix "dd" command in which + * one or both of the given files is a scsi generic device or a raw + * device. A block size ('bs') is assumed to be 512 if not given. This + * program complains if 'ibs' or 'obs' are given with some other value + * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If + * 'of' is not given or 'of=-' then stdout assumed. Multipliers: + * 'c','C' *1 'b','B' *512 'k' *1024 'K' *1000 + * 'm' *(1024^2) 'M' *(1000^2) 'g' *(1024^3) 'G' *(1000^3) + * + * A non-standard argument "bpt" (blocks per transfer) is added to control + * the maximum number of blocks in each transfer. The default value is 128. + * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16KB + * in this case) are transferred to or from the sg device in a single SCSI + * command. + * + * This version should compile with Linux sg drivers with version numbers + * >= 30000 . This version uses queuing within the Linux sg driver. + */ + +#define _XOPEN_SOURCE 500 + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <poll.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include <linux/major.h> +#include <sys/time.h> +typedef uint8_t u_char; /* horrible, for scsi.h */ +#include "sg_lib.h" +#include "sg_io_linux.h" +#include "sg_unaligned.h" + + +static char * version_str = "0.63 20190324"; +/* resurrected from "0.55 20020509" */ + +#define DEF_BLOCK_SIZE 512 +#define DEF_BLOCKS_PER_TRANSFER 128 + + +#define SENSE_BUFF_LEN 32 /* Arbitrary, could be larger */ +#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */ +#define S_RW_LEN 10 /* Use SCSI READ(10) and WRITE(10) */ + +#define SGP_READ10 0x28 +#define SGP_WRITE10 0x2a +#define DEF_NUM_THREADS 4 /* actually degree of concurrency */ +#define MAX_NUM_THREADS 1024 + +#ifndef RAW_MAJOR +#define RAW_MAJOR 255 /*unlikely value */ +#endif + +#define FT_OTHER 0 /* filetype other than sg or raw device */ +#define FT_SG 1 /* filetype is sg char device */ +#define FT_RAW 2 /* filetype is raw char device */ + +#define QS_IDLE 0 /* ready to start a copy cycle */ +#define QS_IN_STARTED 1 /* commenced read */ +#define QS_IN_FINISHED 2 /* finished read, ready for write */ +#define QS_OUT_STARTED 3 /* commenced write */ + +#define QS_IN_POLL 11 +#define QS_OUT_POLL 12 + +#define STR_SZ 1024 +#define INOUTF_SZ 512 +#define EBUFF_SZ 512 + + +struct request_element; + +typedef struct request_collection +{ /* one instance visible to all threads */ + int infd; + int skip; + int in_type; + int in_scsi_type; + int in_blk; /* next block address to read */ + int in_count; /* blocks remaining for next read */ + int in_done_count; /* count of completed in blocks */ + int in_partial; + int outfd; + int seek; + int out_type; + int out_scsi_type; + int out_blk; /* next block address to write */ + int out_count; /* blocks remaining for next write */ + int out_done_count; /* count of completed out blocks */ + int out_partial; + int bs; + int bpt; + int dio; + int dio_incomplete; + int sum_of_resids; + int coe; + int debug; + int num_rq_elems; + struct request_element * req_arr; +} Rq_coll; + +typedef struct request_element +{ /* one instance per worker thread */ + int qstate; /* "QS" state */ + int infd; + int outfd; + int wr; + int blk; + int num_blks; + uint8_t * buffp; + uint8_t * alloc_bp; + sg_io_hdr_t io_hdr; + uint8_t cmd[S_RW_LEN]; + uint8_t sb[SENSE_BUFF_LEN]; + int bs; + int dio; + int dio_incomplete; + int resid; + int in_scsi_type; + int out_scsi_type; + int debug; +} Rq_elem; + +static Rq_coll rcoll; +static struct pollfd in_pollfd_arr[MAX_NUM_THREADS]; +static struct pollfd out_pollfd_arr[MAX_NUM_THREADS]; +static int dd_count = -1; + +static const char * sg_allow_dio = "/sys/module/sg/parameters/allow_dio"; + +static int sg_finish_io(int wr, Rq_elem * rep); + + +/* Returns the number of times 'ch' is found in string 's' given the + * string's length. */ +static int +num_chs_in_str(const char * s, int slen, int ch) +{ + int res = 0; + + while (--slen >= 0) { + if (ch == s[slen]) + ++res; + } + return res; +} + +static void +install_handler (int sig_num, void (*sig_handler) (int sig)) +{ + struct sigaction sigact; + sigaction (sig_num, NULL, &sigact); + if (sigact.sa_handler != SIG_IGN) + { + sigact.sa_handler = sig_handler; + sigemptyset (&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction (sig_num, &sigact, NULL); + } +} + +static void +print_stats() +{ + int infull, outfull; + + if (0 != rcoll.out_count) + fprintf(stderr, " remaining block count=%d\n", rcoll.out_count); + infull = dd_count - rcoll.in_done_count - rcoll.in_partial; + fprintf(stderr, "%d+%d records in\n", infull, rcoll.in_partial); + outfull = dd_count - rcoll.out_done_count - rcoll.out_partial; + fprintf(stderr, "%d+%d records out\n", outfull, rcoll.out_partial); +} + +static void +interrupt_handler(int sig) +{ + struct sigaction sigact; + + sigact.sa_handler = SIG_DFL; + sigemptyset (&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction (sig, &sigact, NULL); + fprintf(stderr, "Interrupted by signal,"); + print_stats (); + kill (getpid (), sig); +} + +static void +siginfo_handler(int sig) +{ + fprintf(stderr, "Progress report, continuing ...\n"); + print_stats (); + if (sig) { } /* suppress unused warning */ +} + +static int +dd_filetype(const char * filename) +{ + struct stat st; + + if (stat(filename, &st) < 0) + return FT_OTHER; + if (S_ISCHR(st.st_mode)) { + if (RAW_MAJOR == major(st.st_rdev)) + return FT_RAW; + else if (SCSI_GENERIC_MAJOR == major(st.st_rdev)) + return FT_SG; + } + return FT_OTHER; +} + +static void +usage() +{ + fprintf(stderr, "Usage: " + "sgq_dd [if=<infile>] [skip=<n>] [of=<ofile>] [seek=<n>] " + "[bs=<num>]\n" + " [bpt=<num>] [count=<n>] [dio=0|1] [thr=<n>] " + "[coe=0|1] [gen=<n>]\n" + " [time=0|1] [deb=<n>] [--version]\n" + " usually either 'if' or 'of' is a sg or raw device\n" + " 'bpt' is blocks_per_transfer (default is 128)\n" + " 'dio' is direct IO, 1->attempt, 0->indirect IO (def)\n" + " 'thr' is number of queues, must be > 0, default 4, max 1024\n"); + fprintf(stderr, " 'coe' continue on sg error, 0->exit (def), " + "1->zero + continue\n" + " 'time' 0->no timing(def), 1->time plus calculate throughput\n" + " 'gen' 0-> 1 file is special(def), 1-> any files allowed\n" + " 'deb' is debug, 0->none (def), > 0->varying degrees of debug\n"); +} + +/* Returns -1 for error, 0 for nothing found, QS_IN_POLL or QS_OUT_POLL */ +static int +do_poll(Rq_coll * clp, int timeout, int * req_indexp) +{ + int k, res; + + if (FT_SG == clp->out_type) { + while (((res = poll(out_pollfd_arr, clp->num_rq_elems, timeout)) < 0) + && (EINTR == errno)) + ; + if (res < 0) { + perror("poll error on output fds"); + return -1; + } + else if (res > 0) { + for (k = 0; k < clp->num_rq_elems; ++k) { + if (out_pollfd_arr[k].revents & POLLIN) { + if (req_indexp) + *req_indexp = k; + return QS_OUT_POLL; + } + } + } + } + if (FT_SG == clp->in_type) { + while (((res = poll(in_pollfd_arr, clp->num_rq_elems, timeout)) < 0) + && (EINTR == errno)) + ; + if (res < 0) { + perror("poll error on input fds"); + return -1; + } + else if (res > 0) { + for (k = 0; k < clp->num_rq_elems; ++k) { + if (in_pollfd_arr[k].revents & POLLIN) { + if (req_indexp) + *req_indexp = k; + return QS_IN_POLL; + } + } + } + } + return 0; +} + + +/* Return of 0 -> success, -1 -> failure, 2 -> try again */ +static int +read_capacity(int sg_fd, int * num_sect, int * sect_sz) +{ + int res; + uint8_t rc_cdb [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint8_t rcBuff[64]; + uint8_t sense_b[64]; + sg_io_hdr_t io_hdr; + + memset(&io_hdr, 0, sizeof(sg_io_hdr_t)); + io_hdr.interface_id = 'S'; + io_hdr.cmd_len = sizeof(rc_cdb); + io_hdr.mx_sb_len = sizeof(sense_b); + io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; + io_hdr.dxfer_len = sizeof(rcBuff); + io_hdr.dxferp = rcBuff; + io_hdr.cmdp = rc_cdb; + io_hdr.sbp = sense_b; + io_hdr.timeout = DEF_TIMEOUT; + + if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) { + perror("read_capacity (SG_IO) error"); + return -1; + } + res = sg_err_category3(&io_hdr); + if (SG_LIB_CAT_UNIT_ATTENTION == res) + return 2; /* probably have another go ... */ + else if (SG_LIB_CAT_CLEAN != res) { + sg_chk_n_print3("read capacity", &io_hdr, 1); + return -1; + } + *num_sect = 1 + sg_get_unaligned_be32(rcBuff + 0); + *sect_sz = sg_get_unaligned_be32(rcBuff + 4); +#ifdef DEBUG + fprintf(stderr, "number of sectors=%d, sector size=%d\n", + *num_sect, *sect_sz); +#endif + return 0; +} + +/* 0 -> ok, 1 -> short read, -1 -> error */ +static int +normal_in_operation(Rq_coll * clp, Rq_elem * rep, int blocks) +{ + int res; + int stop_after_write = 0; + + rep->qstate = QS_IN_STARTED; + if (rep->debug > 8) + fprintf(stderr, "normal_in_operation: start blk=%d num_blks=%d\n", + rep->blk, rep->num_blks); + while (((res = read(rep->infd, rep->buffp, + blocks * rep->bs)) < 0) && (EINTR == errno)) + ; + if (res < 0) { + fprintf(stderr, "sgq_dd: reading, in_blk=%d, errno=%d\n", rep->blk, + errno); + return -1; + } + if (res < blocks * rep->bs) { + int o_blocks = blocks; + stop_after_write = 1; + blocks = res / rep->bs; + if ((res % rep->bs) > 0) { + blocks++; + clp->in_partial++; + } + /* Reverse out + re-apply blocks on clp */ + clp->in_blk -= o_blocks; + clp->in_count += o_blocks; + rep->num_blks = blocks; + clp->in_blk += blocks; + clp->in_count -= blocks; + } + clp->in_done_count -= blocks; + rep->qstate = QS_IN_FINISHED; + return stop_after_write; +} + +/* 0 -> ok, -1 -> error */ +static int +normal_out_operation(Rq_coll * clp, Rq_elem * rep, int blocks) +{ + int res; + + rep->qstate = QS_OUT_STARTED; + if (rep->debug > 8) + fprintf(stderr, "normal_out_operation: start blk=%d num_blks=%d\n", + rep->blk, rep->num_blks); + while (((res = write(rep->outfd, rep->buffp, + rep->num_blks * rep->bs)) < 0) && (EINTR == errno)) + ; + if (res < 0) { + fprintf(stderr, "sgq_dd: output, out_blk=%d, errno=%d\n", rep->blk, + errno); + return -1; + } + if (res < blocks * rep->bs) { + blocks = res / rep->bs; + if ((res % rep->bs) > 0) { + blocks++; + clp->out_partial++; + } + rep->num_blks = blocks; + } + clp->out_done_count -= blocks; + rep->qstate = QS_IDLE; + return 0; +} + +/* Returns 1 for retryable, 0 for ok, -ve for error */ +static int +sg_fin_in_operation(Rq_coll * clp, Rq_elem * rep) +{ + int res; + + rep->qstate = QS_IN_FINISHED; + res = sg_finish_io(rep->wr, rep); + if (res < 0) { + if (clp->coe) { + memset(rep->buffp, 0, rep->num_blks * rep->bs); + fprintf(stderr, ">> substituted zeros for in blk=%d for " + "%d bytes\n", rep->blk, rep->num_blks * rep->bs); + res = 0; + } + else { + fprintf(stderr, "error finishing sg in command\n"); + return res; + } + } + if (0 == res) { /* looks good, going to return */ + if (rep->dio_incomplete || rep->resid) { + clp->dio_incomplete += rep->dio_incomplete; + clp->sum_of_resids += rep->resid; + } + clp->in_done_count -= rep->num_blks; + } + return res; +} + +/* Returns 1 for retryable, 0 for ok, -ve for error */ +static int +sg_fin_out_operation(Rq_coll * clp, Rq_elem * rep) +{ + int res; + + rep->qstate = QS_IDLE; + res = sg_finish_io(rep->wr, rep); + if (res < 0) { + if (clp->coe) { + fprintf(stderr, ">> ignored error for out blk=%d for " + "%d bytes\n", rep->blk, rep->num_blks * rep->bs); + res = 0; + } + else { + fprintf(stderr, "error finishing sg out command\n"); + return res; + } + } + if (0 == res) { + if (rep->dio_incomplete || rep->resid) { + clp->dio_incomplete += rep->dio_incomplete; + clp->sum_of_resids += rep->resid; + } + clp->out_done_count -= rep->num_blks; + } + return res; +} + +static int +sg_start_io(Rq_elem * rep) +{ + sg_io_hdr_t * hp = &rep->io_hdr; + int res; + + rep->qstate = rep->wr ? QS_OUT_STARTED : QS_IN_STARTED; + memset(rep->cmd, 0, sizeof(rep->cmd)); + rep->cmd[0] = rep->wr ? SGP_WRITE10 : SGP_READ10; + sg_put_unaligned_be32((uint32_t)rep->blk, rep->cmd + 2); + sg_put_unaligned_be16((uint16_t)rep->num_blks, rep->cmd + 7); + memset(hp, 0, sizeof(sg_io_hdr_t)); + hp->interface_id = 'S'; + hp->cmd_len = sizeof(rep->cmd); + hp->cmdp = rep->cmd; + hp->dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; + hp->dxfer_len = rep->bs * rep->num_blks; + hp->dxferp = rep->buffp; + hp->mx_sb_len = sizeof(rep->sb); + hp->sbp = rep->sb; + hp->timeout = DEF_TIMEOUT; + hp->usr_ptr = rep; + hp->pack_id = rep->blk; + if (rep->dio) + hp->flags |= SG_FLAG_DIRECT_IO; + if (rep->debug > 8) { + fprintf(stderr, "sg_start_io: SCSI %s, blk=%d num_blks=%d\n", + rep->wr ? "WRITE" : "READ", rep->blk, rep->num_blks); + sg_print_command(hp->cmdp); + fprintf(stderr, " len=%d, dxfrp=%p, cmd_len=%d\n", + hp->dxfer_len, hp->dxferp, hp->cmd_len); + } + + while (((res = write(rep->wr ? rep->outfd : rep->infd, hp, + sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno)) + ; + if (res < 0) { + if (ENOMEM == errno) + return 1; + return res; + } + return 0; +} + +/* -1 -> unrecoverable error, 0 -> successful, 1 -> try again */ +static int +sg_finish_io(int wr, Rq_elem * rep) +{ + int res; + sg_io_hdr_t io_hdr; + sg_io_hdr_t * hp; +#if 0 + static int testing = 0; /* thread dubious! */ +#endif + + memset(&io_hdr, 0 , sizeof(sg_io_hdr_t)); + /* FORCE_PACK_ID active set only read packet with matching pack_id */ + io_hdr.interface_id = 'S'; + io_hdr.dxfer_direction = rep->wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV; + io_hdr.pack_id = rep->blk; + + while (((res = read(wr ? rep->outfd : rep->infd, &io_hdr, + sizeof(sg_io_hdr_t))) < 0) && (EINTR == errno)) + ; + if (res < 0) { + perror("finishing io on sg device, error"); + return -1; + } + if (rep != (Rq_elem *)io_hdr.usr_ptr) { + fprintf(stderr, + "sg_finish_io: bad usr_ptr, request-response mismatch\n"); + exit(1); + } + memcpy(&rep->io_hdr, &io_hdr, sizeof(sg_io_hdr_t)); + hp = &rep->io_hdr; + + switch (sg_err_category3(hp)) { + case SG_LIB_CAT_CLEAN: + break; + case SG_LIB_CAT_RECOVERED: + fprintf(stderr, "Recovered error on block=%d, num=%d\n", + rep->blk, rep->num_blks); + break; + case SG_LIB_CAT_UNIT_ATTENTION: + return 1; + default: + { + char ebuff[EBUFF_SZ]; + snprintf(ebuff, EBUFF_SZ, "%s blk=%d", + rep->wr ? "writing": "reading", rep->blk); + sg_chk_n_print3(ebuff, hp, 1); + return -1; + } + } +#if 0 + if (0 == (++testing % 100)) return -1; +#endif + if (rep->dio && + ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO)) + rep->dio_incomplete = 1; /* count dios done as indirect IO */ + else + rep->dio_incomplete = 0; + rep->resid = hp->resid; + if (rep->debug > 8) + fprintf(stderr, "sg_finish_io: completed %s, blk=%d\n", + wr ? "WRITE" : "READ", rep->blk); + return 0; +} + +/* Returns scsi_type or -1 for error */ +static int +sg_prepare(int fd, int sz) +{ + int res, t; + struct sg_scsi_id info; + + res = ioctl(fd, SG_GET_VERSION_NUM, &t); + if ((res < 0) || (t < 30000)) { + fprintf(stderr, "sgq_dd: sg driver prior to 3.x.y\n"); + return -1; + } + res = ioctl(fd, SG_SET_RESERVED_SIZE, &sz); + if (res < 0) + perror("sgq_dd: SG_SET_RESERVED_SIZE error"); +#if 0 + t = 1; + res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t); + if (res < 0) + perror("sgq_dd: SG_SET_FORCE_PACK_ID error"); +#endif + res = ioctl(fd, SG_GET_SCSI_ID, &info); + if (res < 0) { + perror("sgq_dd: SG_SET_SCSI_ID error"); + return -1; + } + else + return info.scsi_type; +} + +/* Return 0 for ok, anything else for errors */ +static int +prepare_rq_elems(Rq_coll * clp, const char * inf, const char * outf) +{ + int k; + Rq_elem * rep; + size_t psz; + char ebuff[EBUFF_SZ]; + int sz = clp->bpt * clp->bs; + int scsi_type; + + clp->req_arr = malloc(sizeof(Rq_elem) * clp->num_rq_elems); + if (NULL == clp->req_arr) + return 1; + for (k = 0; k < clp->num_rq_elems; ++k) { + rep = &clp->req_arr[k]; + memset(rep, 0, sizeof(Rq_elem)); + psz = getpagesize(); + if (NULL == (rep->alloc_bp = malloc(sz + psz))) + return 1; + rep->buffp = (uint8_t *) + (((unsigned long)rep->alloc_bp + psz - 1) & (~(psz - 1))); + rep->qstate = QS_IDLE; + rep->bs = clp->bs; + rep->dio = clp->dio; + rep->debug = clp->debug; + rep->out_scsi_type = clp->out_scsi_type; + if (FT_SG == clp->in_type) { + if (0 == k) + rep->infd = clp->infd; + else { + if ((rep->infd = open(inf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg reading", inf); + perror(ebuff); + return 1; + } + } + in_pollfd_arr[k].fd = rep->infd; + in_pollfd_arr[k].events = POLLIN; + if ((scsi_type = sg_prepare(rep->infd, sz)) < 0) + return 1; + if (0 == k) + clp->in_scsi_type = scsi_type; + rep->in_scsi_type = clp->in_scsi_type; + } + else + rep->infd = clp->infd; + + if (FT_SG == clp->out_type) { + if (0 == k) + rep->outfd = clp->outfd; + else { + if ((rep->outfd = open(outf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg writing", outf); + perror(ebuff); + return 1; + } + } + out_pollfd_arr[k].fd = rep->outfd; + out_pollfd_arr[k].events = POLLIN; + if ((scsi_type = sg_prepare(rep->outfd, sz)) < 0) + return 1; + if (0 == k) + clp->out_scsi_type = scsi_type; + rep->out_scsi_type = clp->out_scsi_type; + } + else + rep->outfd = clp->outfd; + } + return 0; +} + +/* Returns a "QS" code and req index, or QS_IDLE and position of first idle + (-1 if no idle position). Returns -1 on poll error. */ +static int +decider(Rq_coll * clp, int first_xfer, int * req_indexp) +{ + int k, res; + Rq_elem * rep; + int first_idle_index = -1; + int lowest_blk_index = -1; + int times; + int try_poll = 0; + int lowest_blk = INT_MAX; + + times = first_xfer ? 1 : clp->num_rq_elems; + for (k = 0; k < times; ++k) { + rep = &clp->req_arr[k]; + if ((QS_IN_STARTED == rep->qstate) || + (QS_OUT_STARTED == rep->qstate)) + try_poll = 1; + else if ((QS_IN_FINISHED == rep->qstate) && (rep->blk < lowest_blk)) { + lowest_blk = rep->blk; + lowest_blk_index = k; + } + else if ((QS_IDLE == rep->qstate) && (first_idle_index < 0)) + first_idle_index = k; + } + if (try_poll) { + res = do_poll(clp, 0, req_indexp); + if (0 != res) + return res; + } + + if (lowest_blk_index >= 0) { + if (req_indexp) + *req_indexp = lowest_blk_index; + return QS_IN_FINISHED; + } +#if 0 + if (try_poll) { + res = do_poll(clp, 2, req_indexp); + if (0 != res) + return res; + } +#endif + if (req_indexp) + *req_indexp = first_idle_index; + return QS_IDLE; +} + + +int +main(int argc, char * argv[]) +{ + bool verbose_given = false; + bool version_given = false; + int skip = 0; + int seek = 0; + int ibs = 0; + int obs = 0; + char str[STR_SZ]; + char * key; + char * buf; + char inf[INOUTF_SZ]; + char outf[INOUTF_SZ]; + int res, k, n, keylen; + int in_num_sect = 0; + int out_num_sect = 0; + int num_threads = DEF_NUM_THREADS; + int gen = 0; + int do_time = 0; + int in_sect_sz, out_sect_sz, first_xfer, qstate, req_index, seek_skip; + int blocks, stop_after_write, terminate; + char ebuff[EBUFF_SZ]; + Rq_elem * rep; + struct timeval start_tm, end_tm; + + memset(&rcoll, 0, sizeof(Rq_coll)); + rcoll.bpt = DEF_BLOCKS_PER_TRANSFER; + rcoll.in_type = FT_OTHER; + rcoll.out_type = FT_OTHER; + inf[0] = '\0'; + outf[0] = '\0'; + + for(k = 1; k < argc; k++) { + if (argv[k]) + strncpy(str, argv[k], STR_SZ); + else + continue; + for(key = str, buf = key; *buf && *buf != '=';) + buf++; + if (*buf) + *buf++ = '\0'; + keylen = strlen(key); + if (strcmp(key,"if") == 0) + strncpy(inf, buf, INOUTF_SZ); + else if (strcmp(key,"of") == 0) + strncpy(outf, buf, INOUTF_SZ); + else if (0 == strcmp(key,"ibs")) + ibs = sg_get_num(buf); + else if (0 == strcmp(key,"obs")) + obs = sg_get_num(buf); + else if (0 == strcmp(key,"bs")) + rcoll.bs = sg_get_num(buf); + else if (0 == strcmp(key,"bpt")) + rcoll.bpt = sg_get_num(buf); + else if (0 == strcmp(key,"skip")) + skip = sg_get_num(buf); + else if (0 == strcmp(key,"seek")) + seek = sg_get_num(buf); + else if (0 == strcmp(key,"count")) + dd_count = sg_get_num(buf); + else if (0 == strcmp(key,"dio")) + rcoll.dio = sg_get_num(buf); + else if (0 == strcmp(key,"thr")) + num_threads = sg_get_num(buf); + else if (0 == strcmp(key,"coe")) + rcoll.coe = sg_get_num(buf); + else if (0 == strcmp(key,"gen")) + gen = sg_get_num(buf); + else if ((0 == strncmp(key,"deb", 3)) || (0 == strncmp(key,"verb", 4))) + rcoll.debug = sg_get_num(buf); + else if (0 == strcmp(key,"time")) + do_time = sg_get_num(buf); + else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) { + res = 0; + n = num_chs_in_str(key + 1, keylen - 1, 'h'); + if (n > 0) { + usage(); + return 0; + } + n = num_chs_in_str(key + 1, keylen - 1, 'v'); + if (n > 0) + verbose_given = true; + rcoll.debug += n; + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'V'); + if (n > 0) + version_given = true; + res += n; + if (res < (keylen - 1)) { + fprintf(stderr, "Unrecognised short option in '%s', try " + "'--help'\n", key); + return SG_LIB_SYNTAX_ERROR; + } + } else if (0 == strncmp(key, "--help", 6)) { + usage(); + return 0; + } else if (0 == strncmp(key, "--verb", 6)) { + verbose_given = true; + ++rcoll.debug; + } else if (0 == strncmp(key, "--vers", 6)) + version_given = true; + else { + fprintf(stderr, "Unrecognized argument '%s'\n", key); + usage(); + return 1; + } + } +#ifdef DEBUG + fprintf(stderr, "In DEBUG mode, "); + if (verbose_given && version_given) { + fprintf(stderr, "but override: '-vV' given, zero verbose and " + "continue\n"); + verbose_given = false; + version_given = false; + rcoll.debug = 0; + } else if (! verbose_given) { + fprintf(stderr, "set '-vv'\n"); + rcoll.debug = 2; + } else + fprintf(stderr, "keep verbose=%d\n", rcoll.debug); +#else + if (verbose_given && version_given) + fprintf(stderr, "Not in DEBUG mode, so '-vV' has no special action\n"); +#endif + if (version_given) { + fprintf(stderr, "sgq_dd for sg version 3 driver: %s\n", + version_str); + return 0; + return 0; + } + + if (argc < 2) { + usage(); + return 1; + } + if (rcoll.bs <= 0) { + rcoll.bs = DEF_BLOCK_SIZE; + fprintf(stderr, "Assume default 'bs' (block size) of %d bytes\n", + rcoll.bs); + } + if ((ibs && (ibs != rcoll.bs)) || (obs && (obs != rcoll.bs))) { + fprintf(stderr, "If 'ibs' or 'obs' given must be same as 'bs'\n"); + usage(); + return 1; + } + if ((skip < 0) || (seek < 0)) { + fprintf(stderr, "skip and seek cannot be negative\n"); + return 1; + } + if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) { + fprintf(stderr, "too few or too many threads requested\n"); + usage(); + return 1; + } + if (rcoll.debug) + fprintf(stderr, "sgq_dd: if=%s skip=%d of=%s seek=%d count=%d\n", + inf, skip, outf, seek, dd_count); + install_handler (SIGINT, interrupt_handler); + install_handler (SIGQUIT, interrupt_handler); + install_handler (SIGPIPE, interrupt_handler); + install_handler (SIGUSR1, siginfo_handler); + + rcoll.infd = STDIN_FILENO; + rcoll.outfd = STDOUT_FILENO; + if (inf[0] && ('-' != inf[0])) { + rcoll.in_type = dd_filetype(inf); + + if (FT_SG == rcoll.in_type) { + if ((rcoll.infd = open(inf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg reading", inf); + perror(ebuff); + return 1; + } + } + if (FT_SG != rcoll.in_type) { + if ((rcoll.infd = open(inf, O_RDONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for reading", inf); + perror(ebuff); + return 1; + } + else if (skip > 0) { + loff_t offset = skip; + + offset *= rcoll.bs; /* could exceed 32 here! */ + if (lseek(rcoll.infd, offset, SEEK_SET) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: couldn't skip to required position on %s", inf); + perror(ebuff); + return 1; + } + } + } + } + if (outf[0] && ('-' != outf[0])) { + rcoll.out_type = dd_filetype(outf); + + if (FT_SG == rcoll.out_type) { + if ((rcoll.outfd = open(outf, O_RDWR)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for sg writing", outf); + perror(ebuff); + return 1; + } + } + else { + if (FT_OTHER == rcoll.out_type) { + if ((rcoll.outfd = open(outf, O_WRONLY | O_CREAT, 0666)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for writing", outf); + perror(ebuff); + return 1; + } + } + else { + if ((rcoll.outfd = open(outf, O_WRONLY)) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: could not open %s for raw writing", outf); + perror(ebuff); + return 1; + } + } + if (seek > 0) { + loff_t offset = seek; + + offset *= rcoll.bs; /* could exceed 32 bits here! */ + if (lseek(rcoll.outfd, offset, SEEK_SET) < 0) { + snprintf(ebuff, EBUFF_SZ, + "sgq_dd: couldn't seek to required position on %s", outf); + perror(ebuff); + return 1; + } + } + } + } + if ((STDIN_FILENO == rcoll.infd) && (STDOUT_FILENO == rcoll.outfd)) { + fprintf(stderr, "Disallow both if and of to be stdin and stdout\n"); + return 1; + } + if ((FT_OTHER == rcoll.in_type) && (FT_OTHER == rcoll.out_type) && !gen) { + fprintf(stderr, "Either 'if' or 'of' must be a sg or raw device\n"); + return 1; + } + if (0 == dd_count) + return 0; + else if (dd_count < 0) { + if (FT_SG == rcoll.in_type) { + res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz); + if (2 == res) { + fprintf(stderr, "Unit attention, media changed(in), repeat\n"); + res = read_capacity(rcoll.infd, &in_num_sect, &in_sect_sz); + } + if (0 != res) { + fprintf(stderr, "Unable to read capacity on %s\n", inf); + in_num_sect = -1; + } + else { + if (in_num_sect > skip) + in_num_sect -= skip; + } + } + if (FT_SG == rcoll.out_type) { + res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz); + if (2 == res) { + fprintf(stderr, "Unit attention, media changed(out), " + "repeat\n"); + res = read_capacity(rcoll.outfd, &out_num_sect, &out_sect_sz); + } + if (0 != res) { + fprintf(stderr, "Unable to read capacity on %s\n", outf); + out_num_sect = -1; + } + else { + if (out_num_sect > seek) + out_num_sect -= seek; + } + } + if (in_num_sect > 0) { + if (out_num_sect > 0) + dd_count = (in_num_sect > out_num_sect) ? out_num_sect : + in_num_sect; + else + dd_count = in_num_sect; + } + else + dd_count = out_num_sect; + } + if (rcoll.debug > 1) + fprintf(stderr, "Start of loop, count=%d, in_num_sect=%d, " + "out_num_sect=%d\n", dd_count, in_num_sect, out_num_sect); + if (dd_count <= 0) { + fprintf(stderr, "Couldn't calculate count, please give one\n"); + return 1; + } + + rcoll.in_count = dd_count; + rcoll.in_done_count = dd_count; + rcoll.skip = skip; + rcoll.in_blk = skip; + rcoll.out_count = dd_count; + rcoll.out_done_count = dd_count; + rcoll.seek = seek; + rcoll.out_blk = seek; + + if ((FT_SG == rcoll.in_type) || (FT_SG == rcoll.out_type)) + rcoll.num_rq_elems = num_threads; + else + rcoll.num_rq_elems = 1; + if (prepare_rq_elems(&rcoll, inf, outf)) { + fprintf(stderr, "Setup failure, perhaps no memory\n"); + return 1; + } + + first_xfer = 1; + stop_after_write = 0; + terminate = 0; + seek_skip = rcoll.seek - rcoll.skip; + if (do_time) { + start_tm.tv_sec = 0; + start_tm.tv_usec = 0; + gettimeofday(&start_tm, NULL); + } + while (rcoll.out_done_count > 0) { /* >>>>>>>>> main loop */ + req_index = -1; + qstate = decider(&rcoll, first_xfer, &req_index); + rep = (req_index < 0) ? NULL : (rcoll.req_arr + req_index); + switch (qstate) { + case QS_IDLE: + if ((NULL == rep) || (rcoll.in_count <= 0)) { + /* usleep(1000); */ + /* do_poll(&rcoll, 10, NULL); */ + /* do_poll(&rcoll, 0, NULL); */ + break; + } + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: non-sleeping QS_IDLE state, " + "req_index=%d\n", req_index); + if (first_xfer >= 2) + first_xfer = 0; + else if (1 == first_xfer) + ++first_xfer; + if (stop_after_write) { + terminate = 1; + break; + } + blocks = (rcoll.in_count > rcoll.bpt) ? rcoll.bpt : rcoll.in_count; + rep->wr = 0; + rep->blk = rcoll.in_blk; + rep->num_blks = blocks; + rcoll.in_blk += blocks; + rcoll.in_count -= blocks; + + if (FT_SG == rcoll.in_type) { + res = sg_start_io(rep); + if (0 != res) { + if (1 == res) + fprintf(stderr, "Out of memory starting sg io\n"); + terminate = 1; + } + } + else { + res = normal_in_operation(&rcoll, rep, blocks); + if (res < 0) + terminate = 1; + else if (res > 0) + stop_after_write = 1; + } + break; + case QS_IN_FINISHED: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is QS_IN_FINISHED, " + "req_index=%d\n", req_index); + if ((rep->blk + seek_skip) != rcoll.out_blk) { + /* if write would be out of sequence then wait */ + if (rcoll.debug > 4) + fprintf(stderr, " sgq_dd: QS_IN_FINISHED, " + "out of sequence\n"); + usleep(200); + break; + } + rep->wr = 1; + rep->blk = rcoll.out_blk; + blocks = rep->num_blks; + rcoll.out_blk += blocks; + rcoll.out_count -= blocks; + + if (FT_SG == rcoll.out_type) { + res = sg_start_io(rep); + if (0 != res) { + if (1 == res) + fprintf(stderr, "Out of memory starting sg io\n"); + terminate = 1; + } + } + else { + if (normal_out_operation(&rcoll, rep, blocks) < 0) + terminate = 1; + } + break; + case QS_IN_POLL: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is QS_IN_POLL, " + "req_index=%d\n", req_index); + res = sg_fin_in_operation(&rcoll, rep); + if (res < 0) + terminate = 1; + else if (res > 1) { + if (first_xfer) { + /* only retry on first xfer */ + if (0 != sg_start_io(rep)) + terminate = 1; + } + else + terminate = 1; + } + break; + case QS_OUT_POLL: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is QS_OUT_POLL, " + "req_index=%d\n", req_index); + res = sg_fin_out_operation(&rcoll, rep); + if (res < 0) + terminate = 1; + else if (res > 1) { + if (first_xfer) { + /* only retry on first xfer */ + if (0 != sg_start_io(rep)) + terminate = 1; + } + else + terminate = 1; + } + break; + default: + if (rcoll.debug > 8) + fprintf(stderr, " sgq_dd: state is ?????\n"); + terminate = 1; + break; + } + if (terminate) + break; + } /* >>>>>>>>>>>>> end of main loop */ + + if ((do_time) && (start_tm.tv_sec || start_tm.tv_usec)) { + struct timeval res_tm; + double a, b; + + gettimeofday(&end_tm, NULL); + res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec; + res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec; + if (res_tm.tv_usec < 0) { + --res_tm.tv_sec; + res_tm.tv_usec += 1000000; + } + a = res_tm.tv_sec; + a += (0.000001 * res_tm.tv_usec); + b = (double)rcoll.bs * (dd_count - rcoll.out_done_count); + printf("time to transfer data was %d.%06d secs", + (int)res_tm.tv_sec, (int)res_tm.tv_usec); + if ((a > 0.00001) && (b > 511)) + printf(", %.2f MB/sec\n", b / (a * 1000000.0)); + else + printf("\n"); + } + + if (STDIN_FILENO != rcoll.infd) + close(rcoll.infd); + if (STDOUT_FILENO != rcoll.outfd) + close(rcoll.outfd); + res = 0; + if (0 != rcoll.out_count) { + fprintf(stderr, ">>>> Some error occurred,\n"); + res = 2; + } + print_stats(); + if (rcoll.dio_incomplete) { + int fd; + char c; + + fprintf(stderr, ">> Direct IO requested but incomplete %d times\n", + rcoll.dio_incomplete); + if ((fd = open(sg_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + fprintf(stderr, ">>> %s set to '0' but should be set " + "to '1' for direct IO\n", sg_allow_dio); + } + close(fd); + } + } + if (rcoll.sum_of_resids) + fprintf(stderr, ">> Non-zero sum of residual counts=%d\n", + rcoll.sum_of_resids); + return res; +} diff --git a/examples/transport_ids.txt b/examples/transport_ids.txt new file mode 100644 index 00000000..2657af25 --- /dev/null +++ b/examples/transport_ids.txt @@ -0,0 +1,31 @@ +# This file is an example for the sg_persist utility. +# It discusses using "TransportID"s which are defined (most recently) +# in SPC-4 revision 20 section 7.5.4 titled: "TransportID identifiers". +# +# The sg_persist utility can take one or more "transportID"s from stdin when +# either the '--transport-id=-" or "-X -" option is given on the command +# line. + +# To see transport IDs decoded after they have been read in (e.g. to check +# they are well formed) use the verbose flag 3 times (i.e. "... -vvv ..."). + +# Here is a simple example (for SPI) of a comma separated hex list: +1,0,0,7,0,0,0,1 # SPI, initiator address=7, relative_port_num=1 + +# and here is the transport specific format for the same thing: +# spi,1,7 + +# An example for SAS follows, first as a hex string, then in transport +# specific format (incremented by 1) +6,0,0,0,50,6,5,b0,0,6,f2,60 +sas,500605b00006f261 + +# For iSCSI the hex list form is awkward, better to use the transport +# specific format. [The leading spaces are ignored.] + iqn.5886.com.acme.diskarrays-sn-a8675309 + + + # Leading spaces and tabs before a '#' are ok. + + +# dpg 20090824 |