aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/Makefile127
-rw-r--r--examples/Makefile.freebsd82
-rw-r--r--examples/README17
-rw-r--r--examples/reassign_addr.txt11
-rw-r--r--examples/scsi_inquiry.c130
-rw-r--r--examples/sdiag_sas_p0_cjtpat.txt12
-rw-r--r--examples/sdiag_sas_p0_prbs9.txt12
-rw-r--r--examples/sdiag_sas_p1_cjtpat.txt13
-rw-r--r--examples/sdiag_sas_p1_idle.txt14
-rw-r--r--examples/sdiag_sas_p1_prbs15.txt12
-rw-r--r--examples/sdiag_sas_p1_stop.txt11
-rw-r--r--examples/sg__sat_identify.c216
-rw-r--r--examples/sg__sat_phy_event.c350
-rw-r--r--examples/sg__sat_set_features.c280
-rw-r--r--examples/sg_compare_and_write.txt67
-rw-r--r--examples/sg_excl.c202
-rwxr-xr-xexamples/sg_persist_tst.sh130
-rw-r--r--examples/sg_sat_chk_power.c256
-rw-r--r--examples/sg_sat_smart_rd_data.c188
-rw-r--r--examples/sg_simple1.c193
-rw-r--r--examples/sg_simple16.c122
-rw-r--r--examples/sg_simple2.c197
-rw-r--r--examples/sg_simple3.c205
-rw-r--r--examples/sg_simple4.c238
-rw-r--r--examples/sg_simple5.c236
-rw-r--r--examples/sg_unmap_example.txt40
-rw-r--r--examples/sgq_dd.c1230
-rw-r--r--examples/transport_ids.txt31
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