aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouglas Gilbert <dgilbert@interlog.com>2020-07-02 00:15:50 +0000
committerDouglas Gilbert <dgilbert@interlog.com>2020-07-02 00:15:50 +0000
commit2c59b7853f594c9b3fa7e3f259f7b45ab6d47903 (patch)
tree7b26b7143a3f058954fdea2e5a6071b3844b726b
parent62c85c807d9d57f6bcf9a731d0126feaab661d93 (diff)
downloadsg3_utils-2c59b7853f594c9b3fa7e3f259f7b45ab6d47903.tar.gz
asc/ascq match asc-num.txt @t10 20200624; testing/sg_mrq_testing: new, for blocking mrq usage
git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@853 6180dd3e-e324-4e3e-922d-17de1ae2f315
-rw-r--r--ChangeLog4
-rw-r--r--doc/Makefile.am2
-rw-r--r--doc/Makefile.in14
-rw-r--r--doc/sg3_utils.82
-rw-r--r--doc/sg_rep_pip.84
-rw-r--r--doc/sg_timestamp.82
-rw-r--r--doc/sg_turs.82
-rw-r--r--doc/sg_vpd.82
-rw-r--r--doc/sg_write_same.85
-rw-r--r--lib/sg_lib_data.c5
-rw-r--r--src/sg_dd.c2
-rw-r--r--src/sg_read.c2
-rw-r--r--src/sg_write_verify.c2
-rw-r--r--src/sg_write_x.c2
-rw-r--r--src/sg_xcopy.c2
-rw-r--r--src/sgm_dd.c2
-rw-r--r--testing/Makefile6
-rw-r--r--testing/sg_mrq_dd.cpp4907
-rw-r--r--testing/sgh_dd.cpp134
-rw-r--r--testing/uapi_sg.h1
20 files changed, 5043 insertions, 59 deletions
diff --git a/ChangeLog b/ChangeLog
index b98b0420..2371a8ac 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,7 +2,7 @@ Each utility has its own version number, date of last change and
some description at the top of its ".c" file. All utilities in the main
directory have their own "man" pages. There is also a sg3_utils man page.
-Changelog for sg3_utils-1.46 [20200604] [svn: r852]
+Changelog for sg3_utils-1.46 [20200701] [svn: r853]
- sg_rep_pip: report new provisioning initialization pattern cmd
- sg_dd: separate category for miscompare errors
- sg_get_elem_status: add ralwd bit sbc4r20a
@@ -16,6 +16,8 @@ Changelog for sg3_utils-1.46 [20200604] [svn: r852]
- add dummy start stop unit and test unit ready commands
- wire cache mpage's WCE to nvme 'volatile write cache'
- sg_lib: reprint cdb with illegal request sense key
+ - asc/ascq match asc-num.txt @t10 20200624
+ - testing/sg_mrq_testing: new, for blocking mrq usage
- testing/sgs_dd: add evfd flags and eventfd processing
- testing/sgh_dd: various fixes
diff --git a/doc/Makefile.am b/doc/Makefile.am
index e0dd2e64..22e659e7 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -9,7 +9,7 @@ man_MANS = \
sg_read_block_limits.8 sg_read_buffer.8 sg_read_long.8 sg_readcap.8 \
sg_reassign.8 sg_referrals.8 sg_rep_pip.8 sg_rep_zones.8 \
sg_requests.8 sg_reset_wp.8 sg_rmsn.8 sg_rtpg.8 sg_safte.8 \
- sg_sanitize.8 sg_sat_identify.8 sg_sat_phy_event.8
+ sg_sanitize.8 sg_sat_identify.8 sg_sat_phy_event.8 \
sg_sat_read_gplog.8 sg_sat_set_features.8 sg_seek.8 sg_senddiag.8 \
sg_ses.8 sg_ses_microcode.8 sg_start.8 sg_stpg.8 sg_stream_ctl.8 \
sg_sync.8 sg_timestamp.8 sg_turs.8 sg_unmap.8 sg_verify.8 sg_vpd.8 \
diff --git a/doc/Makefile.in b/doc/Makefile.in
index 448bf3eb..81cdadb3 100644
--- a/doc/Makefile.in
+++ b/doc/Makefile.in
@@ -290,8 +290,13 @@ man_MANS = scsi_mandat.8 scsi_readcap.8 scsi_ready.8 scsi_satl.8 \
sg_read_buffer.8 sg_read_long.8 sg_readcap.8 sg_reassign.8 \
sg_referrals.8 sg_rep_pip.8 sg_rep_zones.8 sg_requests.8 \
sg_reset_wp.8 sg_rmsn.8 sg_rtpg.8 sg_safte.8 sg_sanitize.8 \
- sg_sat_identify.8 sg_sat_phy_event.8 $(am__append_1) \
- $(am__append_3) $(am__append_5)
+ sg_sat_identify.8 sg_sat_phy_event.8 sg_sat_read_gplog.8 \
+ sg_sat_set_features.8 sg_seek.8 sg_senddiag.8 sg_ses.8 \
+ sg_ses_microcode.8 sg_start.8 sg_stpg.8 sg_stream_ctl.8 \
+ sg_sync.8 sg_timestamp.8 sg_turs.8 sg_unmap.8 sg_verify.8 \
+ sg_vpd.8 sg_wr_mode.8 sg_write_buffer.8 sg_write_long.8 \
+ sg_write_same.8 sg_write_verify.8 sg_write_x.8 sg_zone.8 \
+ $(am__append_1) $(am__append_3) $(am__append_5)
CLEANFILES = $(am__append_2) $(am__append_4) $(am__append_6)
all: all-am
@@ -537,11 +542,6 @@ uninstall-man: uninstall-man8
.PRECIOUS: Makefile
- sg_sat_read_gplog.8 sg_sat_set_features.8 sg_seek.8 sg_senddiag.8 \
- sg_ses.8 sg_ses_microcode.8 sg_start.8 sg_stpg.8 sg_stream_ctl.8 \
- sg_sync.8 sg_timestamp.8 sg_turs.8 sg_unmap.8 sg_verify.8 sg_vpd.8 \
- sg_wr_mode.8 sg_write_buffer.8 sg_write_long.8 sg_write_same.8 \
- sg_write_verify.8 sg_write_x.8 sg_zone.8
@OS_LINUX_TRUE@sg_scan.8: sg_scan.8.linux
@OS_LINUX_TRUE@ cp -p $< $@
@OS_WIN32_MINGW_TRUE@sg_scan.8: sg_scan.8.win32
diff --git a/doc/sg3_utils.8 b/doc/sg3_utils.8
index fd0f7991..2dafa936 100644
--- a/doc/sg3_utils.8
+++ b/doc/sg3_utils.8
@@ -1,4 +1,4 @@
-.TH SG3_UTILS "8" "June 2020" "sg3_utils\-1.46" SG3_UTILS
+.TH SG3_UTILS "8" "July 2020" "sg3_utils\-1.46" SG3_UTILS
.SH NAME
sg3_utils \- a package of utilities for sending SCSI commands
.SH SYNOPSIS
diff --git a/doc/sg_rep_pip.8 b/doc/sg_rep_pip.8
index 81dd060d..d054cd2b 100644
--- a/doc/sg_rep_pip.8
+++ b/doc/sg_rep_pip.8
@@ -1,4 +1,4 @@
-.TH SG_REP_PIP "8" "June 2020" "sg3_utils\-1.46" SG3_UTILS
+.TH SG_REP_PIP "8" "July 2020" "sg3_utils\-1.46" SG3_UTILS
.SH NAME
sg_rep_pip \- send SCSI REPORT PROVISIONING INITIALIZATION PATTERN command
.SH SYNOPSIS
@@ -20,7 +20,7 @@ output the usage message then exit.
.TP
\fB\-H\fR, \fB\-\-hex\fR
output the response in hexadecimal to stdout. When used once the whole
-response is output in ASCII hexadecimal with a leading address (starting at
+response is output in ASCII hexadecimal, prefixed by an address (starting at
0) on each line. When used twice the whole response is output in hexadecimal
with no leading address (on each line). The default action is the same as
giving the \fI\-\-hex\fR option once.
diff --git a/doc/sg_timestamp.8 b/doc/sg_timestamp.8
index 3dd076da..f5c69553 100644
--- a/doc/sg_timestamp.8
+++ b/doc/sg_timestamp.8
@@ -22,7 +22,7 @@ The timestamp is sent and received from the \fIDEVICE\fR as the number of
milliseconds since the epoch of 1970\-01\-01 00:00:00 UTC and is held in a 48
bit unsigned integer. That same epoch is used by Unix machines, but they
usually hold the number of seconds since that epoch. The Unix date command
-and especally its "+%s" format is useful in converting to and from
+and especially its "+%s" format is useful in converting to and from
timestamps and more humanly readable forms. See the EXAMPLES section below.
.SH OPTIONS
Arguments to long options are mandatory for short options as well.
diff --git a/doc/sg_turs.8 b/doc/sg_turs.8
index 8cf909c0..6bf8d9cd 100644
--- a/doc/sg_turs.8
+++ b/doc/sg_turs.8
@@ -73,7 +73,7 @@ command that takes a long time to complete (and typically precludes other
media access commands) is still underway, the progress indication can be used
to determine how long before the device returns to its normal state. Around
SPC\-3 T10 changed the preferred command for polling the progress indication
-from TEST UNIT READY to REQUEST SENSE (see the sg_requests utilty).
+from TEST UNIT READY to REQUEST SENSE (see the sg_requests utility).
.PP
The SCSI FORMAT command for disks used with the IMMED bit set is an example
of an operation that takes a significant amount of time and precludes other
diff --git a/doc/sg_vpd.8 b/doc/sg_vpd.8
index 08c371d9..e167e7a2 100644
--- a/doc/sg_vpd.8
+++ b/doc/sg_vpd.8
@@ -45,7 +45,7 @@ displayed to the right of each line of hex.
If this option is used with the \fI\-\-inhex=FN\fR option then the file
\fIFN\fR is assumed to contain 1 or more VPD pages (in ASCII hex or binary).
Decoding continues until the file is exhausted (or an error occurs). Sanity
-checks are aplied on each VPD page's length and the ascending order of VPD
+checks are applied on each VPD page's length and the ascending order of VPD
page numbers (required by SPC\-4) so bad data may be detected.
.br
If the \fI\-\-page=PG\fR option is also given then no VPD page whose page
diff --git a/doc/sg_write_same.8 b/doc/sg_write_same.8
index 1a9d4fff..0e65dd9f 100644
--- a/doc/sg_write_same.8
+++ b/doc/sg_write_same.8
@@ -257,9 +257,8 @@ to the SCSI WRITE SAME command in the data\-out buffer (as required by
SBC\-3). Using the WRITE SAME SCSI command to write one or more blocks
blocks of zeros is equivalent to the NVMe command: Write Zeroes.
.br
-Now we will write zero blocks to the WHOLE disk. [Note sanitize type
-commands will also clear blocks and metdata that are not directly
-visible]:
+Now we will write zero blocks to the WHOLE disk. [Note sanitize type commands
+will also clear blocks and metadata that are not directly visible]:
.PP
sg_write_same \-\-lba=0x0 \-\-num=0 /dev/sdc
.PP
diff --git a/lib/sg_lib_data.c b/lib/sg_lib_data.c
index a53b7cb2..b7417d03 100644
--- a/lib/sg_lib_data.c
+++ b/lib/sg_lib_data.c
@@ -19,7 +19,7 @@
#include "sg_lib_data.h"
-const char * sg_lib_version_str = "2.74 20200423";
+const char * sg_lib_version_str = "2.75 20200624";
/* spc6r01, sbc4r20a, zbc2r04 */
@@ -635,7 +635,7 @@ struct sg_lib_value_name_t sg_lib_read_attr_arr[] = {
/* A conveniently formatted list of SCSI ASC/ASCQ codes and their
* corresponding text can be found at: www.t10.org/lists/asc-num.txt
- * The following should match asc-num.txt dated 20191014 */
+ * The following should match asc-num.txt dated 20200624 */
#ifdef SG_SCSI_STRINGS
struct sg_lib_asc_ascq_range_t sg_lib_asc_ascq_range[] =
@@ -1087,6 +1087,7 @@ struct sg_lib_asc_ascq_t sg_lib_asc_ascq[] =
{0x3B,0x1a,"Data transfer device removed"},
{0x3B,0x1b,"Data transfer device inserted"},
{0x3B,0x1c,"Too many logical objects on partition to support operation"},
+ {0x3B,0x20,"Element static information changed"},
{0x3D,0x00,"Invalid bits in identify message"},
{0x3E,0x00,"Logical unit has not self-configured yet"},
{0x3E,0x01,"Logical unit failure"},
diff --git a/src/sg_dd.c b/src/sg_dd.c
index f950364b..8fa5b141 100644
--- a/src/sg_dd.c
+++ b/src/sg_dd.c
@@ -101,7 +101,7 @@ static const char * version_str = "6.15 20200429";
#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
#ifndef RAW_MAJOR
-#define RAW_MAJOR 255 /*unlikey value */
+#define RAW_MAJOR 255 /*unlikely value */
#endif
#define SG_LIB_FLOCK_ERR 90
diff --git a/src/sg_read.c b/src/sg_read.c
index 4122e620..cd10c2ea 100644
--- a/src/sg_read.c
+++ b/src/sg_read.c
@@ -75,7 +75,7 @@ static const char * version_str = "1.36 20191220";
#define DEF_TIMEOUT 40000 /* 40,000 millisecs == 40 seconds */
#ifndef RAW_MAJOR
-#define RAW_MAJOR 255 /*unlikey value */
+#define RAW_MAJOR 255 /*unlikely value */
#endif
#define FT_OTHER 1 /* filetype other than sg and ... */
diff --git a/src/sg_write_verify.c b/src/sg_write_verify.c
index 5b394c93..58fe9254 100644
--- a/src/sg_write_verify.c
+++ b/src/sg_write_verify.c
@@ -246,7 +246,7 @@ sg_ll_write_verify16(int sg_fd, int wrprotect, bool dpo, int bytchk,
return ret;
}
-/* Returns file descriptor ( >= 0) if successfule. Else a negated sg3_utils
+/* Returns file descriptor ( >= 0) if successful. Else a negated sg3_utils
* error code is returned. */
static int
open_if(const char * fn, int got_stdin)
diff --git a/src/sg_write_x.c b/src/sg_write_x.c
index 0a41d4ae..40edc352 100644
--- a/src/sg_write_x.c
+++ b/src/sg_write_x.c
@@ -156,7 +156,7 @@ struct opts_t {
bool ndob; /* "No Data-Out Buffer" from --same=NDOB */
bool verbose_given;
bool version_given;
- int dld; /* "Duration Limit Descrptor" bit mask; bit 0 -->
+ int dld; /* "Duration Limit Descriptor" bit mask; bit 0 -->
* DLD0, bit 1 --> DLD1, bit 2 --> DLD2
* only WRITE(16) and WRITE SCATTERED(16) */
int dry_run; /* temporary write when used more than once */
diff --git a/src/sg_xcopy.c b/src/sg_xcopy.c
index 352b3887..e691a73e 100644
--- a/src/sg_xcopy.c
+++ b/src/sg_xcopy.c
@@ -97,7 +97,7 @@ static const char * version_str = "0.71 20200510";
#endif
#ifndef RAW_MAJOR
-#define RAW_MAJOR 255 /*unlikey value */
+#define RAW_MAJOR 255 /*unlikely value */
#endif
#define SG_LIB_FLOCK_ERR 90
diff --git a/src/sgm_dd.c b/src/sgm_dd.c
index bbd10e22..b01d694b 100644
--- a/src/sgm_dd.c
+++ b/src/sgm_dd.c
@@ -91,7 +91,7 @@ static const char * version_str = "1.67 20200510";
#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
#ifndef RAW_MAJOR
-#define RAW_MAJOR 255 /*unlikey value */
+#define RAW_MAJOR 255 /*unlikely value */
#endif
#define FT_OTHER 1 /* filetype other than one of following */
diff --git a/testing/Makefile b/testing/Makefile
index a9718f8c..63176f56 100644
--- a/testing/Makefile
+++ b/testing/Makefile
@@ -6,7 +6,8 @@ MANDIR=$(DESTDIR)/$(PREFIX)/man
EXECS = sg_iovec_tst sg_sense_test sg_queue_tst bsg_queue_tst sg_chk_asc \
sg_tst_nvme sg_tst_ioctl sg_tst_bidi tst_sg_lib sgs_dd sg_tst_excl \
- sg_tst_excl2 sg_tst_excl3 sg_tst_context sg_tst_async sgh_dd
+ sg_tst_excl2 sg_tst_excl3 sg_tst_context sg_tst_async sgh_dd \
+ sg_mrq_dd
EXTRAS =
@@ -108,6 +109,9 @@ sg_tst_async: sg_tst_async.o $(LIBFILESNEW)
sgh_dd: sgh_dd.o $(LIBFILESNEW)
$(CXXLD) -o $@ $(LDFLAGS) -pthread -latomic $^
+sg_mrq_dd: sg_mrq_dd.o $(LIBFILESNEW)
+ $(CXXLD) -o $@ $(LDFLAGS) -pthread -latomic $^
+
install: $(EXECS)
install -d $(INSTDIR)
diff --git a/testing/sg_mrq_dd.cpp b/testing/sg_mrq_dd.cpp
new file mode 100644
index 00000000..a42cff74
--- /dev/null
+++ b/testing/sg_mrq_dd.cpp
@@ -0,0 +1,4907 @@
+/*
+ * A utility program for copying files. Specialised for "files" that
+ * represent devices that understand the SCSI command set.
+ *
+ * Copyright (C) 2018-2020 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 program is a specialisation of the Unix "dd" command in which
+ * one or both of the given files is a scsi generic device.
+ * A logical 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.
+ *
+ * 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 (16 KiB
+ * in this case) are transferred to or from the sg device in a single SCSI
+ * command.
+ *
+ * This version is designed for the linux kernel 4 and 5 series.
+ *
+ * sgp_dd is a Posix threads specialization of the sg_dd utility. Both
+ * sgp_dd and sg_dd only perform special tasks when one or both of the given
+ * devices belong to the Linux sg driver.
+ *
+ * sgh_dd further extends sgp_dd to use the experimental kernel buffer
+ * sharing feature added in 3.9.02 .
+ * N.B. This utility was previously called sgs_dd but there was already an
+ * archived version of a dd variant called sgs_dd so this utility name was
+ * renamed [20181221]
+ */
+
+static const char * version_str = "1.01 20200701";
+
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <poll.h>
+#include <limits.h>
+// #include <pthread.h>
+#include <signal.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#ifndef major
+#include <sys/types.h>
+#endif
+#include <sys/time.h>
+#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
+#include <linux/fs.h> /* for BLKSSZGET and friends */
+#include <sys/mman.h> /* for mmap() system call */
+#include <sys/random.h> /* for getrandom() system call */
+
+#include <vector>
+#include <array>
+#include <atomic> // C++ header replacing <stdatomic.h>
+#include <random>
+#include <thread> // needed for std::this_thread::yield()
+#include <mutex>
+#include <condition_variable>
+#include <chrono>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+#include "sg_pr2serr.h"
+
+
+using namespace std;
+
+#ifdef __GNUC__
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wclobbered"
+#endif
+#endif
+
+
+#define MAX_SGL_NUM_VAL (INT32_MAX - 1) /* should reduce for testing */
+// #define MAX_SGL_NUM_VAL 7 /* should reduce for testing */
+#if MAX_SGL_NUM_VAL > INT32_MAX
+#error "MAX_SGL_NUM_VAL cannot exceed 2^31 - 1"
+#endif
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+#define DEF_SCSI_CDB_SZ 10
+#define MAX_SCSI_CDB_SZ 16
+#define PACK_ID_TID_MULTIPLIER (0x1000000) /* 16,777,216 */
+
+#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+
+#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */
+
+#define SGP_READ10 0x28
+#define SGP_PRE_FETCH10 0x34
+#define SGP_PRE_FETCH16 0x90
+#define SGP_VERIFY10 0x2f
+#define SGP_WRITE10 0x2a
+#define DEF_NUM_THREADS 4
+#define MAX_NUM_THREADS 1024 /* was SG_MAX_QUEUE with v3 driver */
+#define DEF_MRQ_NUM 16
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255 /*unlikely value */
+#endif
+
+#define FT_OTHER 1 /* filetype other than one of the following */
+#define FT_SG 2 /* filetype is sg char device */
+#define FT_RAW 4 /* filetype is raw char device */
+#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */
+#define FT_ST 16 /* filetype is st char device (tape) */
+#define FT_BLOCK 32 /* filetype is a block device */
+#define FT_FIFO 64 /* fifo (named or unnamed pipe (stdout)) */
+#define FT_RANDOM_0_FF 128 /* iflag=00, iflag=ff and iflag=random
+ override if=IFILE */
+#define FT_ERROR 256 /* couldn't "stat" file */
+
+#define DEV_NULL_MINOR_NUM 3
+
+#define EBUFF_SZ 768
+
+#define PROC_SCSI_SG_VERSION "/proc/scsi/sg/version"
+#define SYS_SCSI_SG_VERSION "/sys/module/sg/version"
+
+#define SG_SGL_MAX_ELEMENTS 16384
+
+#define SG_COUNT_INDEFINITE (-1)
+#define SG_LBA_INVALID SG_COUNT_INDEFINITE
+
+/* Sizing matches largest SCSI READ and WRITE commands plus those of Unix
+ * read(2)s and write(2)s. User can give larger than 31 bit 'num's but they
+ * are split into several consecutive elements. */
+struct scat_gath_elem {
+ uint64_t lba; /* of start block */
+ uint32_t num; /* number of blocks from and including start block */
+
+ void make_bad() { lba = UINT64_MAX; num = UINT32_MAX; }
+ bool is_bad() const { return (lba == UINT64_MAX && num == UINT32_MAX); }
+};
+
+/* Consider "linearity" as a scatter gather list property. Elements of this
+ * of from the strongest form to the weakest. */
+enum sgl_linearity_e {
+ SGL_LINEAR = 0, /* empty list and 0,0 considered linear */
+ SGL_MONOTONIC, /* since not linear, implies holes */
+ SGL_MONO_OVERLAP, /* monotonic but same LBA in two or more elements */
+ SGL_NON_MONOTONIC /* weakest */
+};
+
+
+/* Holds one scatter gather list and its associated metadata */
+class scat_gath_list {
+public:
+ scat_gath_list() : linearity(SGL_LINEAR), sum_hard(false), m_errno(0),
+ high_lba_p1(0), lowest_lba(0), sum(0) { }
+
+ scat_gath_list(const scat_gath_list &) = default;
+ scat_gath_list & operator=(const scat_gath_list &) = default;
+ ~scat_gath_list() = default;
+
+ bool empty() const;
+ bool empty_or_00() const;
+ int num_elems() const;
+ int64_t get_lowest_lba(bool ignore_degen, bool always_last) const;
+ int64_t get_low_lba_from_linear() const;
+ bool is_pipe_suitable() const;
+
+ friend bool sgls_eq_off(const scat_gath_list &left, int l_e_ind,
+ int l_blk_off,
+ const scat_gath_list &right, int r_e_ind,
+ int r_blk_off, bool allow_partial);
+
+ bool load_from_cli(const char * cl_p, bool b_vb);
+ bool load_from_file(const char * file_name, bool def_hex, bool flexible,
+ bool b_vb);
+ int append_1or(int64_t extra_blks, int64_t start_lba);
+ int append_1or(int64_t extra_blks);
+
+ void dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
+ bool show_sgl, bool lock = true) const;
+
+ /* calculates and sets following bool-s and int64_t-s */
+ void sum_scan(const char * id_str, bool show_sgl, bool b_verbose);
+
+ void set_weaker_linearity(enum sgl_linearity_e lin);
+ enum sgl_linearity_e linearity;
+ const char * linearity_as_str() const;
+
+ bool sum_hard; /* 'num' in last element of 'sgl' is > 0 */
+ int m_errno; /* OS failure errno */
+ int64_t high_lba_p1; /* highest LBA plus 1, next write from and above */
+ int64_t lowest_lba; /* initialized to 0 */
+ int64_t sum; /* of all 'num' elements in 'sgl' */
+
+ friend int diff_between_iters(const struct scat_gath_iter & left,
+ const struct scat_gath_iter & right);
+
+private:
+ friend class scat_gath_iter;
+
+ bool file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
+ bool flexible, bool b_vb);
+
+ vector<scat_gath_elem> sgl; /* an array on heap [0..num_elems()) */
+};
+
+
+class scat_gath_iter {
+public:
+ scat_gath_iter(const scat_gath_list & my_scat_gath_list);
+ scat_gath_iter(const scat_gath_iter & src) = default;
+ scat_gath_iter& operator=(const scat_gath_iter&) = delete;
+ ~scat_gath_iter() = default;
+
+ int64_t current_lba() const;
+ int64_t current_lba_rem_num(int & rem_num) const;
+ struct scat_gath_elem current_elem() const;
+ bool at_end() const;
+ bool is_sgl_linear() const; /* the whole list */
+ int linear_for_n_blks(int max_n) const;
+
+ bool set_by_blk_idx(int64_t _blk_idx);
+ /* add/sub blocks return true if they reach EOL, else false */
+ bool add_blks(uint64_t blk_count);
+ bool sub_blks(uint64_t blk_count);
+
+ void dbg_print(const char * id_str, bool to_stdout, int verbose) const;
+
+ friend int diff_between_iters(const struct scat_gath_iter & left,
+ const struct scat_gath_iter & right);
+
+ friend bool sgls_eq_from_iters(const struct scat_gath_iter & left,
+ const struct scat_gath_iter & right,
+ bool allow_partial);
+
+private:
+ const scat_gath_list &sglist;
+
+ /* dual representation: either it_el_ind,it_blk_off or blk_idx */
+ int it_el_ind; /* refers to sge==sglist[it_el_ind] */
+ int it_blk_off; /* refers to LBA==(sge.lba + it_blk_off) */
+ int64_t blk_idx; /* in range: [0 .. sglist.sum) */
+ bool extend_last;
+};
+
+
+struct flags_t {
+ bool append;
+ bool coe;
+ bool dio;
+ bool direct;
+ bool dpo;
+ bool dsync;
+ bool excl;
+ bool ff;
+ bool fua;
+ bool masync; /* more async sg v4 driver fd flag */
+ bool no_dur;
+ bool order;
+ bool qhead;
+ bool qtail;
+ bool random;
+ bool serial;
+ bool wq_excl;
+ bool zero;
+ int mmap;
+};
+
+typedef pair<int64_t, int> get_next_res;
+typedef array<uint8_t, MAX_SCSI_CDB_SZ> cdb_arr_t;
+
+/* There is one instance of this structure and it is at file scope so it is
+ * initialized to zero. The design of this copy multi-threaded copy algorithm
+ * attempts to have no locks on the fast path. Contention in gcoll.get_next()
+ * is resolved by the loser repeating its operation. Statistics and error
+ * information is held in each thread until it shuts down and contention
+ * can occur at that point. */
+struct global_collection /* one instance visible to all threads */
+{
+ /* get_next() is the pivotal function for multi-threaded safety. It can
+ * be safely called from all threads with the desired number of blocks
+ * (typically mrq*bpt) and this function returns a pair. The first pair
+ * value is the starting count value/index [0..dd_count) and the second
+ * pair value is the number of blocks to copy. If desired_num_blks is
+ * negative this flags an error has occurred. If the second value in the
+ * returned pair is 0 then the calling thread should shutdown; a
+ * negative value indicates an error has occurred (e.g. in another
+ * thread) and the calling thread should shutdown. */
+ get_next_res get_next(int desired_num_blks);
+ atomic<int64_t> next_count_pos;
+
+ int infd;
+ int64_t dd_count;
+ int in_type;
+ int cdbsz_in;
+ int help;
+ struct flags_t in_flags;
+ atomic<int64_t> in_rem_count; /* | count of remaining in blocks */
+ atomic<int> in_partial; /* | */
+ off_t in_st_size; /* Only for FT_OTHER (regular) file */
+ int mrq_num; /* Number of multi-reqs for sg v4 */
+ int outfd;
+ int out_type;
+ int out2fd;
+ int out2_type;
+ off_t out2_st_size; /* Only for FT_OTHER (regular) file */
+ int cdbsz_out;
+ struct flags_t out_flags;
+ atomic<int64_t> out_rem_count; /* | count of remaining out blocks */
+ atomic<int> out_partial; /* | */
+ // pthread_mutex_t out2_mutex;
+ off_t out_st_size; /* Only for FT_OTHER (regular) file */
+ condition_variable infant_cv; /* after thread:0 does first segment */
+ mutex infant_mut;
+ bool processed;
+ int bs;
+ int bpt;
+ int outregfd;
+ int outreg_type;
+ off_t outreg_st_size;
+ atomic<int> dio_incomplete_count;
+ atomic<int> sum_of_resids;
+ int verbose; /* both -v and deb=VERB bump this field */
+ int dry_run;
+ bool cdbsz_given;
+ bool count_given;
+ bool flexible;
+ bool ofile_given;
+ bool ofile2_given;
+ bool unit_nanosec; /* default duration unit is millisecond */
+ bool mrq_cmds; /* mrq=<NRQS>,C given */
+ bool verify; /* don't copy, verify like Unix: cmp */
+ bool prefetch; /* for verify: do PF(b),RD(a),V(b)_a_data */
+ const char * infp;
+ const char * outfp;
+ const char * out2fp;
+ class scat_gath_list i_sgl;
+ class scat_gath_list o_sgl;
+};
+
+typedef struct request_element
+{ /* one instance per worker thread */
+ struct global_collection *clp;
+ bool has_share;
+ bool both_sg;
+ bool same_sg;
+ bool only_in_sg;
+ bool only_out_sg;
+ bool stop_after_write;
+ int id;
+ int infd;
+ int outfd;
+ int out2fd;
+ int outregfd;
+ uint8_t * buffp;
+ uint8_t * alloc_bp;
+ struct sg_io_v4 io_hdr4[2];
+ uint8_t cmd[MAX_SCSI_CDB_SZ];
+ uint8_t sb[SENSE_BUFF_LEN];
+ int dio_incomplete_count;
+ int mmap_active;
+ int resid;
+ int rd_p_id;
+ int rep_count;
+ int rq_id;
+ int mmap_len;
+ int mrq_id;
+ int mrq_index;
+ int mrq_pack_id_off;
+ int64_t in_follow_on;
+ int64_t out_follow_on;
+ int64_t in_local_count;
+ int64_t out_local_count;
+ int64_t in_rem_count;
+ int64_t out_rem_count;
+ int in_local_partial;
+ int out_local_partial;
+ int in_resid_bytes;
+ long seed;
+ struct drand48_data drand; /* opaque, used by srand48_r and mrand48_r */
+} Rq_elem;
+
+/* Additional parameters for sg_start_io() and sg_finish_io() */
+struct sg_io_extra {
+ bool is_wr2;
+ bool prefetch;
+ bool dout_is_split;
+ int hpv4_ind;
+ int blk_offset;
+ int blks;
+};
+
+#define MONO_MRQ_ID_INIT 0x10000
+
+// typedef vector< pair<int, struct sg_io_v4> > mrq_arr_t;
+typedef array<uint8_t, 32> big_cdb; /* allow up to a 32 byte cdb */
+typedef pair< vector<struct sg_io_v4>, vector<big_cdb> > mrq_arr_t;
+
+
+/* Use this class to wrap C++11 <random> features to produce uniform random
+ * unsigned ints in the range [lo, hi] (inclusive) given a_seed */
+class Rand_uint {
+public:
+ Rand_uint(unsigned int lo, unsigned int hi, unsigned int a_seed)
+ : uid(lo, hi), dre(a_seed) { }
+ /* uid ctor takes inclusive range when integral type */
+
+ unsigned int get() { return uid(dre); }
+
+private:
+ uniform_int_distribution<unsigned int> uid;
+ default_random_engine dre;
+};
+
+static atomic<long int> pos_index(0);
+
+static atomic<int> num_ebusy(0);
+static atomic<int> num_start_eagain(0);
+static atomic<int> num_fin_eagain(0);
+static atomic<long> num_waiting_calls(0);
+
+static sigset_t signal_set;
+
+static const char * proc_allow_dio = "/proc/scsi/sg/allow_dio";
+
+static int sg_in_open(struct global_collection *clp, const char *inf,
+ uint8_t **mmpp, int *mmap_len);
+static int sg_out_open(struct global_collection *clp, const char *outf,
+ uint8_t **mmpp, int *mmap_len);
+static int do_both_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4);
+static int do_normal_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4);
+static int do_normal_normal_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks);
+
+#define STRERR_BUFF_LEN 128
+
+static mutex strerr_mut;
+
+static bool have_sg_version = false;
+static int sg_version = 0;
+static bool sg_version_ge_40030 = false;
+static atomic<bool> shutting_down = false;
+static bool do_sync = false;
+static int do_time = 1;
+static struct global_collection gcoll;
+static struct timeval start_tm;
+static int num_threads = DEF_NUM_THREADS;
+static int exit_status = 0;
+static bool after1 = false;
+
+static mutex rand_lba_mutex;
+
+static const char * my_name = "sg_mrq_dd: ";
+
+// static const char * mrq_blk_s = "mrq: ordinary blocking";
+static const char * mrq_svb_s = "mrq: shared variable blocking (svb)";
+static const char * mrq_ob_s = "mrq: ordered blocking";
+static const char * mrq_vb_s = "mrq: variable blocking";
+
+
+#ifdef __GNUC__
+static int pr2serr_lk(const char * fmt, ...)
+ __attribute__ ((format (printf, 1, 2)));
+#if 0
+static void pr_errno_lk(int e_no, const char * fmt, ...)
+ __attribute__ ((format (printf, 2, 3)));
+#endif
+#else
+static int pr2serr_lk(const char * fmt, ...);
+#if 0
+static void pr_errno_lk(int e_no, const char * fmt, ...);
+#endif
+#endif
+
+
+static int
+pr2serr_lk(const char * fmt, ...)
+{
+ int n;
+ va_list args;
+ lock_guard<mutex> lk(strerr_mut);
+
+ va_start(args, fmt);
+ n = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return n;
+}
+
+#if 0 // not used yet
+static void
+pr_errno_lk(int e_no, const char * fmt, ...)
+{
+ char b[180];
+ va_list args;
+ lock_guard<mutex> lk(strerr_mut);
+
+ va_start(args, fmt);
+ vsnprintf(b, sizeof(b), fmt, args);
+ fprintf(stderr, "%s: %s\n", b, strerror(e_no));
+ va_end(args);
+}
+#endif
+
+static void
+lk_print_command_len(const char *prefix, uint8_t * cmdp, int len, bool lock)
+{
+ if (lock) {
+ lock_guard<mutex> lk(strerr_mut);
+
+ if (prefix && *prefix)
+ fputs(prefix, stderr);
+ sg_print_command_len(cmdp, len);
+ } else {
+ if (prefix && *prefix)
+ fputs(prefix, stderr);
+ sg_print_command_len(cmdp, len);
+ }
+}
+
+static void
+lk_chk_n_print4(const char * leadin, const struct sg_io_v4 * h4p,
+ bool raw_sinfo)
+{
+ lock_guard<mutex> lk(strerr_mut);
+
+ sg_linux_sense_print(leadin, h4p->device_status, h4p->transport_status,
+ h4p->driver_status, (const uint8_t *)h4p->response,
+ h4p->response_len, raw_sinfo);
+}
+
+static void
+hex2stderr_lk(const uint8_t * b_str, int len, int no_ascii)
+{
+ lock_guard<mutex> lk(strerr_mut);
+
+ hex2stderr(b_str, len, no_ascii);
+}
+
+/* Flags decoded into abbreviations for those that are set, separated by
+ * '|' . */
+static char *
+sg_flags_str(int flags, int b_len, char * b)
+{
+ int n = 0;
+
+ if ((b_len < 1) || (! b))
+ return b;
+ b[0] = '\0';
+ if (SG_FLAG_DIRECT_IO & flags) { /* 0x1 */
+ n += sg_scnpr(b + n, b_len - n, "DIO|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_MMAP_IO & flags) { /* 0x4 */
+ n += sg_scnpr(b + n, b_len - n, "MMAP|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_YIELD_TAG & flags) { /* 0x8 */
+ n += sg_scnpr(b + n, b_len - n, "YTAG|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_Q_AT_TAIL & flags) { /* 0x10 */
+ n += sg_scnpr(b + n, b_len - n, "QTAI|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SG_FLAG_Q_AT_HEAD & flags) { /* 0x20 */
+ n += sg_scnpr(b + n, b_len - n, "QHEA|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_NO_WAITQ & flags) { /* 0x40 */
+ n += sg_scnpr(b + n, b_len - n, "NWTQ|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DOUT_OFFSET & flags) { /* 0x80 */
+ n += sg_scnpr(b + n, b_len - n, "DOFF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_COMPLETE_B4 & flags) { /* 0x100 */
+ n += sg_scnpr(b + n, b_len - n, "NWTQ|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_SIGNAL & flags) { /* 0x200 */
+ n += sg_scnpr(b + n, b_len - n, "SIGNAL|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_IMMED & flags) { /* 0x400 */
+ n += sg_scnpr(b + n, b_len - n, "IMM|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_STOP_IF & flags) { /* 0x800 */
+ n += sg_scnpr(b + n, b_len - n, "STOPIF|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DEV_SCOPE & flags) { /* 0x1000 */
+ n += sg_scnpr(b + n, b_len - n, "DEV_SC|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_SHARE & flags) { /* 0x2000 */
+ n += sg_scnpr(b + n, b_len - n, "SHARE|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_DO_ON_OTHER & flags) { /* 0x4000 */
+ n += sg_scnpr(b + n, b_len - n, "DO_OTH|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_KEEP_SHARE & flags) { /* 0x8000 */
+ n += sg_scnpr(b + n, b_len - n, "KEEP_SH|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_NO_DXFER & flags) { /* 0x10000 */
+ n += sg_scnpr(b + n, b_len - n, "NDXFER|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_MULTIPLE_REQS & flags) { /* 0x20000 */
+ n += sg_scnpr(b + n, b_len - n, "MRQS|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_EVENTFD & flags) { /* 0x40000 */
+ n += sg_scnpr(b + n, b_len - n, "EVFD|");
+ if (n >= b_len)
+ goto fini;
+ }
+ if (SGV4_FLAG_ORDERED_SLV & flags) { /* 0x80000 */
+ n += sg_scnpr(b + n, b_len - n, "OSLV|");
+ if (n >= b_len)
+ goto fini;
+ }
+fini:
+ if (n < b_len) { /* trim trailing '\' */
+ if ('|' == b[n - 1])
+ b[n - 1] = '\0';
+ } else if ('|' == b[b_len - 1])
+ b[b_len - 1] = '\0';
+ return b;
+}
+
+static void
+v4hdr_out_lk(const char * leadin, const sg_io_v4 * h4p, int id)
+{
+ lock_guard<mutex> lk(strerr_mut);
+ char b[80];
+
+ if (leadin)
+ pr2serr("%s [id=%d]:\n", leadin, id);
+ if (('Q' != h4p->guard) || (0 != h4p->protocol) ||
+ (0 != h4p->subprotocol))
+ pr2serr(" <<<sg_io_v4 _NOT_ properly set>>>\n");
+ pr2serr(" pointers: cdb=%s sense=%s din=%p dout=%p\n",
+ (h4p->request ? "y" : "NULL"), (h4p->response ? "y" : "NULL"),
+ (void *)h4p->din_xferp, (void *)h4p->dout_xferp);
+ pr2serr(" lengths: cdb=%u sense=%u din=%u dout=%u\n",
+ h4p->request_len, h4p->max_response_len, h4p->din_xfer_len,
+ h4p->dout_xfer_len);
+ pr2serr(" flags=0x%x request_extra{pack_id}=%d\n",
+ h4p->flags, h4p->request_extra);
+ pr2serr(" flags set: %s\n", sg_flags_str(h4p->flags, sizeof(b), b));
+ pr2serr(" OUT:\n");
+ pr2serr(" response_len=%d driver/transport/device_status="
+ "0x%x/0x%x/0x%x\n", h4p->response_len, h4p->driver_status,
+ h4p->transport_status, h4p->device_status);
+ pr2serr(" info=0x%x din_resid=%u dout_resid=%u spare_out=%u "
+ "dur=%u\n",
+ h4p->info, h4p->din_resid, h4p->dout_resid, h4p->spare_out,
+ h4p->duration);
+}
+
+static void
+fetch_sg_version(void)
+{
+ FILE * fp;
+ char b[96];
+
+ have_sg_version = false;
+ sg_version = 0;
+ fp = fopen(PROC_SCSI_SG_VERSION, "r");
+ if (fp && fgets(b, sizeof(b) - 1, fp)) {
+ if (1 == sscanf(b, "%d", &sg_version))
+ have_sg_version = !!sg_version;
+ } else {
+ int j, k, l;
+
+ if (fp)
+ fclose(fp);
+ fp = fopen(SYS_SCSI_SG_VERSION, "r");
+ if (fp && fgets(b, sizeof(b) - 1, fp)) {
+ if (3 == sscanf(b, "%d.%d.%d", &j, &k, &l)) {
+ sg_version = (j * 10000) + (k * 100) + l;
+ have_sg_version = !!sg_version;
+ }
+ }
+ }
+ if (fp)
+ fclose(fp);
+}
+
+static void
+calc_duration_throughput(int contin)
+{
+ struct timeval end_tm, 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)gcoll.bs * (gcoll.dd_count - gcoll.out_rem_count.load());
+ pr2serr("time to transfer data %s %d.%06d secs",
+ (contin ? "so far" : "was"), (int)res_tm.tv_sec,
+ (int)res_tm.tv_usec);
+ if ((a > 0.00001) && (b > 511))
+ pr2serr(", %.2f MB/sec\n", b / (a * 1000000.0));
+ else
+ pr2serr("\n");
+}
+
+static void
+print_stats(const char * str)
+{
+ int64_t infull, outfull;
+
+ if (0 != gcoll.out_rem_count.load())
+ pr2serr(" remaining block count=%" PRId64 "\n",
+ gcoll.out_rem_count.load());
+ infull = gcoll.dd_count - gcoll.in_rem_count.load();
+ pr2serr("%s%" PRId64 "+%d records in\n", str,
+ infull, gcoll.in_partial.load());
+
+ outfull = gcoll.dd_count - gcoll.out_rem_count.load();
+ pr2serr("%s%" PRId64 "+%d records %s\n", str,
+ outfull, gcoll.out_partial.load(),
+ (gcoll.verify ? "verified" : "out"));
+}
+
+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);
+ pr2serr("Interrupted by signal,");
+ if (do_time > 0)
+ calc_duration_throughput(0);
+ print_stats("");
+ kill(getpid (), sig);
+}
+
+static void
+siginfo_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time > 0)
+ calc_duration_throughput(1);
+ print_stats(" ");
+}
+
+static void
+siginfo2_handler(int sig)
+{
+ if (sig) { ; } /* unused, dummy to suppress warning */
+ pr2serr("Progress report, continuing ...\n");
+ if (do_time > 0)
+ calc_duration_throughput(1);
+ print_stats(" ");
+}
+
+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);
+ }
+}
+
+#if 0 /* SG_LIB_ANDROID */
+static void
+thread_exit_handler(int sig)
+{
+ pthread_exit(0);
+}
+#endif
+
+/* Make safe_strerror() thread safe */
+static char *
+tsafe_strerror(int code, char * ebp)
+{
+ lock_guard<mutex> lk(strerr_mut);
+ char * cp;
+
+ cp = safe_strerror(code);
+ strncpy(ebp, cp, STRERR_BUFF_LEN);
+ ebp[STRERR_BUFF_LEN - 1] = '\0';
+ return ebp;
+}
+
+
+/* Following macro from D.R. Butenhof's POSIX threads book:
+ * ISBN 0-201-63392-2 . Changed __FILE__ to __func__ */
+#define err_exit(code,text) do { \
+ char strerr_buff[STRERR_BUFF_LEN]; \
+ pr2serr("%s at \"%s\":%d: %s\n", \
+ text, __func__, __LINE__, tsafe_strerror(code, strerr_buff)); \
+ exit(1); \
+ } while (0)
+
+
+static int
+dd_filetype(const char * filename, off_t & st_size)
+{
+ struct stat st;
+ size_t len = strlen(filename);
+
+ if ((1 == len) && ('.' == filename[0]))
+ return FT_DEV_NULL;
+ if (stat(filename, &st) < 0)
+ return FT_ERROR;
+ if (S_ISCHR(st.st_mode)) {
+ if ((MEM_MAJOR == major(st.st_rdev)) &&
+ (DEV_NULL_MINOR_NUM == minor(st.st_rdev)))
+ return FT_DEV_NULL;
+ if (RAW_MAJOR == major(st.st_rdev))
+ return FT_RAW;
+ if (SCSI_GENERIC_MAJOR == major(st.st_rdev))
+ return FT_SG;
+ if (SCSI_TAPE_MAJOR == major(st.st_rdev))
+ return FT_ST;
+ } else if (S_ISBLK(st.st_mode))
+ return FT_BLOCK;
+ else if (S_ISFIFO(st.st_mode))
+ return FT_FIFO;
+ st_size = st.st_size;
+ return FT_OTHER;
+}
+
+static void
+usage(int pg_num)
+{
+ if (pg_num > 3)
+ goto page4;
+ else if (pg_num > 2)
+ goto page3;
+ else if (pg_num > 1)
+ goto page2;
+
+ pr2serr("Usage: sg_mrq_dd [bs=BS] [count=COUNT] [ibs=BS] [if=IFILE]"
+ " [iflag=FLAGS]\n"
+ " [obs=BS] [of=OFILE] [oflag=FLAGS] "
+ "[seek=SEEK]\n"
+ " [skip=SKIP] [--help] [--version]\n\n");
+ pr2serr(" [bpt=BPT] [cdbsz=6|10|12|16] "
+ "[coe=0|1]\n"
+ " [deb=VERB] [dio=0|1]\n"
+ " [fua=0|1|2|3] [mrq=MRQ[,C]] "
+ "[of2=OFILE2]\n"
+ " [ofreg=OFREG] [sync=0|1] [thr=THR] "
+ "[time=0|1]\n"
+ " [verbose=VERB] [--dry-run] "
+ "\n"
+ " [--verbose] [--verify] [--version]\n\n"
+ " where the main options (shown in first group above) are:\n"
+ " bs must be device logical block size (default "
+ "512)\n"
+ " count number of blocks to copy (def: device size)\n"
+ " if file or device to read from (def: stdin)\n"
+ " iflag comma separated list from: [coe,dio,"
+ "direct,dpo,\n"
+ " dsync,excl,fua,masync,mmap,nodur,\n"
+ " null,order,qtail,serial,wq_excl]\n"
+ " mrq number of cmds placed in each sg call "
+ "(def: 16);\n"
+ " may have trailing ',C', to send bulk cdb_s\n"
+ " of file or device to write to (def: /dev/null "
+ "N.B. different\n"
+ " from dd it defaults to stdout). If 'of=.' "
+ "uses /dev/null\n"
+ " of2 second file or device to write to (def: "
+ "/dev/null)\n"
+ " oflag comma separated list from: [append,<<list from "
+ "iflag>>]\n"
+ " seek block position to start writing to OFILE\n"
+ " skip block position to start reading from IFILE\n"
+ " --help|-h output this usage message then exit\n"
+ " --prefetch|-p with verify: do pre-fetch first\n"
+ " --verify|-x do a verify (compare) operation [def: do a "
+ "copy]\n"
+ " --version|-V output version string then exit\n\n"
+ "Copy IFILE to OFILE, similar to dd command. This utility is "
+ "specialized for\nSCSI devices and uses multiple POSIX threads. "
+ "It expects one or both IFILE\nand OFILE to be sg devices. With "
+ "--verify option does a verify/compare\noperation instead of a "
+ "copy. This utility is Linux specific and uses the\nv4 sg "
+ "driver 'share' capability if available. Use '-hh', '-hhh' or "
+ "'-hhhh'\nfor more information.\n"
+ );
+ return;
+page2:
+ pr2serr("Syntax: sgh_dd [operands] [options]\n\n"
+ " where: operands have the form name=value and are pecular to "
+ "'dd'\n"
+ " style commands, and options start with one or "
+ "two hyphens;\n"
+ " the lesser used operands and option are:\n\n"
+ " bpt is blocks_per_transfer (default is 128)\n"
+ " cdbsz size of SCSI READ, WRITE or VERIFY cdb_s "
+ "(default is 10)\n"
+ " coe continue on error, 0->exit (def), "
+ "1->zero + continue\n"
+ " deb for debug, 0->none (def), > 0->varying degrees "
+ "of debug\n"
+ " dio is direct IO, 1->attempt, 0->indirect IO (def)\n"
+ " fua force unit access: 0->don't(def), 1->OFILE, "
+ "2->IFILE,\n"
+ " 3->OFILE+IFILE\n"
+ " ofreg OFREG is regular file or pipe to send what is "
+ "read from\n"
+ " IFILE in the first half of each shared element\n"
+ " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on OFILE "
+ "after copy\n"
+ " thr is number of threads, must be > 0, default 4, "
+ "max 1024\n"
+ " time 0->no timing, 1->calc throughput(def), "
+ "2->nanosec precision\n"
+ " verbose same as 'deb=VERB': increase verbosity\n"
+ " --dry-run|-d prepare but bypass copy/read\n"
+ " --verbose|-v increase verbosity of utility\n\n"
+ "Use '-hhh' or '-hhhh' for more information about flags.\n"
+ );
+ return;
+page3:
+ pr2serr("Syntax: sgh_dd [operands] [options]\n\n"
+ " where: 'iflag=<arg>' and 'oflag=<arg>' arguments are listed "
+ "below:\n\n"
+ " 00 use all zeros instead of if=IFILE (only in "
+ "iflags)\n"
+ " append append output to OFILE (assumes OFILE is "
+ "regular file)\n"
+ " coe continue of error (reading, fills with zeros)\n"
+ " dio sets the SG_FLAG_DIRECT_IO in sg requests\n"
+ " direct sets the O_DIRECT flag on open()\n"
+ " dpo sets the DPO (disable page out) in SCSI READs "
+ "and WRITEs\n"
+ " dsync sets the O_SYNC flag on open()\n"
+ " excl sets the O_EXCL flag on open()\n"
+ " ff use all 0xff bytes instead of if=IFILE (only in "
+ "iflags)\n"
+ " fua sets the FUA (force unit access) in SCSI READs "
+ "and WRITEs\n"
+ " masync set 'more async' flag on this sg device\n"
+ " mmap setup mmap IO on IFILE or OFILE\n"
+ " mmap,mmap when used twice, doesn't call munmap()\n"
+ " mrq_svb if mrq and sg->sg copy, do shared_variable_"
+ "blocking\n"
+ " nodur turns off command duration calculations\n"
+ " order require write ordering on sg->sg copy; only "
+ "for oflag\n"
+ " qhead queue new request at head of block queue\n"
+ " qtail queue new request at tail of block queue (def: "
+ "q at head)\n"
+ " random use random data instead of if=IFILE (only in "
+ "iflags)\n"
+ " serial serialize sg command execution (def: overlap)\n"
+ " wq_excl set SG_CTL_FLAGM_EXCL_WAITQ on this sg fd\n"
+ "\n"
+ "Copies IFILE to OFILE (and to OFILE2 if given). If IFILE and "
+ "OFILE are sg\ndevices 'shared' mode is selected. of2=OFILE2 "
+ "uses 'oflag=FLAGS'. When sharing, the data stays in a\nsingle "
+ "in-kernel buffer which is copied (or mmap-ed) to the user "
+ "space\nif the 'ofreg=OFREG' is given. Use '-hhhh' for more "
+ "information.\n"
+ );
+ return;
+page4:
+ pr2serr("pack_id:\n"
+ "These are ascending integers, starting at 1, associated with "
+ "each issued\nSCSI command. When both IFILE and OFILE are sg "
+ "devices, then the READ in\neach read-write pair is issued an "
+ "even pack_id and its WRITE pair is\ngiven the pack_id one "
+ "higher (i.e. an odd number). This enables a\n'cat '"
+ "/proc/scsi/sg/debug' user to see that progress is being "
+ "made.\n\n");
+ pr2serr("Debugging:\n"
+ "Apart from using one or more '--verbose' options which gets a "
+ "bit noisy\n'cat /proc/scsi/sg/debug' can give a good overview "
+ "of what is happening.\nThat does a sg driver object tree "
+ "traversal that does minimal locking\nto make sure that each "
+ "traversal is 'safe'. So it is important to note\nthe whole "
+ "tree is not locked. This means for fast devices the overall\n"
+ "tree state may change while the traversal is occurring. For "
+ "example,\nit has been observed that both the master and slave "
+ "sides of a request\nshare show they are in 'active' state "
+ "which should not be possible.\nIt occurs because the master "
+ "probably jumped out of active state and\nthe slave request "
+ "entered it while some other nodes were being printed.\n\n");
+ pr2serr("Busy state:\n"
+ "Busy state (abbreviated to 'bsy' in the /proc/scsi/sg/debug "
+ "output)\nis entered during request setup and completion. It "
+ "is intended to be\na temporary state. It should not block "
+ "but does sometimes (e.g. in\nblock_get_request()). Even so "
+ "that blockage should be short and if not\nthere is a "
+ "problem.\n\n");
+ pr2serr("--verify :\n"
+ "For comparing IFILE with OFILE. Does repeated sequences of: "
+ "READ(ifile)\nand uses data returned to send to VERIFY(ofile, "
+ "BYTCHK=1). So the OFILE\ndevice/disk is doing the actual "
+ "comparison. Stops on first miscompare.\n\n");
+ pr2serr("--prefetch :\n"
+ "Used with --verify option. Prepends a PRE-FETCH(ofile, IMMED) "
+ "to verify\nsequence. This should speed the trailing VERIFY by "
+ "making sure that\nthe data it needs for the comparison is "
+ "already in its cache.\n");
+ return;
+}
+
+
+bool
+scat_gath_list::empty() const
+{
+ return sgl.empty();
+}
+
+bool
+scat_gath_list::empty_or_00() const
+{
+ if (sgl.empty())
+ return true;
+ return ((sgl.size() == 1) && (sgl[0].lba == 0) && (sgl[0].num == 0));
+}
+
+int
+scat_gath_list::num_elems() const
+{
+ return sgl.size();
+}
+
+
+/* Read numbers (up to 64 bits in size) from command line (comma (or
+ * (single) space **) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. Assumed to be LBA (64 bit) and
+ * number_of_block (32 bit) pairs. ** Space on command line needs to
+ * be escaped, otherwise it is an operand/option separator. */
+bool
+scat_gath_list::load_from_cli(const char * cl_p, bool b_vb)
+{
+ bool split, full_pair;
+ int in_len, k, j;
+ const int max_nbs = MAX_SGL_NUM_VAL;
+ int64_t ll, large_num;
+ uint64_t prev_lba;
+ char * cp;
+ char * c2p;
+ const char * lcp;
+ struct scat_gath_elem sge;
+
+ if (NULL == cl_p) {
+ pr2serr("%s: bad arguments\n", __func__);
+ goto err_out;
+ }
+ lcp = cl_p;
+ in_len = strlen(cl_p);
+ if ('-' == cl_p[0]) { /* read from stdin */
+ pr2serr("%s: logic error: no stdin here\n", __func__);
+ goto err_out;
+ } else { /* list of numbers (default decimal) on command line */
+ k = strspn(cl_p, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+ if (in_len != k) {
+ if (b_vb)
+ pr2serr("%s: error at pos %d\n", __func__, k + 1);
+ goto err_out;
+ }
+ j = 0;
+ full_pair = true;
+ for (k = 0, split = false; ; ++k) {
+ if (split) {
+ /* splitting given elem with large number_of_blocks into
+ * multiple elems within array being built */
+ ++j;
+ sge.lba = prev_lba + (uint64_t)max_nbs;
+ if (large_num > max_nbs) {
+ sge.num = (uint32_t)max_nbs;
+ prev_lba = sge.lba;
+ large_num -= max_nbs;
+ sgl.push_back(sge);
+ } else {
+ sge.num = (uint32_t)large_num;
+ split = false;
+ if (b_vb)
+ pr2serr("%s: split large sg elem into %d element%s\n",
+ __func__, j, (j == 1 ? "" : "s"));
+ sgl.push_back(sge);
+ goto check_for_next;
+ }
+ continue;
+ }
+ full_pair = false;
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ sge.lba = (uint64_t)ll;
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp) {
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ }
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } else {
+ if (b_vb)
+ pr2serr("%s: error at pos %d\n", __func__,
+ (int)(lcp - cl_p + 1));
+ goto err_out;
+ }
+ ll = sg_get_llnum(lcp);
+ if (ll >= 0) {
+ full_pair = true;
+ if (ll > max_nbs) {
+ sge.num = (uint32_t)max_nbs;
+ prev_lba = sge.lba;
+ large_num = ll - max_nbs;
+ split = true;
+ j = 1;
+ continue;
+ }
+ sge.num = (uint32_t)ll;
+ } else { /* bad or negative number as number_of_blocks */
+ if (b_vb)
+ pr2serr("%s: bad number at pos %d\n", __func__,
+ (int)(lcp - cl_p + 1));
+ goto err_out;
+ }
+ sgl.push_back(sge);
+check_for_next:
+ cp = (char *)strchr(lcp, ',');
+ c2p = (char *)strchr(lcp, ' ');
+ if (NULL == cp) {
+ cp = c2p;
+ if (NULL == cp)
+ break;
+ }
+ if (c2p && (c2p < cp))
+ cp = c2p;
+ lcp = cp + 1;
+ } /* end of for loop over items in operand */
+ /* other than first pair, expect even number of items */
+ if ((k > 0) && (! full_pair)) {
+ if (b_vb)
+ pr2serr("%s: expected even number of items: "
+ "LBA0,NUM0,LBA1,NUM1...\n", __func__);
+ goto err_out;
+ }
+ }
+ return true;
+err_out:
+ if (0 == m_errno)
+ m_errno = SG_LIB_SYNTAX_ERROR;
+ return false;
+}
+
+bool
+scat_gath_list::file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
+ bool flexible, bool b_vb)
+{
+ bool bit0;
+ bool pre_addr1 = true;
+ bool pre_hex_seen = false;
+ int in_len, k, j, m, ind;
+ const int max_nbs = MAX_SGL_NUM_VAL;
+ int off = 0;
+ int64_t ll;
+ uint64_t ull, prev_lba;
+ char * lcp;
+ struct scat_gath_elem sge;
+ char line[1024];
+
+ for (j = 0 ; ; ++j) {
+ if (NULL == fgets(line, sizeof(line), fp))
+ break;
+ // could improve with carry_over logic if sizeof(line) too small
+ in_len = strlen(line);
+ if (in_len > 0) {
+ if ('\n' == line[in_len - 1]) {
+ --in_len;
+ line[in_len] = '\0';
+ } else {
+ m_errno = SG_LIB_SYNTAX_ERROR;
+ if (b_vb)
+ pr2serr("%s: %s: line too long, max %d bytes\n",
+ __func__, fnp, (int)(sizeof(line) - 1));
+ goto err_out;
+ }
+ }
+ if (in_len < 1)
+ continue;
+ lcp = line;
+ m = strspn(lcp, " \t");
+ if (m == in_len)
+ continue;
+ lcp += m;
+ in_len -= m;
+ if ('#' == *lcp)
+ continue;
+ if (pre_addr1 || pre_hex_seen) {
+ /* Accept lines with leading 'HEX' and ignore as long as there
+ * is one _before_ any LBA,NUM lines in the file. This allows
+ * HEX marked sgls to be concaternated together. */
+ if (('H' == toupper(lcp[0])) && ('E' == toupper(lcp[1])) &&
+ ('X' == toupper(lcp[2]))) {
+ pre_hex_seen = true;
+ if (def_hex)
+ continue; /* bypass 'HEX' marker line if expecting hex */
+ else {
+ if (flexible) {
+ def_hex = true; /* okay, switch to hex parse */
+ continue;
+ } else {
+ pr2serr("%s: %s: 'hex' string detected on line %d, "
+ "expecting decimal\n", __func__, fnp, j + 1);
+ m_errno = EINVAL;
+ goto err_out;
+ }
+ }
+ }
+ }
+ k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXbBdDiIkKmMgGtTpP, \t");
+ if ((k < in_len) && ('#' != lcp[k])) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: syntax error at line %d, pos %d\n",
+ __func__, fnp, j + 1, m + k + 1);
+ goto err_out;
+ }
+ for (k = 0; k < 256; ++k) {
+ /* limit parseable items on one line to 256 */
+ if (def_hex) { /* don't accept negatives or multipliers */
+ if (1 == sscanf(lcp, "%" SCNx64, &ull))
+ ll = (int64_t)ull;
+ else
+ ll = -1; /* use (2**64 - 1) as error flag */
+ } else
+ ll = sg_get_llnum(lcp);
+ if (-1 != ll) {
+ ind = ((off + k) >> 1);
+ bit0 = !! (0x1 & (off + k));
+ if (ind >= SG_SGL_MAX_ELEMENTS) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: array length exceeded\n", __func__,
+ fnp);
+ goto err_out;
+ }
+ if (bit0) { /* bit0 set when decoding a NUM */
+ if (ll < 0) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: bad number in line %d, at pos "
+ "%d\n", __func__, fnp, j + 1,
+ (int)(lcp - line + 1));
+ goto err_out;
+ }
+ if (ll > max_nbs) {
+ int h = 1;
+
+ /* split up this elem into multiple, smaller elems */
+ do {
+ sge.num = (uint32_t)max_nbs;
+ prev_lba = sge.lba;
+ sgl.push_back(sge);
+ sge.lba = prev_lba + (uint64_t)max_nbs;
+ ++h;
+ off += 2;
+ ll -= max_nbs;
+ } while (ll > max_nbs);
+ if (b_vb)
+ pr2serr("%s: split large sg elem into %d "
+ "elements\n", __func__, h);
+ }
+ sge.num = (uint32_t)ll;
+ sgl.push_back(sge);
+ } else { /* bit0 clear when decoding a LBA */
+ if (pre_addr1)
+ pre_addr1 = false;
+ sge.lba = (uint64_t)ll;
+ }
+ } else { /* failed to decode number on line */
+ if ('#' == *lcp) { /* numbers before #, rest of line comment */
+ --k;
+ break; /* goes to next line */
+ }
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: error in line %d, at pos %d\n",
+ __func__, fnp, j + 1, (int)(lcp - line + 1));
+ goto err_out;
+ }
+ lcp = strpbrk(lcp, " ,\t#");
+ if ((NULL == lcp) || ('#' == *lcp))
+ break;
+ lcp += strspn(lcp, " ,\t");
+ if ('\0' == *lcp)
+ break;
+ } /* <<< end of for(k < 256) loop */
+ off += (k + 1);
+ } /* <<< end of for loop, one iteration per line */
+ /* allow one items, but not higher odd number of items */
+ if ((off > 1) && (0x1 & off)) {
+ m_errno = EINVAL;
+ if (b_vb)
+ pr2serr("%s: %s: expect even number of items: "
+ "LBA0,NUM0,LBA1,NUM1...\n", __func__, fnp);
+ goto err_out;
+ }
+ clearerr(fp); /* even EOF on first pass needs this before rescan */
+ return true;
+err_out:
+ clearerr(fp);
+ return false;
+}
+
+/* Read numbers from filename (or stdin), line by line (comma (or (single)
+ * space) separated list); places starting_LBA,number_of_block pairs in an
+ * array of scat_gath_elem elements pointed to by the returned value. If
+ * this fails NULL is returned and an error number is written to errp (if it
+ * is non-NULL). Assumed decimal (and may have suffix multipliers) when
+ * def_hex==false; if a number is prefixed by '0x', '0X' or contains trailing
+ * 'h' or 'H' that denotes a hex number. When def_hex==true all numbers are
+ * assumed to be hex (ignored '0x' prefixes and 'h' suffixes) and multiplers
+ * are not permitted. Heap allocates an array just big enough to hold all
+ * elements if the file is countable. Pipes and stdin are not considered
+ * countable. In the non-countable case an array of MAX_FIXED_SGL_ELEMS
+ * elements is pre-allocated; if it is exceeded sg_convert_errno(EDOM) is
+ * placed in *errp (if it is non-NULL). One of the first actions is to write
+ * 0 to *errp (if it is non-NULL) so the caller does not need to zero it
+ * before calling. */
+bool
+scat_gath_list::load_from_file(const char * file_name, bool def_hex,
+ bool flexible, bool b_vb)
+{
+ bool have_stdin;
+ bool have_err = false;
+ FILE * fp;
+ const char * fnp;
+
+ have_stdin = ((1 == strlen(file_name)) && ('-' == file_name[0]));
+ if (have_stdin) {
+ fp = stdin;
+ fnp = "<stdin>";
+ } else {
+ fnp = file_name;
+ fp = fopen(fnp, "r");
+ if (NULL == fp) {
+ m_errno = errno;
+ if (b_vb)
+ pr2serr("%s: opening %s: %s\n", __func__, fnp,
+ safe_strerror(m_errno));
+ return false;
+ }
+ }
+ if (! file2sgl_helper(fp, fnp, def_hex, flexible, b_vb))
+ have_err = true;
+ if (! have_stdin)
+ fclose(fp);
+ return have_err ? false : true;
+}
+
+const char *
+scat_gath_list::linearity_as_str() const
+{
+ switch (linearity) {
+ case SGL_LINEAR:
+ return "linear";
+ case SGL_MONOTONIC:
+ return "monotonic";
+ case SGL_MONO_OVERLAP:
+ return "monotonic, overlapping";
+ case SGL_NON_MONOTONIC:
+ return "non-monotonic";
+ default:
+ return "unknown";
+ }
+}
+
+void
+scat_gath_list::set_weaker_linearity(enum sgl_linearity_e lin)
+{
+ int i_lin = (int)lin;
+
+ if (i_lin > (int)linearity)
+ linearity = lin;
+}
+
+/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
+ * output. */
+void
+scat_gath_list::dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
+ bool show_sgl, bool lock) const
+{
+ int k;
+ if (lock)
+ strerr_mut.lock();
+ int num = sgl.size();
+ const char * caller = id_str ? id_str : "unknown";
+ FILE * fp = to_stdout ? stdout : stderr;
+
+ if (! skip_meta) {
+ fprintf(fp, "%s: elems=%d, sgl %spresent, linearity=%s\n",
+ caller, num, (sgl.empty() ? "not " : ""),
+ linearity_as_str());
+ fprintf(fp, " sum=%" PRId64 ", sum_hard=%s lowest=0x%" PRIx64
+ ", high_lba_p1=", sum, (sum_hard ? "true" : "false"),
+ lowest_lba);
+ fprintf(fp, "0x%" PRIx64 "\n", high_lba_p1);
+ }
+ fprintf(fp, " >> %s scatter gather list (%d element%s):\n", caller, num,
+ (num == 1 ? "" : "s"));
+ if (show_sgl) {
+ for (k = 0; k < num; ++k) {
+ const struct scat_gath_elem & sge = sgl[k];
+
+ fprintf(fp, " lba: 0x%" PRIx64 ", number: 0x%" PRIx32,
+ sge.lba, sge.num);
+ if (sge.lba > 0)
+ fprintf(fp, " [next lba: 0x%" PRIx64 "]", sge.lba + sge.num);
+ fprintf(fp, "\n");
+ }
+ }
+ if (lock)
+ strerr_mut.unlock();
+}
+
+/* Assumes sgl array (vector) is setup. The other fields in this object are
+ * set by analyzing sgl in a single pass. The fields that are set are:
+ * fragmented, lowest_lba, high_lba_p1, monotonic, overlapping, sum and
+ * sum_hard. Degenerate elements (i.e. those with 0 blocks) are ignored apart
+ * from when one is last which makes sum_hard false and its LBA becomes
+ * high_lba_p1 if it is the highest in the list. An empty sgl is equivalent
+ * to a 1 element list with [0, 0], so sum_hard==false, monit==true,
+ * fragmented==false and overlapping==false . id_str may be NULL, present
+ * to enhance verbose output. */
+void
+scat_gath_list::sum_scan(const char * id_str, bool show_sgl, bool b_vb)
+{
+ bool degen = false;
+ bool first = true;
+ bool regular = true; /* no overlapping segments detected */
+ int k;
+ int elems = sgl.size();
+ uint32_t prev_num, t_num;
+ uint64_t prev_lba, t_lba, low, high, end;
+
+ sum = 0;
+ for (k = 0, low = 0, high = 0; k < elems; ++k) {
+ const struct scat_gath_elem & sge = sgl[k];
+
+ degen = false;
+ t_num = sge.num;
+ if (0 == t_num) {
+ degen = true;
+ if (! first)
+ continue; /* ignore degen element that not first */
+ }
+ if (first) {
+ low = sge.lba;
+ sum = t_num;
+ high = sge.lba + sge.num;
+ first = false;
+ } else {
+ t_lba = sge.lba;
+ if ((prev_lba + prev_num) != t_lba)
+ set_weaker_linearity(SGL_MONOTONIC);
+ sum += t_num;
+ end = t_lba + t_num;
+ if (end > high)
+ high = end; /* high is one plus highest LBA */
+ if (prev_lba < t_lba)
+ ;
+ else if (prev_lba == t_lba) {
+ if (prev_num > 0) {
+ set_weaker_linearity(SGL_MONO_OVERLAP);
+ break;
+ }
+ } else {
+ low = t_lba;
+ set_weaker_linearity(SGL_NON_MONOTONIC);
+ break;
+ }
+ if (regular) {
+ if ((prev_lba + prev_num) > t_lba)
+ regular = false;
+ }
+ }
+ prev_lba = sge.lba;
+ prev_num = sge.num;
+ } /* end of for loop while still elements and monot true */
+
+ if (k < elems) { /* only here if above breaks are taken */
+ prev_lba = t_lba;
+ ++k;
+ for ( ; k < elems; ++k) {
+ const struct scat_gath_elem & sge = sgl[k];
+
+ degen = false;
+ t_lba = sge.lba;
+ t_num = sge.num;
+ if (0 == t_num) {
+ degen = true;
+ continue;
+ }
+ sum += t_num;
+ end = t_lba + t_num;
+ if (end > high)
+ high = end;
+ if (prev_lba > t_lba) {
+ if (t_lba < low)
+ low = t_lba;
+ }
+ prev_lba = t_lba;
+ }
+ } else
+ if (! regular)
+ set_weaker_linearity(SGL_MONO_OVERLAP);
+
+ lowest_lba = low;
+ if (degen && (elems > 0)) { /* last element always impacts high_lba_p1 */
+ t_lba = sgl[elems - 1].lba;
+ high_lba_p1 = (t_lba > high) ? t_lba : high;
+ } else
+ high_lba_p1 = high;
+ sum_hard = (elems > 0) ? ! degen : false;
+ if (b_vb)
+ dbg_print(false, id_str, false, show_sgl);
+}
+
+/* Usually will append (or add to start if empty) sge unless 'extra_blks'
+ * exceeds MAX_SGL_NUM_VAL. In that case multiple sge_s are added with
+ * sge.num = MAX_SGL_NUM_VAL or less (for final sge) until extra_blks is
+ * exhausted. Returns new size of scatter gather list. */
+int
+scat_gath_list::append_1or(int64_t extra_blks, int64_t start_lba)
+{
+ int o_num = sgl.size();
+ const int max_nbs = MAX_SGL_NUM_VAL;
+ int64_t cnt = 0;
+ struct scat_gath_elem sge;
+
+ if ((extra_blks <= 0) || (start_lba < 0))
+ return o_num; /* nothing to do */
+ if ((o_num > 0) && (! sum_hard)) {
+ sge = sgl[o_num - 1]; /* assume sge.num==0 */
+ if (sge.lba == (uint64_t)start_lba) {
+ if (extra_blks <= max_nbs)
+ sge.num = extra_blks;
+ else
+ sge.num = max_nbs;
+ sgl[o_num - 1] = sge;
+ cnt = sge.num;
+ sum += cnt;
+ sum_hard = true;
+ if (cnt <= extra_blks) {
+ high_lba_p1 = sge.lba + cnt;
+ return o_num;
+ }
+ }
+ } else if (0 == o_num)
+ lowest_lba = start_lba;
+
+ for ( ; cnt < extra_blks; cnt += max_nbs) {
+ sge.lba = start_lba + cnt;
+ if ((extra_blks - cnt) <= max_nbs)
+ sge.num = extra_blks - cnt;
+ else
+ sge.num = max_nbs;
+ sgl.push_back(sge);
+ sum += sge.num;
+ } /* always loops at least once */
+ sum_hard = true;
+ high_lba_p1 = sge.lba + sge.num;
+ return sgl.size();
+}
+
+int
+scat_gath_list::append_1or(int64_t extra_blks)
+{
+ int o_num = sgl.size();
+ if (o_num < 1)
+ return append_1or(extra_blks, 0);
+
+ struct scat_gath_elem sge = sgl[o_num - 1];
+ return append_1or(extra_blks, sge.lba + sge.num);
+}
+
+bool
+sgls_eq_off(const scat_gath_list & left, int l_e_ind, int l_blk_off,
+ const scat_gath_list & right, int r_e_ind, int r_blk_off,
+ bool allow_partial)
+{
+ int lrem, rrem;
+ int lelems = left.sgl.size();
+ int relems = right.sgl.size();
+
+ while ((l_e_ind < lelems) && (r_e_ind < relems)) {
+ if ((left.sgl[l_e_ind].lba + l_blk_off) !=
+ (right.sgl[r_e_ind].lba + r_blk_off))
+ return false;
+ lrem = left.sgl[l_e_ind].num - l_blk_off;
+ rrem = right.sgl[r_e_ind].num - r_blk_off;
+ if (lrem == rrem) {
+ ++l_e_ind;
+ l_blk_off = 0;
+ ++r_e_ind;
+ r_blk_off = 0;
+ } else if (lrem < rrem) {
+ ++l_e_ind;
+ l_blk_off = 0;
+ r_blk_off += lrem;
+ } else {
+ ++r_e_ind;
+ r_blk_off = 0;
+ l_blk_off += rrem;
+ }
+ }
+ if ((l_e_ind >= lelems) && (r_e_ind >= relems))
+ return true;
+ return allow_partial;
+}
+
+/* If bad arguments returns -1, otherwise returns the lowest LBA in *sglp .
+ * If no elements considered returns 0. If ignore_degen is true than
+ * ignores all elements with sge.num zero unless always_last is also
+ * true in which case the last element is always considered. */
+int64_t
+scat_gath_list::get_lowest_lba(bool ignore_degen, bool always_last) const
+{
+ int k;
+ const int num_elems = sgl.size();
+ bool some = (num_elems > 0);
+ int64_t res = INT64_MAX;
+
+ for (k = 0; k < num_elems; ++k) {
+ if ((0 == sgl[k].num) && ignore_degen)
+ continue;
+ if ((int64_t)sgl[k].lba < res)
+ res = sgl[k].lba;
+ }
+ if (always_last && some) {
+ if ((int64_t)sgl[k - 1].lba < res)
+ res = sgl[k - 1].lba;
+ }
+ return (INT64_MAX == res) ? 0 : res;
+}
+
+/* Returns >= 0 if sgl can be simplified to a single LBA. So an empty sgl
+ * will return 0; a one element sgl will return its LBA. A multiple element
+ * sgl only returns the first element's LBA (that is not degenerate) if the
+ * sgl is monotonic and not fragmented. In the extreme case takes last
+ * element's LBA if all prior elements are degenerate. Else returns -1 .
+ * Assumes sgl_sum_scan() has been called. */
+int64_t
+scat_gath_list::get_low_lba_from_linear() const
+{
+ const int num_elems = sgl.size();
+ int k;
+
+ if (num_elems <= 1)
+ return (1 == num_elems) ? sgl[0].lba : 0;
+ else {
+ if (linearity == SGL_LINEAR) {
+ for (k = 0; k < (num_elems - 1); ++k) {
+ if (sgl[k].num > 0)
+ return sgl[k].lba;
+ }
+ /* take last element's LBA if all earlier are degenerate */
+ return sgl[k].lba;
+ } else
+ return -1;
+ }
+}
+
+bool
+scat_gath_list::is_pipe_suitable() const
+{
+ return (lowest_lba == 0) && (linearity == SGL_LINEAR);
+}
+
+scat_gath_iter::scat_gath_iter(const scat_gath_list & parent)
+ : sglist(parent), it_el_ind(0), it_blk_off(0), blk_idx(0)
+{
+ int elems = sglist.num_elems();
+
+ if (elems > 0)
+ extend_last = (0 == sglist.sgl[elems - 1].num);
+}
+
+bool
+scat_gath_iter::set_by_blk_idx(int64_t _blk_idx)
+{
+ bool first;
+ int k;
+ const int elems = sglist.sgl.size();
+ const int last_ind = elems - 1;
+ uint32_t num;
+ int64_t bc = _blk_idx;
+
+ if (bc < 0)
+ return false;
+
+ if (bc == blk_idx)
+ return true;
+ else if (bc > blk_idx) {
+ k = it_el_ind;
+ bc -= blk_idx;
+ } else
+ k = 0;
+ for (first = true; k < elems; ++k, first = false) {
+ num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
+ sglist.sgl[k].num;
+ if (first) {
+ if ((int64_t)(num - it_blk_off) < bc)
+ bc -= (num - it_blk_off);
+ else {
+ it_blk_off = bc + it_blk_off;
+ break;
+ }
+ } else {
+ if ((int64_t)num < bc)
+ bc -= num;
+ else {
+ it_blk_off = (uint32_t)bc;
+ break;
+ }
+ }
+ }
+ it_el_ind = k;
+ blk_idx = _blk_idx;
+
+ if (k < elems)
+ return true;
+ else if ((k == elems) && (0 == it_blk_off))
+ return true; /* EOL */
+ else
+ return false;
+}
+
+/* Given a blk_count, the iterator (*iter_p) is moved toward the EOL.
+ * Returns true unless blk_count takes iterator two or more past the last
+ * element. So if blk_count takes the iterator to the EOL, this function
+ * returns true. Takes into account iterator's extend_last flag. */
+bool
+scat_gath_iter::add_blks(uint64_t blk_count)
+{
+ bool first;
+ int k;
+ const int elems = sglist.sgl.size();
+ const int last_ind = elems - 1;
+ uint32_t num;
+ uint64_t bc = blk_count;
+
+ if (0 == bc)
+ return true;
+ for (first = true, k = it_el_ind; k < elems; ++k, first = false) {
+ num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
+ sglist.sgl[k].num;
+ if (first) {
+ if ((uint64_t)(num - it_blk_off) < bc)
+ bc -= (num - it_blk_off);
+ else {
+ it_blk_off = bc + it_blk_off;
+ break;
+ }
+ } else {
+ if ((uint64_t)num < bc)
+ bc -= num;
+ else {
+ it_blk_off = (uint32_t)bc;
+ break;
+ }
+ }
+ }
+ it_el_ind = k;
+ blk_idx += blk_count;
+
+ if (k < elems)
+ return true;
+ else if ((k == elems) && (0 == it_blk_off))
+ return true; /* EOL */
+ else
+ return false;
+}
+
+/* Move the iterator from its current position (which may be to EOL) towards
+ * the start of the sgl (i.e. backwards) for blk_count blocks. Returns true
+ * if iterator is valid after the move, else returns false. N.B. if false is
+ * returned, then the iterator is invalid and may need to set it to a valid
+ * value. */
+bool
+scat_gath_iter::sub_blks(uint64_t blk_count)
+{
+ bool first;
+ int k = it_el_ind;
+ uint64_t bc = 0;
+ const uint64_t orig_blk_count = blk_count;
+
+ if (0 == blk_count)
+ return true;
+ for (first = true; k >= 0; --k) {
+ if (first) {
+ if (blk_count > (uint64_t)it_blk_off)
+ blk_count -= it_blk_off;
+ else {
+ it_blk_off -= blk_count;
+ break;
+ }
+ first = false;
+ } else {
+ uint32_t off = sglist.sgl[k].num;
+
+ bc = blk_count;
+ if (bc > (uint64_t)off)
+ blk_count -= off;
+ else {
+ bc = off - bc;
+ break;
+ }
+ }
+ }
+ if (k < 0) {
+ blk_idx = 0;
+ return false; /* bad situation */
+ }
+ if ((int64_t)orig_blk_count <= blk_idx)
+ blk_idx -= orig_blk_count;
+ else
+ blk_idx = 0;
+ it_el_ind = k;
+ if (! first)
+ it_blk_off = (uint32_t)bc;
+ return true;
+}
+
+/* Returns LBA referred to by iterator if valid or returns SG_LBA_INVALID
+ * (-1) if at end or invalid. */
+int64_t
+scat_gath_iter::current_lba() const
+{
+ const int elems = sglist.sgl.size();
+ int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
+
+ if (it_el_ind < elems) {
+ struct scat_gath_elem sge = sglist.sgl[it_el_ind];
+
+ if ((uint32_t)it_blk_off < sge.num)
+ return sge.lba + it_blk_off;
+ else if (((uint32_t)it_blk_off == sge.num) &&
+ ((it_el_ind + 1) < elems)) {
+ class scat_gath_iter iter(*this);
+
+ ++iter.it_el_ind;
+ iter.it_blk_off = 0;
+ /* worst case recursion will stop at end of sgl */
+ return iter.current_lba();
+ }
+ }
+ return res;
+}
+
+int64_t
+scat_gath_iter::current_lba_rem_num(int & rem_num) const
+{
+ const int elems = sglist.sgl.size();
+ int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
+
+ if (it_el_ind < elems) {
+ struct scat_gath_elem sge = sglist.sgl[it_el_ind];
+
+ if ((uint32_t)it_blk_off < sge.num) {
+ rem_num = sge.num - it_blk_off;
+ return sge.lba + it_blk_off;
+ } else if (((uint32_t)it_blk_off == sge.num) &&
+ ((it_el_ind + 1) < elems)) {
+ class scat_gath_iter iter(*this);
+
+ ++iter.it_el_ind;
+ iter.it_blk_off = 0;
+ /* worst case recursion will stop at end of sgl */
+ return iter.current_lba_rem_num(rem_num);
+ }
+ }
+ rem_num = -1;
+ return res;
+}
+
+struct scat_gath_elem
+scat_gath_iter::current_elem() const
+{
+ const int elems = sglist.sgl.size();
+ struct scat_gath_elem sge;
+
+ sge.make_bad();
+ if (it_el_ind < elems)
+ return sglist.sgl[it_el_ind];
+ return sge;
+}
+
+/* Returns true of no sgl or sgl is at the end [elems, 0], otherwise it
+ * returns false. */
+bool
+scat_gath_iter::at_end() const
+{
+ const int elems = sglist.sgl.size();
+
+ return ((0 == elems) || ((it_el_ind == elems) && (0 == it_blk_off)));
+}
+
+/* Returns true if associated iterator is monotonic (increasing) and not
+ * fragmented. Empty sgl and single element degenerate considered linear.
+ * Assumes sgl_sum_scan() has been called on sgl. */
+bool
+scat_gath_iter::is_sgl_linear() const
+{
+ return sglist.linearity == SGL_LINEAR;
+}
+
+int
+scat_gath_iter::linear_for_n_blks(int max_n) const
+{
+ int k, rem;
+ const int elems = sglist.sgl.size();
+ uint64_t prev_lba;
+ struct scat_gath_elem sge;
+
+ if (at_end() || (max_n <= 0))
+ return 0;
+ sge = sglist.sgl[it_el_ind];
+ rem = (int)sge.num - it_blk_off;
+ if (max_n <= rem)
+ return max_n;
+ prev_lba = sge.lba + sge.num;
+ for (k = it_el_ind + 1; k < elems; ++k) {
+ sge = sglist.sgl[k];
+ if (sge.lba != prev_lba)
+ return rem;
+ rem += sge.num;
+ if (max_n <= rem)
+ return max_n;
+ prev_lba = sge.lba + sge.num;
+ }
+ return rem;
+}
+
+/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
+ * output. */
+void
+scat_gath_iter::dbg_print(const char * id_str, bool to_stdout,
+ int verbose) const
+{
+ const char * caller = id_str ? id_str : "unknown";
+ FILE * fp = to_stdout ? stdout : stderr;
+ lock_guard<mutex> lk(strerr_mut);
+
+ fprintf(fp, "%s: it_el_ind=%d, it_blk_off=%d, blk_idx=%" PRId64 "\n",
+ caller, it_el_ind, it_blk_off, blk_idx);
+ fprintf(fp, " extend_last=%d\n", extend_last);
+ if (verbose)
+ sglist.dbg_print(false, " iterator's", to_stdout, verbose > 1, false);
+}
+
+/* Calculates difference between iterators, logically: res <-- lhs - rhs
+ * Checks that lhsp and rhsp have same underlying sgl, if not returns
+ * INT_MIN. Assumes iterators close enough for result to lie in range
+ * from (-INT_MAX) to INT_MAX (inclusive). */
+int
+diff_between_iters(const struct scat_gath_iter & left,
+ const struct scat_gath_iter & right)
+{
+ int res, k, r_e_ind, l_e_ind;
+
+ if (&left.sglist != &right.sglist) {
+ pr2serr("%s: bad args\n", __func__);
+ return INT_MIN;
+ }
+ r_e_ind = right.it_el_ind;
+ l_e_ind = left.it_el_ind;
+ if (l_e_ind < r_e_ind) { /* so difference will be negative */
+ res = diff_between_iters(right, left); /* cheat */
+ if (INT_MIN == res)
+ return res;
+ return -res;
+ } else if (l_e_ind == r_e_ind)
+ return (int)left.it_blk_off - (int)right.it_blk_off;
+ /* (l_e_ind > r_e_ind) so (lhs > rhs) */
+ res = (int)right.sglist.sgl[r_e_ind].num - right.it_blk_off;
+ for (k = 1; (r_e_ind + k) < l_e_ind; ++k) {
+ // pr2serr("%s: k=%d, res=%d, num=%d\n", __func__, k, res,
+ // (int)right.sglist.sgl[r_e_ind + k].num);
+ res += (int)right.sglist.sgl[r_e_ind + k].num;
+ }
+ res += left.it_blk_off;
+ // pr2serr("%s: at exit res=%d\n", __func__, res);
+ return res;
+}
+
+/* Compares from the current iterator positions of left and left until
+ * the shorter list is exhausted. Returns false on the first inequality.
+ * If no inequality and both remaining lists are same length then returns
+ * true. If no inequality but remaining lists differ in length then returns
+ * allow_partial. */
+bool
+sgls_eq_from_iters(const struct scat_gath_iter & left,
+ const struct scat_gath_iter & right,
+ bool allow_partial)
+{
+ return sgls_eq_off(left.sglist, left.it_el_ind, left.it_blk_off,
+ right.sglist, right.it_el_ind, right.it_blk_off,
+ allow_partial);
+}
+
+get_next_res
+global_collection::get_next(int desired_num_blks)
+{
+ int64_t expected, desired;
+
+ if (desired_num_blks <= 0) {
+ if (desired_num_blks < 0) {
+ if (next_count_pos.load() >= 0) /* flag error detection */
+ next_count_pos.store(desired_num_blks);
+ }
+ return make_pair(next_count_pos.load(), 0);
+ }
+
+ expected = next_count_pos.load();
+ do { /* allowed to race with other threads */
+ if (expected < 0)
+ return make_pair(0, (int)expected);
+ else if (expected >= dd_count)
+ return make_pair(expected, 0); /* clean finish */
+ desired = expected + desired_num_blks;
+ if (desired > dd_count)
+ desired = dd_count;
+ } while (! next_count_pos.compare_exchange_strong(expected, desired));
+ return make_pair(expected, desired - expected);
+}
+
+#if 0
+/* Returns the number of times either 'ch1' or 'ch2' is found in
+ * string 's' given the string's length. */
+static int
+num_either_ch_in_str(const char * s, int slen, int ch1, int ch2)
+{
+ int k;
+ int res = 0;
+
+ while (--slen >= 0) {
+ k = s[slen];
+ if ((ch1 == k) || (ch2 == k))
+ ++res;
+ }
+ return res;
+}
+#endif
+
+/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */
+static int
+scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+ int res;
+ uint8_t rcBuff[RCAP16_REPLY_LEN];
+
+ res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, false, 0);
+ if (0 != res)
+ return res;
+
+ if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+ (0xff == rcBuff[3])) {
+
+ res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, false,
+ 0);
+ if (0 != res)
+ return res;
+ *num_sect = sg_get_unaligned_be64(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 8);
+ } else {
+ /* take care not to sign extend values > 0x7fffffff */
+ *num_sect = (int64_t)sg_get_unaligned_be32(rcBuff + 0) + 1;
+ *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
+ }
+ return 0;
+}
+
+/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */
+/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */
+static int
+read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz)
+{
+#ifdef BLKSSZGET
+ if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) {
+ perror("BLKSSZGET ioctl error");
+ return -1;
+ } else {
+ #ifdef BLKGETSIZE64
+ uint64_t ull;
+
+ if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) {
+
+ perror("BLKGETSIZE64 ioctl error");
+ return -1;
+ }
+ *num_sect = ((int64_t)ull / (int64_t)*sect_sz);
+ #else
+ unsigned long ul;
+
+ if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) {
+ perror("BLKGETSIZE ioctl error");
+ return -1;
+ }
+ *num_sect = (int64_t)ul;
+ #endif
+ }
+ return 0;
+#else
+ *num_sect = 0;
+ *sect_sz = 0;
+ return -1;
+#endif
+}
+
+static void
+sig_listen_thread(struct global_collection * clp)
+{
+ int sig_number;
+
+ while (1) {
+ sigwait(&signal_set, &sig_number);
+ if (shutting_down)
+ break;
+ if (SIGINT == sig_number) {
+ pr2serr_lk("%sinterrupted by SIGINT\n", my_name);
+ clp->next_count_pos.store(-1);
+ }
+ }
+}
+
+static bool
+sg_share_prepare(int slave_wr_fd, int master_rd_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_SHARE_FD;
+ seip->share_fd = master_rd_fd;
+ if (ioctl(slave_wr_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(shared_fd=%d), failed "
+ "errno=%d %s\n", id, master_rd_fd, errno,
+ strerror(errno));
+ return false;
+ }
+ if (vb_b)
+ pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(shared_fd)) ok, master_fd=%d, "
+ "slave_fd=%d\n", __func__, id, master_rd_fd, slave_wr_fd);
+ return true;
+}
+
+static void
+sg_take_snap(int sg_fd, int id, bool vb_b)
+{
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_SNAP_DEV;
+ seip->ctl_flags &= SG_CTL_FLAGM_SNAP_DEV; /* 0 --> don't append */
+ if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(SNAP_DEV), failed errno=%d %s\n",
+ id, errno, strerror(errno));
+ return;
+ }
+ if (vb_b)
+ pr2serr_lk("tid=%d: ioctl(SNAP_DEV) ok\n", id);
+}
+
+static inline uint8_t *
+get_buffp(Rq_elem * rep)
+{
+ return rep->buffp;
+}
+
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+/* Each thread's "main" function */
+static void
+read_write_thread(struct global_collection * clp, int id, bool singleton)
+{
+ Rq_elem rel;
+ Rq_elem * rep = &rel;
+ int n, sz, fd, vb, err, seg_blks;
+ int res = 0;
+ int num_sg = 0;
+ // volatile bool stop_after_write = false;
+ bool own_infd = false;
+ bool in_is_sg, in_mmap, out_is_sg, out_mmap;
+ bool own_outfd = false;
+ bool own_out2fd = false;
+ bool only_one_sg = false;
+ // bool share_and_ofreg;
+ class scat_gath_iter i_sg_it(clp->i_sgl);
+ class scat_gath_iter o_sg_it(clp->o_sgl);
+ vector<cdb_arr_t> a_cdb;
+ vector<struct sg_io_v4> a_v4;
+ // mrq_arr_t deferred_arr; /* MRQ deferred array (vector) */
+
+ vb = clp->verbose;
+ sz = clp->mrq_num * clp->bpt * clp->bs;
+ in_is_sg = (FT_SG == clp->in_type);
+ in_mmap = (in_is_sg && (clp->in_flags.mmap > 0));
+ out_is_sg = (FT_SG == clp->out_type);
+ out_mmap = (out_is_sg && (clp->out_flags.mmap > 0));
+ memset(rep, 0, sizeof(Rq_elem));
+ rep->clp = clp;
+ rep->id = id;
+
+ if (in_is_sg && out_is_sg)
+ rep->both_sg = true;
+ else if (in_is_sg || out_is_sg) {
+ only_one_sg = true;
+ if (in_is_sg)
+ rep->only_in_sg = true;
+ else
+ rep->only_out_sg = true;
+ }
+
+ if (vb > 2)
+ pr2serr_lk("%d <-- Starting worker thread\n", id);
+ if (! rep->both_sg) {
+ rep->buffp = sg_memalign(sz, 0 /* page align */, &rep->alloc_bp,
+ false);
+ if (NULL == rep->buffp)
+ err_exit(ENOMEM, "out of memory creating user buffers\n");
+ }
+ rep->infd = clp->infd;
+ rep->outfd = clp->outfd;
+ rep->out2fd = clp->out2fd;
+ rep->outregfd = clp->outregfd;
+ rep->rep_count = 0;
+ rep->in_follow_on = -1;
+ rep->out_follow_on = -1;
+
+ if (rep->infd == rep->outfd) {
+ if (in_is_sg)
+ rep->same_sg = true;
+ }
+ if (clp->in_flags.random) {
+ ssize_t ssz;
+
+ ssz = getrandom(&rep->seed, sizeof(rep->seed), 0);
+ if (ssz < (ssize_t)sizeof(rep->seed))
+ pr2serr_lk("[%d] %s: getrandom() failed, ret=%d\n", id, __func__,
+ (int)ssz);
+ if (vb > 1)
+ pr2serr_lk("[%d] %s: seed=%ld\n", id, __func__, rep->seed);
+ srand48_r(rep->seed, &rep->drand);
+ }
+
+ if (in_is_sg && clp->infp) {
+ fd = sg_in_open(clp, clp->infp, (in_mmap ? &rep->buffp : NULL),
+ (in_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ rep->infd = fd;
+ rep->mmap_active = in_mmap ? clp->in_flags.mmap : 0;
+ if (in_mmap && (vb > 4))
+ pr2serr_lk("[%d] %s: mmap buffp=%p\n", id, __func__, rep->buffp);
+ own_infd = true;
+ ++num_sg;
+ if (vb > 2)
+ pr2serr_lk("[%d]: opened local sg IFILE\n", id);
+ }
+ if (out_is_sg && clp->outfp) {
+ fd = sg_out_open(clp, clp->outfp, (out_mmap ? &rep->buffp : NULL),
+ (out_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ rep->outfd = fd;
+ if (! rep->mmap_active)
+ rep->mmap_active = out_mmap ? clp->out_flags.mmap : 0;
+ if (out_mmap && (vb > 4))
+ pr2serr_lk("[%d]: mmap buffp=%p\n", id, rep->buffp);
+ own_outfd = true;
+ ++num_sg;
+ if (vb > 2)
+ pr2serr_lk("[%d]: opened local sg OFILE\n", id);
+ }
+ if ((FT_SG == clp->out2_type) && clp->out2fp) {
+ fd = sg_out_open(clp, clp->out2fp,
+ (out_mmap ? &rep->buffp : NULL),
+ (out_mmap ? &rep->mmap_len : NULL));
+ if (fd < 0)
+ goto fini;
+ rep->out2fd = fd;
+ own_out2fd = true;
+ if (vb > 2)
+ pr2serr_lk("[%d]: opened local sg OFILE2\n", id);
+ }
+ if (vb > 2) {
+ if (in_is_sg && (! own_infd))
+ pr2serr_lk("[%d]: using global sg IFILE, fd=%d\n", id, rep->infd);
+ if (out_is_sg && (! own_outfd))
+ pr2serr_lk("[%d]: using global sg OFILE, fd=%d\n", id, rep->outfd);
+ if ((FT_SG == clp->out2_type) && (! own_out2fd))
+ pr2serr_lk("[%d]: using global sg OFILE2, fd=%d\n", id,
+ rep->out2fd);
+ }
+ if (rep->both_sg)
+ rep->has_share = sg_share_prepare(rep->outfd, rep->infd, id, vb > 9);
+ if (vb > 9)
+ pr2serr_lk("[%d]: has_share=%s\n", id,
+ (rep->has_share ? "true" : "false"));
+ // share_and_ofreg = (rep->has_share && (rep->outregfd >= 0));
+
+ /* vvvvvvvvvvvvvv Main segment copy loop vvvvvvvvvvvvvvvvvvvvvvv */
+ while (1) {
+ get_next_res gnr = clp->get_next(clp->mrq_num * clp->bpt);
+
+ seg_blks = gnr.second;
+ if (seg_blks <= 0) {
+ if (seg_blks < 0)
+ res = -seg_blks;
+ break;
+ }
+ if (! i_sg_it.set_by_blk_idx(gnr.first)) {
+ pr2serr_lk("[%d]: input set_by_blk_idx() failed\n", id);
+ i_sg_it.dbg_print("input after set_by_blk_idx", false, vb > 5);
+ res = 2;
+ break;
+ }
+ if (! o_sg_it.set_by_blk_idx(gnr.first)) {
+ pr2serr_lk("[%d]: output set_by_blk_idx() failed\n", id);
+ res = 3;
+ break;
+ }
+ if (rep->both_sg) {
+ uint32_t nn = (2 * clp->mrq_num) + 4;
+
+ if (a_cdb.capacity() < nn)
+ a_cdb.reserve(nn);
+ if (a_v4.capacity() < nn)
+ a_v4.reserve(nn);
+ res = do_both_sg_segment(rep, i_sg_it, o_sg_it, seg_blks, a_cdb,
+ a_v4);
+ if (res < 0)
+ break;
+ } else if (only_one_sg) {
+ uint32_t nn = clp->mrq_num + 4;
+
+ if (a_cdb.capacity() < nn)
+ a_cdb.reserve(nn);
+ if (a_v4.capacity() < nn)
+ a_v4.reserve(nn);
+ res = do_normal_sg_segment(rep, i_sg_it, o_sg_it, seg_blks, a_cdb,
+ a_v4);
+ if (res < 0)
+ break;
+ } else {
+ res = do_normal_normal_segment(rep, i_sg_it, o_sg_it, seg_blks);
+ if (res < 0)
+ break;
+ }
+ if (singleton) {
+ {
+ lock_guard<mutex> lk(clp->infant_mut);
+
+ clp->processed = true;
+ } /* this unlocks lk */
+ clp->infant_cv.notify_one();
+ singleton = false;
+ }
+ if (rep->stop_after_write)
+ break;
+ } /* ^^^^^^^^^^ end of main while loop which copies segments ^^^^^^ */
+ if (singleton) {
+ {
+ lock_guard<mutex> lk(clp->infant_mut);
+
+ clp->processed = true;
+ } /* this unlocks lk */
+ clp->infant_cv.notify_one();
+ singleton = false;
+ }
+ if (res < 0) {
+ if (seg_blks >= 0)
+ clp->get_next(-1); /* flag error to main */
+ pr2serr_lk("%s: t=%d: aborting, res=%d\n", __func__, rep->id, res);
+ }
+
+fini:
+
+#if 0
+ if ((rep->mmap_active == 0) && rep->alloc_bp)
+ free(rep->alloc_bp);
+ if ((1 == rep->mmap_active) && (rep->mmap_len > 0)) {
+ if (munmap(rep->buffp, rep->mmap_len) < 0) {
+ int err = errno;
+ char bb[64];
+
+ pr2serr_lk("thread=%d: munmap() failed: %s\n", rep->id,
+ tsafe_strerror(err, bb));
+ }
+ if (vb > 4)
+ pr2serr_lk("thread=%d: munmap(%p, %d)\n", rep->id, rep->buffp,
+ rep->mmap_len);
+ rep->mmap_active = 0;
+ }
+#endif
+
+ if (own_infd && (rep->infd >= 0)) {
+ if (vb && in_is_sg) {
+ ++num_waiting_calls;
+ if (ioctl(rep->infd, SG_GET_NUM_WAITING, &n) >= 0) {
+ if (n > 0)
+ pr2serr_lk("%s: tid=%d: num_waiting=%d prior close(in)\n",
+ __func__, rep->id, n);
+ } else {
+ err = errno;
+ pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: "
+ "%s\n", __func__, rep->id, err, strerror(err));
+ }
+ }
+ close(rep->infd);
+ }
+ if (own_outfd && (rep->outfd >= 0)) {
+ if (vb && out_is_sg) {
+ ++num_waiting_calls;
+ if (ioctl(rep->outfd, SG_GET_NUM_WAITING, &n) >= 0) {
+ if (n > 0)
+ pr2serr_lk("%s: tid=%d: num_waiting=%d prior "
+ "close(out)\n", __func__, rep->id, n);
+ } else {
+ err = errno;
+ pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: "
+ "%s\n", __func__, rep->id, err, strerror(err));
+ }
+ }
+ close(rep->outfd);
+ }
+ if (own_out2fd && (rep->out2fd >= 0))
+ close(rep->out2fd);
+ /* pass stats back to master */
+ clp->in_rem_count -= rep->in_local_count;
+ clp->out_rem_count -= rep->out_local_count;
+ clp->in_partial += rep->in_local_partial;
+ clp->out_partial += rep->out_local_partial;
+}
+
+/* N.B. Returns 'blocks' is successful, lesser positive number if there was
+ * a short read, or an error code which is negative. */
+static int
+normal_in_rd(Rq_elem * rep, int64_t lba, int blocks, int d_boff)
+{
+ struct global_collection * clp = rep->clp;
+ int res, err;
+ int id = rep->id;
+ uint8_t * bp;
+ char strerr_buff[STRERR_BUFF_LEN];
+
+ if (clp->verbose > 4)
+ pr2serr_lk("[%d] %s: lba=%" PRIu64 ", blocks=%d, d_boff=%d\n", id,
+ __func__, lba, blocks, d_boff);
+ if (FT_RANDOM_0_FF == clp->in_type) {
+ int k, j;
+ const int jbump = sizeof(uint32_t);
+ long rn;
+ uint8_t * bp;
+
+ if (clp->in_flags.zero)
+ memset(rep->buffp + d_boff, 0, blocks * clp->bs);
+ else if (clp->in_flags.ff)
+ memset(rep->buffp + d_boff, 0xff, blocks * clp->bs);
+ else {
+ bp = rep->buffp + d_boff;
+ for (k = 0; k < blocks; ++k, bp += clp->bs) {
+ for (j = 0; j < clp->bs; j += jbump) {
+ /* mrand48 takes uniformly from [-2^31, 2^31) */
+ mrand48_r(&rep->drand, &rn);
+ *((uint32_t *)(bp + j)) = (uint32_t)rn;
+ }
+ }
+ }
+ return blocks;
+ }
+
+ if (clp->in_type != FT_FIFO) {
+ int64_t pos = lba * clp->bs;
+
+ if (rep->in_follow_on != pos) {
+ if (lseek64(rep->infd, pos, SEEK_SET) < 0) {
+ err = errno;
+ pr2serr_lk("[%d] %s: >> lseek64(%" PRId64 "): %s\n", id,
+ __func__, pos, safe_strerror(err));
+ return -err;
+ }
+ rep->in_follow_on = pos;
+ }
+ }
+ bp = rep->buffp + d_boff;
+ while (((res = read(clp->infd, bp, blocks * clp->bs)) < 0) &&
+ ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ err = errno;
+ if (clp->in_flags.coe) {
+ memset(bp, 0, blocks * clp->bs);
+ pr2serr_lk("[%d] %s : >> substituted zeros for in blk=%" PRId64
+ " for %d bytes, %s\n", id, __func__, lba,
+ blocks * clp->bs,
+ tsafe_strerror(err, strerr_buff));
+ res = blocks * clp->bs;
+ } else {
+ pr2serr_lk("[%d] %s: error in normal read, %s\n", id, __func__,
+ tsafe_strerror(err, strerr_buff));
+ return -err;
+ }
+ }
+ rep->in_follow_on += res;
+ if (res < blocks * clp->bs) {
+ blocks = res / clp->bs;
+ if ((res % clp->bs) > 0) {
+ rep->in_local_partial++;
+ rep->in_resid_bytes = res % clp->bs;
+ }
+ }
+ return blocks;
+}
+
+/* N.B. Returns 'blocks' is successful, lesser positive number if there was
+ * a short write, or an error code which is negative. */
+static int
+normal_out_wr(Rq_elem * rep, int64_t lba, int blocks, int d_boff)
+{
+ int res, err;
+ int id = rep->id;
+ struct global_collection * clp = rep->clp;
+ uint8_t * bp = rep->buffp + d_boff;
+ char strerr_buff[STRERR_BUFF_LEN];
+
+ if (clp->verbose > 4)
+ pr2serr_lk("[%d] %s: lba=%" PRIu64 ", blocks=%d, d_boff=%d\n", id,
+ __func__, lba, blocks, d_boff);
+
+ if (clp->in_type != FT_FIFO) {
+ int64_t pos = lba * clp->bs;
+
+ if (rep->out_follow_on != pos) {
+ if (lseek64(rep->outfd, pos, SEEK_SET) < 0) {
+ err = errno;
+ pr2serr_lk("[%d] %s: >> lseek64(%" PRId64 "): %s\n", id,
+ __func__, pos, safe_strerror(err));
+ return -err;
+ }
+ rep->out_follow_on = pos;
+ }
+ }
+ while (((res = write(clp->outfd, bp, blocks * clp->bs))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ err = errno;
+ if (clp->out_flags.coe) {
+ pr2serr_lk("[%d] %s: >> ignored error for out lba=%" PRId64
+ " for %d bytes, %s\n", id, __func__, lba,
+ blocks * clp->bs, tsafe_strerror(err, strerr_buff));
+ res = blocks * clp->bs;
+ }
+ else {
+ pr2serr_lk("[%d] %s: error normal write, %s\n", id, __func__,
+ tsafe_strerror(err, strerr_buff));
+ return -err;
+ }
+ }
+ rep->out_follow_on += res;
+ if (res < blocks * clp->bs) {
+ blocks = res / clp->bs;
+ if ((res % clp->bs) > 0) {
+ blocks++;
+ rep->out_local_partial++;
+ }
+ }
+ return blocks;
+}
+
+static int
+extra_out_wr(Rq_elem * rep, int num_bytes, int d_boff)
+{
+ int res, err;
+ int id = rep->id;
+ struct global_collection * clp = rep->clp;
+ uint8_t * bp = rep->buffp + d_boff;
+ char strerr_buff[STRERR_BUFF_LEN];
+
+ if (clp->verbose > 4)
+ pr2serr_lk("[%d] %s: num_bytes=%d, d_boff=%d\n", id, __func__,
+ num_bytes, d_boff);
+
+ while (((res = write(clp->outfd, bp, num_bytes))
+ < 0) && ((EINTR == errno) || (EAGAIN == errno)))
+ std::this_thread::yield();/* another thread may be able to progress */
+ if (res < 0) {
+ err = errno;
+ pr2serr_lk("[%d] %s: error normal write, %s\n", id, __func__,
+ tsafe_strerror(err, strerr_buff));
+ return -err;
+ }
+ if (res > 0)
+ rep->out_local_partial++;
+ return res;
+}
+
+static int
+sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks,
+ int64_t start_block, bool ver_true, bool write_true,
+ bool fua, bool dpo)
+{
+ int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88};
+ int ve_opcode[] = {0xff /* no VER(6) */, 0x2f, 0xaf, 0x8f};
+ int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a};
+ int sz_ind;
+
+ memset(cdbp, 0, cdb_sz);
+ if (ver_true) { /* only support VERIFY(10) */
+ if (cdb_sz < 10) {
+ pr2serr_lk("%s only support VERIFY(10)\n", my_name);
+ return 1;
+ }
+ cdb_sz = 10;
+ fua = false;
+ cdbp[1] |= 0x2; /* BYTCHK=1 --> sending dout for comparison */
+ cdbp[0] = ve_opcode[1];
+ }
+ if (dpo)
+ cdbp[1] |= 0x10;
+ if (fua)
+ cdbp[1] |= 0x8;
+ switch (cdb_sz) {
+ case 6:
+ sz_ind = 0;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1);
+ cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks;
+ if (blocks > 256) {
+ pr2serr_lk("%sfor 6 byte commands, maximum number of blocks is "
+ "256\n", my_name);
+ return 1;
+ }
+ if ((start_block + blocks - 1) & (~0x1fffff)) {
+ pr2serr_lk("%sfor 6 byte commands, can't address blocks beyond "
+ "%d\n", my_name, 0x1fffff);
+ return 1;
+ }
+ if (dpo || fua) {
+ pr2serr_lk("%sfor 6 byte commands, neither dpo nor fua bits "
+ "supported\n", my_name);
+ return 1;
+ }
+ break;
+ case 10:
+ if (! ver_true) {
+ sz_ind = 1;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ }
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7);
+ if (blocks & (~0xffff)) {
+ pr2serr_lk("%sfor 10 byte commands, maximum number of blocks is "
+ "%d\n", my_name, 0xffff);
+ return 1;
+ }
+ break;
+ case 12:
+ sz_ind = 2;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6);
+ break;
+ case 16:
+ sz_ind = 3;
+ cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] :
+ rd_opcode[sz_ind]);
+ sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2);
+ sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10);
+ break;
+ default:
+ pr2serr_lk("%sexpected cdb size of 6, 10, 12, or 16 but got %d\n",
+ my_name, cdb_sz);
+ return 1;
+ }
+ return 0;
+}
+
+#if 0
+
+static bool
+sg_wr_swap_share(Rq_elem * rep, int to_fd, bool before)
+{
+ bool not_first = false;
+ int err = 0;
+ int k;
+ int master_fd = rep->infd; /* in (READ) side is master */
+ struct global_collection * clp = rep->clp;
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip = &sei;
+
+ if (rep->clp->verbose > 2)
+ pr2serr_lk("%s: tid=%d: to_fd=%d, before=%d\n", __func__, rep->id,
+ to_fd, (int)before);
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CHG_SHARE_FD;
+ seip->sei_rd_mask |= SG_SEIM_CHG_SHARE_FD;
+ seip->share_fd = to_fd;
+ if (before) {
+ /* clear MASTER_FINI bit to put master in SG_RQ_SHR_SWAP state */
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MASTER_FINI;
+ seip->ctl_flags &= SG_CTL_FLAGM_MASTER_FINI;/* would be 0 anyway */
+ }
+ for (k = 0; (ioctl(master_fd, SG_SET_GET_EXTENDED, seip) < 0) &&
+ (EBUSY == errno); ++k) {
+ err = errno;
+ if (k > 10000)
+ break;
+ if (! not_first) {
+ if (clp->verbose > 3)
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(change_shared_fd=%d), "
+ "failed errno=%d %s\n", rep->id, master_fd, err,
+ strerror(err));
+ not_first = true;
+ }
+ err = 0;
+ std::this_thread::yield();/* another thread may be able to progress */
+ }
+ if (err) {
+ pr2serr_lk("tid=%d: ioctl(EXTENDED(change_shared_fd=%d), failed "
+ "errno=%d %s\n", rep->id, master_fd, err, strerror(err));
+ return false;
+ }
+ if (clp->verbose > 15)
+ pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(change_shared_fd)) ok, "
+ "master_fd=%d, to_slave_fd=%d\n", __func__, rep->id,
+ master_fd, to_fd);
+ return true;
+}
+
+#endif
+
+static int
+process_mrq_response(Rq_elem * rep, const struct sg_io_v4 * ctl_v4p,
+ const struct sg_io_v4 * a_v4p, int num_mrq,
+ uint32_t & good_inblks, uint32_t & good_outblks)
+{
+ struct global_collection * clp = rep->clp;
+ bool ok;
+ int id = rep->id;
+ int resid = ctl_v4p->din_resid;
+ int sres = ctl_v4p->spare_out;
+ int n_subm = num_mrq - ctl_v4p->dout_resid;
+ int n_cmpl = ctl_v4p->info;
+ int n_good = 0;
+ int vb = clp->verbose;
+ int k, slen, sstatus;
+ const struct sg_io_v4 * a_np = a_v4p;
+
+ good_inblks = 0;
+ good_outblks = 0;
+ if (n_subm < 0) {
+ pr2serr_lk("[%d] %s: co.dout_resid(%d) > num_mrq(%d)\n", id, __func__,
+ ctl_v4p->dout_resid, num_mrq);
+ return -1;
+ }
+ if (n_cmpl != (num_mrq - resid))
+ pr2serr_lk("[%d] %s: co.info(%d) != (num_mrq(%d) - co.din_resid(%d))\n"
+ "will use co.info\n", id, __func__, n_cmpl, num_mrq, resid);
+ if (n_cmpl > n_subm) {
+ pr2serr_lk("[%d] %s: n_cmpl(%d) > n_subm(%d), use n_subm for both\n",
+ id, __func__, n_cmpl, n_subm);
+ n_cmpl = n_subm;
+ }
+ if (sres) {
+ pr2serr_lk("[%d] %s: secondary error: %s [%d], info=0x%x\n", id,
+ __func__, strerror(sres), sres, ctl_v4p->info);
+ if (E2BIG == sres) {
+ sg_take_snap(rep->infd, id, true);
+ sg_take_snap(rep->outfd, id, true);
+ }
+ }
+ /* Check if those submitted have finished or not */
+ for (k = 0; k < n_subm; ++k, ++a_np) {
+ slen = a_np->response_len;
+ if (! (SG_INFO_MRQ_FINI & a_np->info)) {
+ pr2serr_lk("[%d] %s, a_n[%d]: missing SG_INFO_MRQ_FINI ? ?\n",
+ id, __func__, k);
+ v4hdr_out_lk("a_np", a_np, id);
+ v4hdr_out_lk("cop", ctl_v4p, id);
+ }
+ ok = true;
+ sstatus = a_np->device_status;
+ if ((sstatus && (SAM_STAT_CONDITION_MET != sstatus)) ||
+ a_np->transport_status || a_np->driver_status) {
+ ok = false;
+ if (SAM_STAT_CHECK_CONDITION != a_np->device_status) {
+ pr2serr_lk("[%d] %s, a_n[%d]:\n", id, __func__, k);
+ if (vb)
+ lk_chk_n_print4(" >>", a_np, false);
+ }
+ }
+ if (slen > 0) {
+ struct sg_scsi_sense_hdr ssh;
+ const uint8_t *sbp = (const uint8_t *)a_np->response;
+
+ if (sg_scsi_normalize_sense(sbp, slen, &ssh) &&
+ (ssh.response_code >= 0x70)) {
+ char b[256];
+
+ if (ssh.response_code & 0x1)
+ ok = true;
+ if (vb) {
+ sg_get_sense_str(" ", sbp, slen, false, sizeof(b), b);
+ pr2serr_lk("[%d] %s, a_n[%d]:\n%s\n", id, __func__, k, b);
+ }
+ }
+ }
+ if (ok) {
+ ++n_good;
+ if (a_np->dout_xfer_len >= (uint32_t)clp->bs) {
+ if (a_np->dout_resid)
+ good_outblks += (a_np->dout_xfer_len - a_np->dout_resid) /
+ clp->bs;
+ else /* avoid division in common case of resid==0 */
+ good_outblks += (uint32_t)a_np->usr_ptr;
+ }
+ if (a_np->din_xfer_len >= (uint32_t)clp->bs) {
+ if (a_np->din_resid)
+ good_inblks += (a_np->din_xfer_len - a_np->din_resid) /
+ clp->bs;
+ else
+ good_inblks += (uint32_t)a_np->usr_ptr;
+ }
+ }
+ }
+ if ((n_subm == num_mrq) || (vb < 3))
+ goto fini;
+ pr2serr_lk("[%d] %s: checking response array beyond number of "
+ "submissions:\n", id, __func__);
+ for (k = n_subm; k < num_mrq; ++k, ++a_np) {
+ if (SG_INFO_MRQ_FINI & a_np->info)
+ pr2serr_lk("[%d] %s, a_n[%d]: unexpected SG_INFO_MRQ_FINI set\n",
+ id, __func__, k);
+ if (a_np->device_status || a_np->transport_status ||
+ a_np->driver_status) {
+ pr2serr_lk("[%d] %s, a_n[%d]:\n", id, __func__, k);
+ lk_chk_n_print4(" ", a_np, false);
+ }
+ }
+fini:
+ return n_good;
+}
+
+
+// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< zzzzzzzzz
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+sg_half_segment(Rq_elem * rep, scat_gath_iter & sg_it, bool is_wr,
+ int seg_blks, uint8_t *dp,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4)
+{
+ int num_mrq, k, res, fd, mrq_pack_id_base, id, b_len, rflags;
+ int num, kk, lin_blks, cdbsz, num_good;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks, out_fin_blks;
+ uint32_t mrq_q_blks = 0;
+ uint32_t in_mrq_q_blks = 0;
+ uint32_t out_mrq_q_blks = 0;
+ const int max_cdb_sz = MAX_SCSI_CDB_SZ;
+ struct sg_io_v4 * a_v4p;
+ struct sg_io_v4 ctl_v4; /* MRQ control object */
+ struct global_collection * clp = rep->clp;
+ const char * iosub_str = "SUBMIT(variable blocking)";
+ char b[80];
+ cdb_arr_t t_cdb = {};
+ struct sg_io_v4 t_v4;
+ struct sg_io_v4 * t_v4p = &t_v4;
+ struct flags_t * flagsp = is_wr ? &clp->out_flags : &clp->in_flags;
+ bool serial = flagsp->serial;
+
+ id = rep->id;
+ b_len = sizeof(b);
+ if (serial)
+ iosub_str = "(ordered blocking)";
+
+ a_cdb.clear();
+ a_v4.clear();
+ mrq_pack_id_base = id * PACK_ID_TID_MULTIPLIER;
+
+ rflags = 0;
+ if (flagsp->mmap && (rep->outregfd >= 0))
+ rflags |= SGV4_FLAG_MMAP_IO;
+ if (flagsp->dio)
+ rflags |= SGV4_FLAG_DIRECT_IO;
+ if (flagsp->qhead)
+ rflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (flagsp->qtail)
+ rflags |= SGV4_FLAG_Q_AT_TAIL;
+
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ lin_blks = sg_it.linear_for_n_blks(kk);
+ num = lin_blks;
+ if (num <= 0) {
+ res = 0;
+ pr2serr_lk("[%d] %s: unexpected num=%d\n", id, __func__, num);
+ break;
+ }
+
+ /* First build the command/request for the master (READ) side */
+ cdbsz = is_wr ? clp->cdbsz_out : clp->cdbsz_in;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num, sg_it.current_lba(),
+ false, is_wr, flagsp->fua, flagsp->dpo);
+ if (res) {
+ pr2serr_lk("[%d] %s: sg_build_scsi_cdb() failed\n", id, __func__);
+ break;
+ } else if (clp->verbose > 3)
+ lk_print_command_len("cdb: ", t_cdb.data(), cdbsz, true);
+ a_cdb.push_back(t_cdb);
+
+ memset(t_v4p, 0, sizeof(*t_v4p));
+ t_v4p->guard = 'Q';
+ t_v4p->flags = rflags;
+ t_v4p->request_len = cdbsz;
+ if (is_wr) {
+ t_v4p->dout_xfer_len = num * clp->bs;
+ t_v4p->dout_xferp = (uint64_t)(dp + (mrq_q_blks * clp->bs));
+ } else {
+ t_v4p->din_xfer_len = num * clp->bs;
+ t_v4p->din_xferp = (uint64_t)(dp + (mrq_q_blks * clp->bs));
+ }
+ t_v4p->timeout = DEF_TIMEOUT;
+ t_v4p->usr_ptr = num; /* pass number blocks requested */
+ mrq_q_blks += num;
+ t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ a_v4.push_back(t_v4);
+
+ sg_it.add_blks(num);
+ }
+
+ if (rep->only_in_sg)
+ fd = rep->infd;
+ else if (rep->only_out_sg)
+ fd = rep->outfd;
+ else {
+ pr2serr_lk("[%d] %s: why am I here? No sg devices\n", id, __func__);
+ return -EINVAL;
+ }
+ num_mrq = a_v4.size();
+ a_v4p = a_v4.data();
+ res = 0;
+ memset(&ctl_v4, 0, sizeof(ctl_v4));
+ ctl_v4.guard = 'Q';
+ ctl_v4.request_len = a_cdb.size() * max_cdb_sz;
+ ctl_v4.request = (uint64_t)a_cdb.data();
+ ctl_v4.max_response_len = sizeof(rep->sb);
+ ctl_v4.response = (uint64_t)rep->sb;
+ ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_STOP_IF;
+ ctl_v4.dout_xferp = (uint64_t)a_v4.data(); /* request array */
+ ctl_v4.dout_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ ctl_v4.din_xferp = (uint64_t)a_v4.data(); /* response array */
+ ctl_v4.din_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ if (false /* allow_mrq_abort */)
+ ctl_v4.request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+
+ if (clp->verbose > 4) {
+ pr2serr_lk("[%d] %s: Controlling object _before_ ioctl(SG_IO%s):\n",
+ id, __func__, iosub_str);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk("Controlling object before", &ctl_v4, id);
+ }
+
+try_again:
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, serial ? mrq_ob_s : mrq_vb_s);
+ }
+ if (serial)
+ res = ioctl(fd, SG_IO, &ctl_v4);
+ else
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4); /* overlapping commands */
+ if (res < 0) {
+ int err = errno;
+
+ if (E2BIG == err)
+ sg_take_snap(fd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* allow another thread to progress */
+ goto try_again;
+ }
+ pr2serr_lk("[%d] %s: ioctl(SG_IO%s, %s)-->%d, errno=%d: %s\n", id,
+ __func__, iosub_str, sg_flags_str(ctl_v4.flags, b_len, b),
+ res, err, strerror(err));
+ return -err;
+ }
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object output by ioctl(%s):\n", __func__,
+ iosub_str);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk("Controlling object after", &ctl_v4, id);
+ if (clp->verbose > 5) {
+ for (k = 0; k < num_mrq; ++k) {
+ pr2serr_lk("AFTER: def_arr[%d]:\n", k);
+ v4hdr_out_lk("normal v4 object", (a_v4p + k), id);
+ // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p),
+ // 1);
+ }
+ }
+ }
+ num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks,
+ out_fin_blks);
+ if (is_wr)
+ out_mrq_q_blks = mrq_q_blks;
+ else
+ in_mrq_q_blks = mrq_q_blks;
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: >>> seg_blks=%d, num_good=%d, in_q/fin blks=%u/%u; "
+ "out_q/fin blks=%u/%u\n", __func__, o_seg_blks, num_good,
+ in_mrq_q_blks, in_fin_blks, out_mrq_q_blks, out_fin_blks);
+
+ if (num_good < 0)
+ return -ENODATA;
+ else {
+ if (num_good < num_mrq) {
+ int resid_blks = in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ rep->in_rem_count += resid_blks;
+
+ resid_blks = out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0)
+ rep->out_rem_count += resid_blks;
+ rep->stop_after_write = true;
+ }
+ }
+ return is_wr ? out_fin_blks : in_fin_blks;
+}
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+do_normal_normal_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks)
+{
+ int k, kk, res, id, num, d_off;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks = 0;
+ uint32_t out_fin_blks = 0;
+ struct global_collection * clp = rep->clp;
+
+ id = rep->id;
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = i_sg_it.linear_for_n_blks(kk);
+ res = normal_in_rd(rep, i_sg_it.current_lba(), num,
+ d_off * clp->bs);
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal in failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ i_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ seg_blks = d_off;
+ in_fin_blks = seg_blks;
+
+ if (FT_DEV_NULL == clp->out_type)
+ goto fini;
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = o_sg_it.linear_for_n_blks(kk);
+ res = normal_out_wr(rep, o_sg_it.current_lba(), num,
+ d_off * clp->bs);
+ if (res < num) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal out failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ }
+ o_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ if (rep->in_resid_bytes > 0) {
+ res = extra_out_wr(rep, rep->in_resid_bytes, d_off * clp->bs);
+ if (res < 0)
+ pr2serr_lk("[%d] %s: extr out failed d_off=%d, err=%d\n", id,
+ __func__, d_off, -res);
+ rep->in_resid_bytes = 0;
+ }
+ seg_blks = d_off;
+ out_fin_blks = seg_blks;
+
+fini:
+ rep->in_local_count += in_fin_blks;
+ rep->out_local_count += out_fin_blks;
+
+ if ((in_fin_blks + out_fin_blks) < (uint32_t)o_seg_blks) {
+ int resid_blks = o_seg_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ rep->in_rem_count += resid_blks;
+ resid_blks = o_seg_blks - out_fin_blks;
+ if (resid_blks > 0)
+ rep->out_rem_count += resid_blks;
+ }
+ return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks));
+}
+
+/* Returns number of blocks successfully processed or a negative error
+ * number. */
+static int
+do_normal_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4)
+{
+ bool in_is_normal = ! rep->only_in_sg;
+ int k, kk, res, id, num, d_off;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks = 0;
+ uint32_t out_fin_blks = 0;
+ struct global_collection * clp = rep->clp;
+
+ id = rep->id;
+ a_cdb.clear();
+ a_v4.clear();
+
+ if (in_is_normal) { /* in: normal --> out : sg */
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = i_sg_it.linear_for_n_blks(kk);
+ res = normal_in_rd(rep, i_sg_it.current_lba(), num,
+ d_off * clp->bs);
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal in failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ i_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ seg_blks = d_off;
+ in_fin_blks = seg_blks;
+
+ if (rep->in_resid_bytes > 0) {
+ ++seg_blks;
+ rep->in_resid_bytes = 0;
+ }
+ res = sg_half_segment(rep, o_sg_it, true /* is_wr */, seg_blks,
+ rep->buffp, a_cdb, a_v4);
+ if (res < seg_blks) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: sg out failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ goto fini;
+ }
+ rep->stop_after_write = true;
+ }
+ seg_blks = res;
+ out_fin_blks = seg_blks;
+
+ } else { /* in: sg --> out: normal */
+ res = sg_half_segment(rep, i_sg_it, false, seg_blks, rep->buffp,
+ a_cdb, a_v4);
+ if (res < seg_blks) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: sg in failed, err=%d\n", id, __func__,
+ -res);
+ goto fini;
+ }
+ rep->stop_after_write = true;
+ }
+ seg_blks = res;
+ in_fin_blks = seg_blks;
+
+ if (FT_DEV_NULL == clp->out_type)
+ goto bypass;
+ d_off = 0;
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ num = o_sg_it.linear_for_n_blks(kk);
+ res = normal_out_wr(rep, o_sg_it.current_lba(), num,
+ d_off * clp->bs);
+ if (res < num) {
+ if (res < 0) {
+ pr2serr_lk("[%d] %s: normal out failed d_off=%d, err=%d\n",
+ id, __func__, d_off, -res);
+ break;
+ }
+ }
+ o_sg_it.add_blks(res);
+ if (res < num) {
+ d_off += res;
+ rep->stop_after_write = true;
+ break;
+ }
+ }
+ seg_blks = d_off;
+ out_fin_blks = seg_blks;
+ }
+bypass:
+ rep->in_local_count += in_fin_blks;
+ rep->out_local_count += out_fin_blks;
+
+ if ((in_fin_blks + out_fin_blks) < (uint32_t)o_seg_blks) {
+ int resid_blks = o_seg_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ rep->in_rem_count += resid_blks;
+ resid_blks = o_seg_blks - out_fin_blks;
+ if (resid_blks > 0)
+ rep->out_rem_count += resid_blks;
+ }
+fini:
+ return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks));
+}
+
+/* This function sets up a multiple request (mrq) transaction and sends it
+ * to the pass-through. Returns number of blocks processed (==seg_blks for
+ * all good) or a negative error number. */
+static int
+do_both_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it,
+ scat_gath_iter & o_sg_it, int seg_blks,
+ vector<cdb_arr_t> & a_cdb,
+ vector<struct sg_io_v4> & a_v4)
+{
+ int num_mrq, k, res, fd, mrq_pack_id_base, id, b_len, iflags, oflags;
+ int num, kk, i_lin_blks, o_lin_blks, cdbsz, num_good;
+ int o_seg_blks = seg_blks;
+ uint32_t in_fin_blks, out_fin_blks;
+ uint32_t in_mrq_q_blks = 0;
+ uint32_t out_mrq_q_blks = 0;
+ const int max_cdb_sz = MAX_SCSI_CDB_SZ;
+ struct sg_io_v4 * a_v4p;
+ struct sg_io_v4 ctl_v4; /* MRQ control object */
+ struct global_collection * clp = rep->clp;
+ const char * iosub_str = "SUBMIT(svb)";
+ char b[80];
+ cdb_arr_t t_cdb = {};
+ struct sg_io_v4 t_v4;
+ struct sg_io_v4 * t_v4p = &t_v4;
+ struct flags_t * iflagsp = &clp->in_flags;
+ struct flags_t * oflagsp = &clp->out_flags;
+
+ id = rep->id;
+ b_len = sizeof(b);
+
+ a_cdb.clear();
+ a_v4.clear();
+ mrq_pack_id_base = id * PACK_ID_TID_MULTIPLIER;
+
+ iflags = SGV4_FLAG_SHARE;
+ if (iflagsp->mmap && (rep->outregfd >= 0))
+ iflags |= SGV4_FLAG_MMAP_IO;
+ else
+ iflags |= SGV4_FLAG_NO_DXFER;
+ if (iflagsp->dio)
+ iflags |= SGV4_FLAG_DIRECT_IO;
+ if (iflagsp->qhead)
+ iflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (iflagsp->qtail)
+ iflags |= SGV4_FLAG_Q_AT_TAIL;
+
+ oflags = SGV4_FLAG_SHARE | SGV4_FLAG_NO_DXFER;
+ if (oflagsp->dio)
+ oflags |= SGV4_FLAG_DIRECT_IO;
+ if (oflagsp->qhead)
+ oflags |= SGV4_FLAG_Q_AT_HEAD;
+ if (oflagsp->qtail)
+ oflags |= SGV4_FLAG_Q_AT_TAIL;
+ oflags |= SGV4_FLAG_DO_ON_OTHER;
+
+ for (k = 0; seg_blks > 0; ++k, seg_blks -= num) {
+ kk = min<int>(seg_blks, clp->bpt);
+ i_lin_blks = i_sg_it.linear_for_n_blks(kk);
+ o_lin_blks = o_sg_it.linear_for_n_blks(kk);
+ num = min<int>(i_lin_blks, o_lin_blks);
+ if (num <= 0) {
+ res = 0;
+ pr2serr_lk("[%d] %s: unexpected num=%d\n", id, __func__, num);
+ break;
+ }
+
+ /* First build the command/request for the master (READ) side */
+ cdbsz = clp->cdbsz_in;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num,
+ i_sg_it.current_lba(), false, false,
+ iflagsp->fua, iflagsp->dpo);
+ if (res) {
+ pr2serr_lk("%s: t=%d: input sg_build_scsi_cdb() failed\n",
+ __func__, id);
+ break;
+ } else if (clp->verbose > 3)
+ lk_print_command_len("input cdb: ", t_cdb.data(), cdbsz, true);
+ a_cdb.push_back(t_cdb);
+
+ memset(t_v4p, 0, sizeof(*t_v4p));
+ t_v4p->guard = 'Q';
+ t_v4p->flags = iflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->din_xfer_len = num * clp->bs;
+ t_v4p->timeout = DEF_TIMEOUT;
+ t_v4p->usr_ptr = num; /* pass number blocks requested */
+ in_mrq_q_blks += num;
+ t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ a_v4.push_back(t_v4);
+
+ /* Now build the command/request for slave (WRITE or VERIFY) side */
+ cdbsz = clp->cdbsz_out;
+ res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num,
+ o_sg_it.current_lba(), clp->verify, true,
+ oflagsp->fua, oflagsp->dpo);
+ if (res) {
+ pr2serr_lk("%s: t=%d: output sg_build_scsi_cdb() failed\n",
+ __func__, id);
+ break;
+ } else if (clp->verbose > 3)
+ lk_print_command_len("output cdb: ", t_cdb.data(), cdbsz, true);
+ a_cdb.push_back(t_cdb);
+ memset(t_v4p, 0, sizeof(*t_v4p));
+ t_v4p->guard = 'Q';
+ t_v4p->flags = oflags;
+ t_v4p->request_len = cdbsz;
+ t_v4p->dout_xfer_len = num * clp->bs;
+ t_v4p->timeout = DEF_TIMEOUT;
+ t_v4p->usr_ptr = num; /* pass number blocks requested */
+ out_mrq_q_blks += num;
+ t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+ a_v4.push_back(t_v4);
+
+ if (clp->verbose > 5) {
+ pr2serr_lk("%s: t=%d: a_v4 array contents:\n", __func__, id);
+ hex2stderr_lk((const uint8_t *)a_v4.data(),
+ a_v4.size() * sizeof(struct sg_io_v4), 1);
+ }
+ i_sg_it.add_blks(num);
+ o_sg_it.add_blks(num);
+ }
+
+ if (rep->both_sg || rep->same_sg)
+ fd = rep->infd; /* assume share to rep->outfd */
+ else if (rep->only_in_sg)
+ fd = rep->infd;
+ else if (rep->only_out_sg)
+ fd = rep->outfd;
+ else {
+ pr2serr_lk("[%d] %s: why am I here? No sg devices\n", id, __func__);
+ res = -1;
+ goto fini;
+ }
+ num_mrq = a_v4.size();
+ a_v4p = a_v4.data();
+ res = 0;
+ memset(&ctl_v4, 0, sizeof(ctl_v4));
+ ctl_v4.guard = 'Q';
+ ctl_v4.request_len = a_cdb.size() * max_cdb_sz;
+ ctl_v4.request = (uint64_t)a_cdb.data();
+ ctl_v4.max_response_len = sizeof(rep->sb);
+ ctl_v4.response = (uint64_t)rep->sb;
+ ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_STOP_IF |
+ SGV4_FLAG_SHARE;
+ if ((! clp->verify) && clp->out_flags.order)
+ ctl_v4.flags |= SGV4_FLAG_ORDERED_SLV;
+ ctl_v4.dout_xferp = (uint64_t)a_v4.data(); /* request array */
+ ctl_v4.dout_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ ctl_v4.din_xferp = (uint64_t)a_v4.data(); /* response array */
+ ctl_v4.din_xfer_len = a_v4.size() * sizeof(struct sg_io_v4);
+ if (false /* allow_mrq_abort */)
+ ctl_v4.request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off;
+
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object _before_ ioctl(SG_IO%s):\n",
+ __func__, iosub_str);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk("Controlling object before", &ctl_v4, id);
+ }
+
+try_again:
+ if (!after1 && (clp->verbose > 1)) {
+ after1 = true;
+ pr2serr_lk("%s: %s\n", __func__, mrq_svb_s);
+ }
+ res = ioctl(fd, SG_IOSUBMIT, &ctl_v4);
+ if (res < 0) {
+ int err = errno;
+
+ if (E2BIG == err)
+ sg_take_snap(fd, id, true);
+ else if (EBUSY == err) {
+ ++num_ebusy;
+ std::this_thread::yield();/* allow another thread to progress */
+ goto try_again;
+ }
+ pr2serr_lk("%s: ioctl(SG_IO%s, %s)-->%d, errno=%d: %s\n", __func__,
+ iosub_str, sg_flags_str(ctl_v4.flags, b_len, b), res, err,
+ strerror(err));
+ res = -err;
+ goto fini;
+ }
+ if (clp->verbose > 4) {
+ pr2serr_lk("%s: Controlling object output by ioctl(%s):\n", __func__,
+ iosub_str);
+ if (clp->verbose > 5)
+ hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
+ v4hdr_out_lk("Controlling object after", &ctl_v4, id);
+ if (clp->verbose > 5) {
+ for (k = 0; k < num_mrq; ++k) {
+ pr2serr_lk("AFTER: def_arr[%d]:\n", k);
+ v4hdr_out_lk("normal v4 object", (a_v4p + k), id);
+ // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p),
+ // 1);
+ }
+ }
+ }
+ num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks,
+ out_fin_blks);
+ if (clp->verbose > 2)
+ pr2serr_lk("%s: >>> seg_blks=%d, num_good=%d, in_q/fin blks=%u/%u; "
+ "out_q/fin blks=%u/%u\n", __func__, o_seg_blks, num_good,
+ in_mrq_q_blks, in_fin_blks, out_mrq_q_blks, out_fin_blks);
+
+ if (num_good < 0)
+ res = -ENODATA;
+ else {
+ rep->in_local_count += in_fin_blks;
+ rep->out_local_count += out_fin_blks;
+
+ if (num_good < num_mrq) {
+ int resid_blks = in_mrq_q_blks - in_fin_blks;
+
+ if (resid_blks > 0)
+ rep->in_rem_count += resid_blks;
+ resid_blks = out_mrq_q_blks - out_fin_blks;
+ if (resid_blks > 0)
+ rep->out_rem_count += resid_blks;
+ rep->stop_after_write = true;
+ }
+ }
+fini:
+ return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks));
+}
+
+/* Returns reserved_buffer_size/mmap_size if success, else 0 for failure */
+static int
+sg_prepare_resbuf(int fd, int bs, int bpt, bool unit_nano, bool no_dur,
+ bool masync, bool wq_excl, uint8_t **mmpp)
+{
+ static bool done = false;
+ int res, t, num;
+ uint8_t *mmp;
+ struct sg_extended_info sei;
+ struct sg_extended_info * seip;
+
+ seip = &sei;
+ res = ioctl(fd, SG_GET_VERSION_NUM, &t);
+ if ((res < 0) || (t < 40000)) {
+ if (ioctl(fd, SG_GET_RESERVED_SIZE, &num) < 0) {
+ perror("SG_GET_RESERVED_SIZE ioctl failed");
+ return 0;
+ }
+ if (! done) {
+ done = true;
+ pr2serr_lk("%ssg driver prior to 4.0.00, reduced functionality\n",
+ my_name);
+ }
+ goto bypass;
+ }
+ if (no_dur || masync) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ if (no_dur) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_NO_DURATION;
+ seip->ctl_flags |= SG_CTL_FLAGM_NO_DURATION;
+ }
+ if (masync) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MORE_ASYNC;
+ seip->ctl_flags |= SG_CTL_FLAGM_MORE_ASYNC;
+ }
+ if (wq_excl) {
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_EXCL_WAITQ;
+ seip->ctl_flags |= SG_CTL_FLAGM_EXCL_WAITQ;
+ }
+ res = ioctl(fd, SG_SET_GET_EXTENDED, seip);
+ if (res < 0)
+ pr2serr_lk("sgh_dd: %s: SG_SET_GET_EXTENDED(NO_DURATION) "
+ "error: %s\n", __func__, strerror(errno));
+ }
+bypass:
+ num = bs * bpt;
+ res = ioctl(fd, SG_SET_RESERVED_SIZE, &num);
+ if (res < 0) {
+ perror("sgh_dd: SG_SET_RESERVED_SIZE error");
+ return 0;
+ } else {
+ int nn;
+
+ res = ioctl(fd, SG_GET_RESERVED_SIZE, &nn);
+ if (res < 0) {
+ perror("sgh_dd: SG_GET_RESERVED_SIZE error");
+ return 0;
+ }
+ if (nn < num) {
+ pr2serr_lk("%s: SG_GET_RESERVED_SIZE shows size truncated, "
+ "wanted %d got %d\n", __func__, num, nn);
+ return 0;
+ }
+ if (mmpp) {
+ mmp = (uint8_t *)mmap(NULL, num, PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (MAP_FAILED == mmp) {
+ int err = errno;
+
+ pr2serr_lk("sgh_dd: %s: sz=%d, fd=%d, mmap() failed: %s\n",
+ __func__, num, fd, strerror(err));
+ return 0;
+ }
+ *mmpp = mmp;
+ }
+ }
+ t = 1;
+ res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t);
+ if (res < 0)
+ perror("sgh_dd: SG_SET_FORCE_PACK_ID error");
+ if (unit_nano) {
+ memset(seip, 0, sizeof(*seip));
+ seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
+ seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
+ seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
+ if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
+ res = -1;
+ pr2serr_lk("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n",
+ errno, strerror(errno));
+ }
+ }
+ t = 1;
+ res = ioctl(fd, SG_SET_DEBUG, &t); /* more info in /proc/scsi/sg/debug */
+ if (res < 0)
+ perror("sgh_dd: SG_SET_DEBUG error");
+ return (res < 0) ? 0 : num;
+}
+
+/* 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;
+}
+
+/* Returns the number of times either 'ch1' or 'ch2' is found in
+ * string 's' given the string's length. */
+int
+num_either_ch_in_str(const char * s, int slen, int ch1, int ch2)
+{
+ int k;
+ int res = 0;
+
+ while (--slen >= 0) {
+ k = s[slen];
+ if ((ch1 == k) || (ch2 == k))
+ ++res;
+ }
+ return res;
+}
+
+/* Allocates and then populates a scatter gether list (array) and returns
+ * it via *sgl_pp. Return of 0 is okay, else error number (in which case
+ * NULL is written to *sgl_pp) . */
+static int
+skip_seek(struct global_collection *clp, const char * key, const char * buf,
+ bool is_skip, bool ignore_verbose)
+{
+ bool def_hex = false;
+ int len, err;
+ int vb = clp->verbose; /* needs to appear before skip/seek= on cl */
+ int64_t ll;
+ const char * cp;
+ class scat_gath_list & either_list = is_skip ? clp->i_sgl : clp->o_sgl;
+
+ if (ignore_verbose)
+ vb = 0;
+ len = (int)strlen(buf);
+ if ((('-' == buf[0]) && (1 == len)) || ((len > 1) && ('@' == buf[0])) ||
+ ((len > 2) && ('H' == toupper(buf[0])) && ('@' == buf[1]))) {
+ if ('H' == toupper(buf[0])) {
+ cp = buf + 2;
+ def_hex = true;
+ } else if ('-' == buf[0])
+ cp = buf;
+ else
+ cp = buf + 1;
+ if (! either_list.load_from_file(cp, def_hex, clp->flexible, true)) {
+ pr2serr("bad argument to '%s=' [err=%d]\n", key,
+ either_list.m_errno);
+ return err ? err : SG_LIB_SYNTAX_ERROR;
+ }
+ } else if (num_either_ch_in_str(buf, len, ',', ' ') > 0) {
+ if (! either_list.load_from_cli(buf, vb > 0)) {
+ pr2serr("bad command line argument to '%s='\n", key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else { /* single number on command line (e.g. skip=1234) */
+ ll = sg_get_llnum(buf);
+ if (-1LL == ll) {
+ pr2serr("bad argument to '%s='\n", key);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ either_list.append_1or(0, ll);
+ if (vb > 1)
+ pr2serr("%s: singleton, half a degenerate sgl element\n", key);
+ }
+
+ either_list.sum_scan(key, vb > 3 /* bool show_sgl */, vb > 1);
+#if 0
+ if (vb > 3) {
+ pr2serr("%s: scatter gathet list:\n", is_skip ? ("skip" : "seek"));
+ either_list.dbg_print(false, is_skip ? ("skip" : "seek"), false,
+ bool show_sgl)
+#endif
+ return 0;
+}
+
+static bool
+process_flags(const char * arg, struct flags_t * fp)
+{
+ char buff[256];
+ char * cp;
+ char * np;
+
+ strncpy(buff, arg, sizeof(buff));
+ buff[sizeof(buff) - 1] = '\0';
+ if ('\0' == buff[0]) {
+ pr2serr("no flag found\n");
+ return false;
+ }
+ cp = buff;
+ do {
+ np = strchr(cp, ',');
+ if (np)
+ *np++ = '\0';
+ if (0 == strcmp(cp, "00"))
+ fp->zero = true;
+ else if (0 == strcmp(cp, "append"))
+ fp->append = true;
+ else if (0 == strcmp(cp, "coe"))
+ fp->coe = true;
+ else if (0 == strcmp(cp, "dio"))
+ fp->dio = true;
+ else if (0 == strcmp(cp, "direct"))
+ fp->direct = true;
+ else if (0 == strcmp(cp, "dpo"))
+ fp->dpo = true;
+ else if (0 == strcmp(cp, "dsync"))
+ fp->dsync = true;
+ else if (0 == strcmp(cp, "excl"))
+ fp->excl = true;
+ else if (0 == strcmp(cp, "ff"))
+ fp->ff = true;
+ else if (0 == strcmp(cp, "fua"))
+ fp->fua = true;
+ else if (0 == strcmp(cp, "masync"))
+ fp->masync = true;
+ else if (0 == strcmp(cp, "mmap"))
+ ++fp->mmap; /* mmap > 1 stops munmap() being called */
+ else if (0 == strcmp(cp, "nodur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "no_dur"))
+ fp->no_dur = true;
+ else if (0 == strcmp(cp, "noxfer"))
+ ; /* accept but ignore */
+ else if (0 == strcmp(cp, "null"))
+ ;
+ else if (0 == strcmp(cp, "ordered"))
+ fp->order = true;
+ else if (0 == strcmp(cp, "order"))
+ fp->order = true;
+ else if (0 == strcmp(cp, "qhead"))
+ fp->qhead = true;
+ else if (0 == strcmp(cp, "qtail"))
+ fp->qtail = true;
+ else if (0 == strcmp(cp, "random"))
+ fp->random = true;
+ else if (0 == strcmp(cp, "serial"))
+ fp->serial = true;
+ else if (0 == strcmp(cp, "swait"))
+ ; /* accept but ignore */
+ else if (0 == strcmp(cp, "wq_excl"))
+ fp->wq_excl = true;
+ else {
+ pr2serr("unrecognised flag: %s\n", cp);
+ return false;
+ }
+ cp = np;
+ } while (cp);
+ return true;
+}
+
+static int
+sg_in_open(struct global_collection *clp, const char *inf, uint8_t **mmpp,
+ int * mmap_lenp)
+{
+ int fd, err, n;
+ int flags = O_RDWR;
+ char ebuff[EBUFF_SZ];
+
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg reading",
+ __func__, inf);
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ n = sg_prepare_resbuf(fd, clp->bs, clp->bpt, clp->unit_nanosec,
+ clp->in_flags.no_dur, clp->in_flags.masync,
+ clp->in_flags.wq_excl, mmpp);
+ if (n <= 0)
+ return -SG_LIB_FILE_ERROR;
+ if (mmap_lenp)
+ *mmap_lenp = n;
+ return fd;
+}
+
+static int
+sg_out_open(struct global_collection *clp, const char *outf, uint8_t **mmpp,
+ int * mmap_lenp)
+{
+ int fd, err, n;
+ int flags = O_RDWR;
+ char ebuff[EBUFF_SZ];
+
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((fd = open(outf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg %s",
+ __func__, outf, (clp->verify ? "verifying" : "writing"));
+ perror(ebuff);
+ return -sg_convert_errno(err);
+ }
+ n = sg_prepare_resbuf(fd, clp->bs, clp->bpt, clp->unit_nanosec,
+ clp->out_flags.no_dur, clp->out_flags.masync,
+ clp->out_flags.wq_excl, mmpp);
+ if (n <= 0)
+ return -SG_LIB_FILE_ERROR;
+ if (mmap_lenp)
+ *mmap_lenp = n;
+ return fd;
+}
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+
+static int
+parse_cmdline_sanity(int argc, char * argv[], struct global_collection * clp,
+ char * inf, char * outf, char * out2f, char * outregf)
+{
+ bool contra = false;
+ bool verbose_given = false;
+ bool version_given = false;
+ bool verify_given = false;
+ bool bpt_given = false;
+ int ibs = 0;
+ int obs = 0;
+ int k, keylen, n, res;
+ char str[STR_SZ];
+ char * key;
+ char * buf;
+ char * skip_buf = NULL;
+ char * seek_buf = NULL;
+ const char * cp;
+
+ for (k = 1; k < argc; k++) {
+ if (argv[k]) {
+ strncpy(str, argv[k], STR_SZ);
+ str[STR_SZ - 1] = '\0';
+ } else
+ continue;
+
+ for (key = str, buf = key; *buf && *buf != '=';)
+ buf++;
+ if (*buf)
+ *buf++ = '\0';
+ keylen = strlen(key);
+ if (0 == strcmp(key, "bpt")) {
+ clp->bpt = sg_get_num(buf);
+ if (-1 == clp->bpt) {
+ pr2serr("%sbad argument to 'bpt='\n", my_name);
+ goto syn_err;
+ }
+ bpt_given = true;
+ } else if (0 == strcmp(key, "bs")) {
+ clp->bs = sg_get_num(buf);
+ if (-1 == clp->bs) {
+ pr2serr("%sbad argument to 'bs='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "cdbsz")) {
+ clp->cdbsz_in = sg_get_num(buf);
+ clp->cdbsz_out = clp->cdbsz_in;
+ clp->cdbsz_given = true;
+ } else if (0 == strcmp(key, "coe")) {
+ clp->in_flags.coe = !! sg_get_num(buf);
+ clp->out_flags.coe = clp->in_flags.coe;
+ } else if (0 == strcmp(key, "count")) {
+ if (clp->count_given) {
+ pr2serr("second 'count=' argument detected, only one "
+ "please\n");
+ contra = true;
+ goto syn_err;
+ }
+ if (0 != strcmp("-1", buf)) {
+ clp->dd_count = sg_get_llnum(buf);
+ if (-1LL == clp->dd_count) {
+ pr2serr("%sbad argument to 'count='\n", my_name);
+ goto syn_err;
+ }
+ } /* treat 'count=-1' as calculate count (same as not given) */
+ clp->count_given = true;
+ } else if ((0 == strncmp(key, "deb", 3)) ||
+ (0 == strncmp(key, "verb", 4)))
+ clp->verbose = sg_get_num(buf);
+ else if (0 == strcmp(key, "dio")) {
+ clp->in_flags.dio = !! sg_get_num(buf);
+ clp->out_flags.dio = clp->in_flags.dio;
+ } else if (0 == strcmp(key, "fua")) {
+ n = sg_get_num(buf);
+ if (n & 1)
+ clp->out_flags.fua = true;
+ if (n & 2)
+ clp->in_flags.fua = true;
+ } else if (0 == strcmp(key, "ibs")) {
+ ibs = sg_get_num(buf);
+ if (-1 == ibs) {
+ pr2serr("%sbad argument to 'ibs='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "if")) {
+ if ('\0' != inf[0]) {
+ pr2serr("Second 'if=' argument??\n");
+ goto syn_err;
+ } else {
+ memcpy(inf, buf, INOUTF_SZ);
+ inf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (0 == strcmp(key, "iflag")) {
+ if (! process_flags(buf, &clp->in_flags)) {
+ pr2serr("%sbad argument to 'iflag='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "mrq")) {
+ if (isdigit(buf[0]))
+ cp = buf;
+ else {
+ pr2serr("%sonly mrq=NRQS which is a number allowed here\n",
+ my_name);
+ goto syn_err;
+ }
+ clp->mrq_num = sg_get_num(cp);
+ if (clp->mrq_num < 0) {
+ pr2serr("%sbad argument to 'mrq='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "obs")) {
+ obs = sg_get_num(buf);
+ if (-1 == obs) {
+ pr2serr("%sbad argument to 'obs='\n", my_name);
+ goto syn_err;
+ }
+ } else if (strcmp(key, "of2") == 0) {
+ if ('\0' != out2f[0]) {
+ pr2serr("Second OFILE2 argument??\n");
+ contra = true;
+ goto syn_err;
+ } else {
+ memcpy(out2f, buf, INOUTF_SZ);
+ out2f[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (strcmp(key, "ofreg") == 0) {
+ if ('\0' != outregf[0]) {
+ pr2serr("Second OFREG argument??\n");
+ contra = true;
+ goto syn_err;
+ } else {
+ memcpy(outregf, buf, INOUTF_SZ);
+ outregf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (strcmp(key, "of") == 0) {
+ if ('\0' != outf[0]) {
+ pr2serr("Second 'of=' argument??\n");
+ goto syn_err;
+ } else {
+ memcpy(outf, buf, INOUTF_SZ);
+ outf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */
+ }
+ } else if (0 == strcmp(key, "oflag")) {
+ if (! process_flags(buf, &clp->out_flags)) {
+ pr2serr("%sbad argument to 'oflag='\n", my_name);
+ goto syn_err;
+ }
+ } else if (0 == strcmp(key, "seek")) {
+ n = strlen(buf);
+ if (n < 1) {
+ pr2serr("%sneed argument to 'seek='\n", my_name);
+ goto syn_err;
+ }
+ seek_buf = (char *)calloc(n + 16, 1);
+ memcpy(seek_buf, buf, n + 1);
+ } else if (0 == strcmp(key, "skip")) {
+ n = strlen(buf);
+ if (n < 1) {
+ pr2serr("%sneed argument to 'skip='\n", my_name);
+ goto syn_err;
+ }
+ skip_buf = (char *)calloc(n + 16, 1);
+ memcpy(skip_buf, buf, n + 1);
+ } else if (0 == strcmp(key, "sync"))
+ do_sync = !! sg_get_num(buf);
+ else if (0 == strcmp(key, "thr"))
+ num_threads = 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, 'd');
+ clp->dry_run += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'h');
+ clp->help += n;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'p');
+ if (n > 0)
+ clp->prefetch = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'v');
+ if (n > 0)
+ verbose_given = true;
+ clp->verbose += n; /* -v ---> --verbose */
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'V');
+ if (n > 0)
+ version_given = true;
+ res += n;
+ n = num_chs_in_str(key + 1, keylen - 1, 'x');
+ if (n > 0)
+ verify_given = true;
+ res += n;
+
+ if (res < (keylen - 1)) {
+ pr2serr("Unrecognised short option in '%s', try '--help'\n",
+ key);
+ goto syn_err;
+ }
+ } else if ((0 == strncmp(key, "--dry-run", 9)) ||
+ (0 == strncmp(key, "--dry_run", 9)))
+ ++clp->dry_run;
+ else if ((0 == strncmp(key, "--help", 6)) ||
+ (0 == strcmp(key, "-?")))
+ ++clp->help;
+ else if ((0 == strncmp(key, "--prefetch", 10)) ||
+ (0 == strncmp(key, "--pre-fetch", 11)))
+ clp->prefetch = true;
+ else if (0 == strncmp(key, "--verb", 6)) {
+ verbose_given = true;
+ ++clp->verbose; /* --verbose */
+ } else if (0 == strncmp(key, "--veri", 6))
+ verify_given = true;
+ else if (0 == strncmp(key, "--vers", 6))
+ version_given = true;
+ else {
+ pr2serr("Unrecognized option '%s'\n", key);
+ pr2serr("For more information use '--help'\n");
+ goto syn_err;
+ }
+ } /* end of parsing for loop */
+
+ if (skip_buf) {
+ res = skip_seek(clp, "skip", skip_buf, true /* skip */, false);
+ free(skip_buf);
+ skip_buf = NULL;
+ if (res) {
+ pr2serr("%sbad argument to 'seek='\n", my_name);
+ goto syn_err;
+ }
+ }
+ if (seek_buf) {
+ res = skip_seek(clp, "seek", seek_buf, false /* skip */, false);
+ free(seek_buf);
+ seek_buf = NULL;
+ if (res) {
+ pr2serr("%sbad argument to 'seek='\n", my_name);
+ goto syn_err;
+ }
+ }
+ /* heap usage should be all freed up now */
+
+#ifdef DEBUG
+ pr2serr("In DEBUG mode, ");
+ if (verbose_given && version_given) {
+ pr2serr("but override: '-vV' given, zero verbose and continue\n");
+ verbose_given = false;
+ version_given = false;
+ clp->verbose = 0;
+ } else if (! verbose_given) {
+ pr2serr("set '-vv'\n");
+ clp->verbose = 2;
+ } else
+ pr2serr("keep verbose=%d\n", clp->verbose);
+#else
+ if (verbose_given && version_given)
+ pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
+#endif
+ if (version_given) {
+ pr2serr("%s%s\n", my_name, version_str);
+ return SG_LIB_OK_FALSE;
+ }
+ if (clp->help > 0) {
+ usage(clp->help);
+ return SG_LIB_OK_FALSE;
+ }
+ if (clp->bs <= 0) {
+ clp->bs = DEF_BLOCK_SIZE;
+ pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n",
+ clp->bs);
+ }
+ if (verify_given) {
+ pr2serr("Doing verify/cmp rather than copy\n");
+ clp->verify = true;
+ }
+ if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) {
+ pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
+ usage(0);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->out_flags.append) {
+ if ((clp->o_sgl.lowest_lba > 0) ||
+ (clp->o_sgl.linearity != SGL_LINEAR)) {
+ pr2serr("Can't use both append and seek switches\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (verify_given) {
+ pr2serr("Can't use both append and verify switches\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (clp->bpt < 1) {
+ pr2serr("bpt must be greater than 0\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if (clp->in_flags.mmap && clp->out_flags.mmap) {
+ pr2serr("mmap flag on both IFILE and OFILE doesn't work\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+#if 0
+ if (clp->out_flags.mmap) {
+ pr2serr("oflag=mmap needs either noshare=1\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+#endif
+ /* defaulting transfer size to 128*2048 for CD/DVDs is too large
+ for the block layer in lk 2.6 and results in an EIO on the
+ SG_IO ioctl. So reduce it in that case. */
+ if ((clp->bs >= 2048) && (! bpt_given))
+ clp->bpt = DEF_BLOCKS_PER_2048TRANSFER;
+ if (clp->in_flags.order)
+ pr2serr("Warning iflag=order is ignored, use with oflag=\n");
+ if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) {
+ pr2serr("too few or too many threads requested\n");
+ usage(1);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ clp->unit_nanosec = (do_time > 1) || !!getenv("SG3_UTILS_LINUX_NANO");
+ return 0;
+
+syn_err:
+ if (seek_buf)
+ free(seek_buf);
+ if (skip_buf)
+ free(skip_buf);
+ return contra ? SG_LIB_CONTRADICT : SG_LIB_SYNTAX_ERROR;
+}
+
+static int
+calc_count(struct global_collection * clp, const char * inf,
+ int64_t & in_num_sect, const char * outf, int64_t & out_num_sect)
+{
+ int in_sect_sz, out_sect_sz, res;
+
+ if (clp->dd_count < 0) {
+ in_num_sect = -1;
+ out_num_sect = -1;
+ }
+ if (FT_SG == clp->in_type) {
+ res = scsi_read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(in), continuing\n");
+ res = scsi_read_capacity(clp->infd, &in_num_sect,
+ &in_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", inf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", inf);
+ else
+ pr2serr("Unable to read capacity on %s\n", inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (clp->bs != in_sect_sz) {
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", clp->infp, clp->bs,
+ in_sect_sz);
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+ if (FT_SG == clp->out_type) {
+ res = scsi_read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
+ if (2 == res) {
+ pr2serr("Unit attention, media changed(out), continuing\n");
+ res = scsi_read_capacity(clp->outfd, &out_num_sect,
+ &out_sect_sz);
+ }
+ if (0 != res) {
+ if (res == SG_LIB_CAT_INVALID_OP)
+ pr2serr("read capacity not supported on %s\n", outf);
+ else if (res == SG_LIB_CAT_NOT_READY)
+ pr2serr("read capacity failed, %s not ready\n", outf);
+ else
+ pr2serr("Unable to read capacity on %s\n", outf);
+ out_num_sect = -1;
+ return SG_LIB_FILE_ERROR;
+ } else if (clp->bs != out_sect_sz) {
+ pr2serr(">> warning: logical block size on %s confusion: "
+ "bs=%d, device claims=%d\n", clp->outfp, clp->bs,
+ out_sect_sz);
+ return SG_LIB_FILE_ERROR;
+ }
+ }
+
+ if (clp->dd_count < 0) {
+ if (FT_SG == clp->in_type)
+ ;
+ else if (FT_BLOCK == clp->in_type) {
+ if (0 != read_blkdev_capacity(clp->infd, &in_num_sect,
+ &in_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", inf);
+ in_num_sect = -1;
+ }
+ if (clp->bs != in_sect_sz) {
+ pr2serr("logical block size on %s confusion; bs=%d, from "
+ "device=%d\n", inf, clp->bs, in_sect_sz);
+ in_num_sect = -1;
+ }
+ }
+
+ if (FT_SG == clp->out_type)
+ ;
+ else if (FT_BLOCK == clp->out_type) {
+ if (0 != read_blkdev_capacity(clp->outfd, &out_num_sect,
+ &out_sect_sz)) {
+ pr2serr("Unable to read block capacity on %s\n", outf);
+ out_num_sect = -1;
+ }
+ if (clp->bs != out_sect_sz) {
+ pr2serr("logical block size on %s confusion: bs=%d, from "
+ "device=%d\n", outf, clp->bs, out_sect_sz);
+ out_num_sect = -1;
+ }
+ }
+ }
+ return 0;
+}
+
+static int
+do_count_work(struct global_collection * clp, const char * inf,
+ int64_t & in_num_sect, const char * outf,
+ int64_t & out_num_sect)
+{
+ int res;
+ class scat_gath_list * isglp = &clp->i_sgl;
+ class scat_gath_list * osglp = &clp->o_sgl;
+
+ res = calc_count(clp, inf, in_num_sect, outf, out_num_sect);
+ if (res)
+ return res;
+
+ if ((-1 == in_num_sect) && (FT_OTHER == clp->in_type)) {
+ in_num_sect = clp->in_st_size / clp->bs;
+ if (clp->in_st_size % clp->bs) {
+ ++in_num_sect;
+ pr2serr("Warning: the file size of %s is not a multiple of BS "
+ "[%d]\n", inf, clp->bs);
+ }
+ }
+ if ((in_num_sect > 0) && (isglp->high_lba_p1 > in_num_sect)) {
+ pr2serr("%shighest LBA [0x%" PRIx64 "] exceeds input length: %"
+ PRIx64 " blocks\n", my_name, isglp->high_lba_p1 - 1,
+ in_num_sect);
+ return SG_LIB_CAT_OTHER;
+ }
+ if ((out_num_sect > 0) && (osglp->high_lba_p1 > out_num_sect)) {
+ pr2serr("%shighest LBA [0x%" PRIx64 "] exceeds output length: %"
+ PRIx64 " blocks\n", my_name, osglp->high_lba_p1 - 1,
+ out_num_sect);
+ return SG_LIB_CAT_OTHER;
+ }
+
+ if (isglp->sum_hard || osglp->sum_hard) {
+ int64_t ccount;
+
+ if (isglp->sum_hard && osglp->sum_hard) {
+ if (isglp->sum != osglp->sum) {
+ pr2serr("%stwo hard sgl_s, sum of blocks differ: in=%" PRId64
+ ", out=%" PRId64 "\n", my_name , isglp->sum,
+ osglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ ccount = isglp->sum;
+ } else if (isglp->sum_hard) {
+ if (osglp->sum > isglp->sum) {
+ pr2serr("%soutput sgl already too many blocks [%" PRId64
+ "]\n", my_name, osglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (osglp->linearity != SGL_NON_MONOTONIC)
+ osglp->append_1or(isglp->sum - osglp->sum);
+ else {
+ pr2serr("%soutput sgl non-montonic: can't extend\n",
+ my_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ ccount = isglp->sum;
+ } else { /* only osglp hard */
+ if (isglp->sum > osglp->sum) {
+ pr2serr("%sinput sgl already too many blocks [%" PRId64
+ "]\n", my_name, isglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (isglp->linearity != SGL_NON_MONOTONIC)
+ isglp->append_1or(osglp->sum - isglp->sum);
+ else {
+ pr2serr("%sinput sgl non-monotonic: can't extend\n",
+ my_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ ccount = osglp->sum;
+ }
+ if (SG_COUNT_INDEFINITE == clp->dd_count)
+ clp->dd_count = ccount;
+ else if (ccount != clp->dd_count) {
+ pr2serr("%scount=COUNT disagrees with scatter gather list "
+ "length [%" PRId64 "]\n", my_name, ccount);
+ return SG_LIB_CAT_OTHER;
+ }
+ } else if (clp->dd_count != 0) { /* and both input and output are soft */
+ if (clp->dd_count > 0) {
+ if (isglp->sum > clp->dd_count) {
+ pr2serr("%sskip sgl sum [%" PRId64 "] exceeds COUNT\n",
+ my_name, isglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (osglp->sum > clp->dd_count) {
+ pr2serr("%sseek sgl sum [%" PRId64 "] exceeds COUNT\n",
+ my_name, osglp->sum);
+ return SG_LIB_CAT_OTHER;
+ }
+ goto fini;
+ }
+
+ /* clp->dd_count == SG_COUNT_INDEFINITE */
+ int64_t iposs = INT64_MAX;
+ int64_t oposs = INT64_MAX;
+
+ if (in_num_sect > 0)
+ iposs = in_num_sect + isglp->sum - isglp->high_lba_p1;
+ if (out_num_sect > 0)
+ oposs = out_num_sect + osglp->sum - osglp->high_lba_p1;
+ clp->dd_count = iposs < oposs ? iposs : oposs;
+ if (INT64_MAX == clp->dd_count) {
+ pr2serr("%scan't deduce count=COUNT, please supply one\n",
+ my_name);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (isglp->sum > clp->dd_count) {
+ pr2serr("%sdeduced COUNT [%" PRId64 "] exceeds skip sgl sum\n",
+ my_name, clp->dd_count);
+ return SG_LIB_CAT_OTHER;
+ }
+ if (osglp->sum > clp->dd_count) {
+ pr2serr("%sdeduced COUNT [%" PRId64 "] exceeds seek sgl sum\n",
+ my_name, clp->dd_count);
+ return SG_LIB_CAT_OTHER;
+ }
+ }
+ if (clp->dd_count == 0)
+ return 0;
+fini:
+ if (clp->dd_count > isglp->sum)
+ isglp->append_1or(clp->dd_count - isglp->sum);
+ if (clp->dd_count > osglp->sum)
+ osglp->append_1or(clp->dd_count - osglp->sum);
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ char inf[INOUTF_SZ];
+ char outf[INOUTF_SZ];
+ char out2f[INOUTF_SZ];
+ char outregf[INOUTF_SZ];
+ int res, k, err, flags;
+ int64_t in_num_sect = -1;
+ int64_t out_num_sect = -1;
+ const char * ccp = NULL;
+ const char * cc2p;
+ struct global_collection * clp = &gcoll;
+ thread sig_listen_thr;
+ vector<thread> work_thr;
+ vector<thread> listen_thr;
+ char ebuff[EBUFF_SZ];
+#if 0 /* SG_LIB_ANDROID */
+ struct sigaction actions;
+
+ memset(&actions, 0, sizeof(actions));
+ sigemptyset(&actions.sa_mask);
+ actions.sa_flags = 0;
+ actions.sa_handler = thread_exit_handler;
+ sigaction(SIGUSR1, &actions, NULL);
+ sigaction(SIGUSR2, &actions, NULL);
+#endif
+ /* memset(clp, 0, sizeof(*clp)); */
+ clp->dd_count = SG_COUNT_INDEFINITE;
+ clp->bpt = DEF_BLOCKS_PER_TRANSFER;
+ clp->in_type = FT_FIFO;
+ /* change dd's default: if of=OFILE not given, assume /dev/null */
+ clp->out_type = FT_DEV_NULL;
+ clp->out2_type = FT_DEV_NULL;
+ clp->cdbsz_in = DEF_SCSI_CDB_SZ;
+ clp->cdbsz_out = DEF_SCSI_CDB_SZ;
+ clp->mrq_num = DEF_MRQ_NUM;
+ inf[0] = '\0';
+ outf[0] = '\0';
+ out2f[0] = '\0';
+ outregf[0] = '\0';
+ fetch_sg_version();
+ if (sg_version >= 40030)
+ sg_version_ge_40030 = true;
+ else {
+ pr2serr("%srequires an sg driver version of 4.0.30 or later\n",
+ my_name);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+
+ res = parse_cmdline_sanity(argc, argv, clp, inf, outf, out2f, outregf);
+ if (SG_LIB_OK_FALSE == res)
+ return 0;
+ if (res)
+ return res;
+
+ install_handler(SIGINT, interrupt_handler);
+ install_handler(SIGQUIT, interrupt_handler);
+ install_handler(SIGPIPE, interrupt_handler);
+ install_handler(SIGUSR1, siginfo_handler);
+ install_handler(SIGUSR2, siginfo2_handler);
+
+ clp->infd = STDIN_FILENO;
+ clp->outfd = STDOUT_FILENO;
+ if (clp->in_flags.ff) {
+ ccp = "<0xff bytes>";
+ cc2p = "ff";
+ } else if (clp->in_flags.random) {
+ ccp = "<random>";
+ cc2p = "random";
+ } else if (clp->in_flags.zero) {
+ ccp = "<zero bytes>";
+ cc2p = "00";
+ }
+ if (ccp) {
+ if (inf[0]) {
+ pr2serr("%siflag=%s and if=%s contradict\n", my_name, cc2p, inf);
+ return SG_LIB_CONTRADICT;
+ }
+ clp->in_type = FT_RANDOM_0_FF;
+ clp->infp = ccp;
+ clp->infd = -1;
+ } else if (inf[0] && ('-' != inf[0])) {
+ clp->in_type = dd_filetype(inf, clp->in_st_size);
+
+ if (FT_ERROR == clp->in_type) {
+ pr2serr("%sunable to access %s\n", my_name, inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_ST == clp->in_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, inf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->in_type) {
+ clp->infd = sg_in_open(clp, inf, NULL, NULL);
+ if (clp->infd < 0)
+ return -clp->infd;
+ } else {
+ flags = O_RDONLY;
+ if (clp->in_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->in_flags.excl)
+ flags |= O_EXCL;
+ if (clp->in_flags.dsync)
+ flags |= O_SYNC;
+
+ if ((clp->infd = open(inf, flags)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for reading",
+ my_name, inf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ clp->infp = inf;
+ }
+ if (outf[0]) {
+ clp->ofile_given = true;
+ if (('-' == outf[0]))
+ clp->out_type = FT_FIFO;
+ else
+ clp->out_type = dd_filetype(outf, clp->out_st_size);
+
+ if ((FT_SG != clp->out_type) && clp->verify) {
+ pr2serr("%s --verify only supported by sg OFILEs\n", my_name);
+ return SG_LIB_FILE_ERROR;
+ }
+ if (FT_FIFO == clp->out_type)
+ ;
+ else if (FT_ST == clp->out_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, outf);
+ return SG_LIB_FILE_ERROR;
+ } else if (FT_SG == clp->out_type) {
+ clp->outfd = sg_out_open(clp, outf, NULL, NULL);
+ if (clp->outfd < 0)
+ return -clp->outfd;
+ } else if (FT_DEV_NULL == clp->out_type)
+ clp->outfd = -1; /* don't bother opening */
+ else {
+ if (FT_RAW != clp->out_type) {
+ flags = O_WRONLY | O_CREAT;
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+ if (clp->out_flags.append)
+ flags |= O_APPEND;
+
+ if ((clp->outfd = open(outf, flags, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for "
+ "writing", my_name, outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ else { /* raw output file */
+ if ((clp->outfd = open(outf, O_WRONLY)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for raw "
+ "writing", my_name, outf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ clp->outfp = outf;
+ }
+
+ if (out2f[0])
+ clp->ofile2_given = true;
+ if (out2f[0] && ('-' != out2f[0])) {
+ clp->out2_type = dd_filetype(out2f, clp->out2_st_size);
+
+ if (FT_ST == clp->out2_type) {
+ pr2serr("%sunable to use scsi tape device %s\n", my_name, out2f);
+ return SG_LIB_FILE_ERROR;
+ }
+ else if (FT_SG == clp->out2_type) {
+ clp->out2fd = sg_out_open(clp, out2f, NULL, NULL);
+ if (clp->out2fd < 0)
+ return -clp->out2fd;
+ }
+ else if (FT_DEV_NULL == clp->out2_type)
+ clp->out2fd = -1; /* don't bother opening */
+ else {
+ if (FT_RAW != clp->out2_type) {
+ flags = O_WRONLY | O_CREAT;
+ if (clp->out_flags.direct)
+ flags |= O_DIRECT;
+ if (clp->out_flags.excl)
+ flags |= O_EXCL;
+ if (clp->out_flags.dsync)
+ flags |= O_SYNC;
+ if (clp->out_flags.append)
+ flags |= O_APPEND;
+
+ if ((clp->out2fd = open(out2f, flags, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for "
+ "writing", my_name, out2f);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ else { /* raw output file */
+ if ((clp->out2fd = open(out2f, O_WRONLY)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scould not open %s for raw "
+ "writing", my_name, out2f);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ if (clp->o_sgl.lowest_lba > 0) {
+ off64_t offset = clp->o_sgl.lowest_lba;
+
+ offset *= clp->bs; /* could exceed 32 bits here! */
+ if (lseek64(clp->out2fd, offset, SEEK_SET) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "%scouldn't seek to required "
+ "position on %s", my_name, out2f);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ }
+ }
+ clp->out2fp = out2f;
+ }
+ if ((FT_SG == clp->in_type ) && (FT_SG == clp->out_type)) {
+ ;
+ } else if (clp->in_flags.order)
+ pr2serr("Warning: oflag=order only active on sg->sg copies\n");
+
+ if (outregf[0]) {
+ int ftyp = dd_filetype(outregf, clp->outreg_st_size);
+
+ clp->outreg_type = ftyp;
+ if (! ((FT_OTHER == ftyp) || (FT_ERROR == ftyp) ||
+ (FT_DEV_NULL == ftyp))) {
+ pr2serr("File: %s can only be regular file or pipe (or "
+ "/dev/null)\n", outregf);
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->outregfd = open(outregf, O_WRONLY | O_CREAT, 0666)) < 0) {
+ err = errno;
+ snprintf(ebuff, EBUFF_SZ, "could not open %s for writing",
+ outregf);
+ perror(ebuff);
+ return sg_convert_errno(err);
+ }
+ if (clp->verbose > 1)
+ pr2serr("ofreg=%s opened okay, fd=%d\n", outregf, clp->outregfd);
+ if (FT_ERROR == ftyp)
+ clp->outreg_type = FT_OTHER; /* regular file created */
+ } else
+ clp->outregfd = -1;
+
+ if ((STDIN_FILENO == clp->infd) && (STDOUT_FILENO == clp->outfd)) {
+ pr2serr("Won't default both IFILE to stdin _and_ OFILE to "
+ "/dev/null\n");
+ pr2serr("For more information use '--help'\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->in_type == FT_FIFO) && (! clp->i_sgl.is_pipe_suitable())) {
+ pr2serr("The skip= argument is not suitable for a pipe\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ if ((clp->out_type == FT_FIFO) && (! clp->o_sgl.is_pipe_suitable())) {
+ pr2serr("The seek= argument is not suitable for a pipe\n");
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ res = do_count_work(clp, inf, in_num_sect, outf, out_num_sect);
+ if (res)
+ return res;
+
+ if (clp->verbose > 2)
+ pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64
+ ", out_num_sect=%" PRId64 "\n", clp->dd_count, in_num_sect,
+ out_num_sect);
+ if (clp->dd_count < 0) {
+ pr2serr("Couldn't calculate count, please give one\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ if (! clp->cdbsz_given) {
+ if ((FT_SG == clp->in_type) && (MAX_SCSI_CDB_SZ != clp->cdbsz_in) &&
+ ((clp->i_sgl.high_lba_p1 > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'if')\n");
+ clp->cdbsz_in = MAX_SCSI_CDB_SZ;
+ }
+ if ((FT_SG == clp->out_type) && (MAX_SCSI_CDB_SZ != clp->cdbsz_out) &&
+ ((clp->o_sgl.high_lba_p1 > UINT_MAX) || (clp->bpt > USHRT_MAX))) {
+ pr2serr("Note: SCSI command size increased to 16 bytes (for "
+ "'of')\n");
+ clp->cdbsz_out = MAX_SCSI_CDB_SZ;
+ }
+ }
+
+ clp->in_rem_count = clp->dd_count;
+ clp->out_rem_count = clp->dd_count;
+#if 0
+ status = pthread_mutex_init(&clp->out2_mutex, NULL);
+ if (0 != status) err_exit(status, "init out2_mutex");
+#endif
+
+ if (clp->dry_run > 0) {
+ pr2serr("Due to --dry-run option, bypass copy/read\n");
+ goto fini;
+ }
+ if (! clp->ofile_given)
+ pr2serr("of=OFILE not given so only read from IFILE, to output to "
+ "stdout use 'of=-'\n");
+
+ sigemptyset(&signal_set);
+ sigaddset(&signal_set, SIGINT);
+#if 0
+ status = pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
+ if (0 != status) err_exit(status, "pthread_sigmask");
+#endif
+
+ res = sigprocmask(SIG_BLOCK, &signal_set, NULL);
+ if (res < 0) {
+ pr2serr("sigprocmask failed: %s\n", safe_strerror(errno));
+ goto fini;
+ }
+
+#if 0
+ status = pthread_create(&sig_listen_thread_id, NULL,
+ sig_listen_thread, (void *)clp);
+ if (0 != status) err_exit(status, "pthread_create, sig...");
+#endif
+
+ listen_thr.emplace_back(sig_listen_thread, clp);
+
+ if (do_time) {
+ start_tm.tv_sec = 0;
+ start_tm.tv_usec = 0;
+ gettimeofday(&start_tm, NULL);
+ }
+
+/* vvvvvvvvvvv Start worker threads vvvvvvvvvvvvvvvvvvvvvvvv */
+ if (num_threads > 0) {
+ /* launch "infant" thread to catch early mortality, if any */
+ work_thr.emplace_back(read_write_thread, clp, 0, true);
+ {
+ unique_lock<mutex> lk(clp->infant_mut);
+ clp->infant_cv.wait(lk, []{ return gcoll.processed; });
+ }
+ if (clp->next_count_pos.load() < 0) {
+ /* infant thread error-ed out, join with it */
+ for (auto & t : work_thr) {
+ if (t.joinable())
+ t.join();
+ }
+ goto jump;
+ }
+
+ /* now start the rest of the threads */
+ for (k = 1; k < num_threads; ++k)
+ work_thr.emplace_back(read_write_thread, clp, k, false);
+
+ /* now wait for worker threads to finish */
+ for (auto & t : work_thr) {
+ if (t.joinable())
+ t.join();
+ }
+ } /* started worker threads and hereafter they have all exited */
+jump:
+ if (do_time && (start_tm.tv_sec || start_tm.tv_usec))
+ calc_duration_throughput(0);
+
+ if (do_sync) {
+ if (FT_SG == clp->out_type) {
+ pr2serr_lk(">> Synchronizing cache on %s\n", outf);
+ res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr_lk("Unit attention(out), continuing\n");
+ res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false,
+ 0);
+ }
+ if (0 != res)
+ pr2serr_lk("Unable to synchronize cache\n");
+ }
+ if (FT_SG == clp->out2_type) {
+ pr2serr_lk(">> Synchronizing cache on %s\n", out2f);
+ res = sg_ll_sync_cache_10(clp->out2fd, 0, 0, 0, 0, 0, false, 0);
+ if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+ pr2serr_lk("Unit attention(out2), continuing\n");
+ res = sg_ll_sync_cache_10(clp->out2fd, 0, 0, 0, 0, 0, false,
+ 0);
+ }
+ if (0 != res)
+ pr2serr_lk("Unable to synchronize cache (of2)\n");
+ }
+ }
+
+ shutting_down = true;
+ for (auto & t : listen_thr) {
+ if (t.joinable()) {
+ t.detach();
+ t.~thread(); /* kill listening thread */
+ }
+ }
+
+fini:
+
+ if ((STDIN_FILENO != clp->infd) && (clp->infd >= 0))
+ close(clp->infd);
+ if ((STDOUT_FILENO != clp->outfd) && (FT_DEV_NULL != clp->out_type) &&
+ (clp->outfd >= 0))
+ close(clp->outfd);
+ if ((clp->out2fd >= 0) && (STDOUT_FILENO != clp->out2fd) &&
+ (FT_DEV_NULL != clp->out2_type))
+ close(clp->out2fd);
+ if ((clp->outregfd >= 0) && (STDOUT_FILENO != clp->outregfd) &&
+ (FT_DEV_NULL != clp->outreg_type))
+ close(clp->outregfd);
+ res = exit_status;
+#if 0
+ if ((0 != clp->out_count.load()) && (0 == clp->dry_run)) {
+ pr2serr(">>>> Some error occurred, remaining blocks=%" PRId64 "\n",
+ clp->out_count.load());
+ if (0 == res)
+ res = SG_LIB_CAT_OTHER;
+ }
+#endif
+ print_stats("");
+ if (clp->dio_incomplete_count.load()) {
+ int fd;
+ char c;
+
+ pr2serr(">> Direct IO requested but incomplete %d times\n",
+ clp->dio_incomplete_count.load());
+ if ((fd = open(proc_allow_dio, O_RDONLY)) >= 0) {
+ if (1 == read(fd, &c, 1)) {
+ if ('0' == c)
+ pr2serr(">>> %s set to '0' but should be set to '1' for "
+ "direct IO\n", proc_allow_dio);
+ }
+ close(fd);
+ }
+ }
+ if (clp->sum_of_resids.load())
+ pr2serr(">> Non-zero sum of residual counts=%d\n",
+ clp->sum_of_resids.load());
+ if (clp->verbose && (num_start_eagain > 0))
+ pr2serr("Number of start EAGAINs: %d\n", num_start_eagain.load());
+ if (clp->verbose && (num_fin_eagain > 0))
+ pr2serr("Number of finish EAGAINs: %d\n", num_fin_eagain.load());
+ if (clp->verbose && (num_ebusy > 0))
+ pr2serr("Number of EBUSYs: %d\n", num_ebusy.load());
+ if (clp->verbose > 1) {
+ pr2serr("Number of SG_GET_NUM_WAITING calls=%ld\n",
+ num_waiting_calls.load());
+ }
+ if (clp->verify && (SG_LIB_CAT_MISCOMPARE == res))
+ pr2serr("Verify/compare failed due to miscompare\n");
+ return (res >= 0) ? res : SG_LIB_CAT_OTHER;
+}
diff --git a/testing/sgh_dd.cpp b/testing/sgh_dd.cpp
index 2dfcd71d..7abcb455 100644
--- a/testing/sgh_dd.cpp
+++ b/testing/sgh_dd.cpp
@@ -36,6 +36,8 @@
* renamed [20181221]
*/
+static const char * version_str = "1.82 20200629";
+
#define _XOPEN_SOURCE 600
#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
@@ -66,6 +68,7 @@
#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */
#include <linux/fs.h> /* for BLKSSZGET and friends */
#include <sys/mman.h> /* for mmap() system call */
+#include <sys/random.h> /* for getrandom() system call */
#include <vector>
#include <array>
@@ -108,8 +111,6 @@
using namespace std;
-static const char * version_str = "1.80 20200404";
-
#ifdef __GNUC__
#ifndef __clang__
#pragma GCC diagnostic ignored "-Wclobbered"
@@ -129,8 +130,6 @@ static const char * version_str = "1.80 20200404";
#define DEF_SCSI_CDBSZ 10
#define MAX_SCSI_CDBSZ 16
-#define URANDOM_DEV "/dev/urandom"
-
#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
#define READ_CAP_REPLY_LEN 8
#define RCAP16_REPLY_LEN 32
@@ -156,7 +155,9 @@ static const char * version_str = "1.80 20200404";
#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */
#define FT_ST 16 /* filetype is st char device (tape) */
#define FT_BLOCK 32 /* filetype is a block device */
-#define FT_ERROR 64 /* couldn't "stat" file */
+#define FT_RANDOM_0_FF 64 /* iflag=00, iflag=ff and iflag=random
+ override if=IFILE */
+#define FT_ERROR 128 /* couldn't "stat" file */
#define DEV_NULL_MINOR_NUM 3
@@ -174,6 +175,7 @@ struct flags_t {
bool dpo;
bool dsync;
bool excl;
+ bool ff;
bool fua;
bool masync; /* more async sg v4 driver flag */
bool mrq_immed; /* mrq submit non-blocking */
@@ -185,12 +187,14 @@ struct flags_t {
bool noxfer;
bool qhead;
bool qtail;
+ bool random;
bool same_fds;
bool swait;
bool v3;
bool v4;
bool v4_given;
bool wq_excl;
+ bool zero;
int mmap;
};
@@ -210,8 +214,6 @@ struct global_collection
atomic<bool> in_stop; /* | */
pthread_mutex_t in_mutex; /* -/ */
int nmrqs; /* Number of multi-reqs for sg v4 */
- int inmrqs; /* if both imrq= and omrq= must be == */
- int onmrqs; /* ... unless one is zero */
int outfd;
int64_t seek;
int out_type;
@@ -304,6 +306,8 @@ typedef struct request_element
int mrq_index;
uint32_t in_mrq_q_blks;
uint32_t out_mrq_q_blks;
+ long seed;
+ struct drand48_data drand; /* opaque, used by srand48_r and mrand48_r */
pthread_t mrq_abort_thread_id;
Mrq_abort_info mai;
} Rq_elem;
@@ -597,6 +601,11 @@ sg_flags_str(int flags, int b_len, char * b)
if (n >= b_len)
goto fini;
}
+ if (SGV4_FLAG_ORDERED_SLV & flags) { /* 0x80000 */
+ n += sg_scnpr(b + n, b_len - n, "OSLV|");
+ if (n >= b_len)
+ goto fini;
+ }
fini:
if (n < b_len) { /* trim trailing '\' */
if ('|' == b[n - 1])
@@ -637,24 +646,6 @@ v4hdr_out_lk(const char * leadin, const sg_io_v4 * h4p, int id)
pthread_mutex_unlock(&strerr_mut);
}
-static unsigned int
-get_urandom_uint(void)
-{
- unsigned int res = 0;
- int n;
- uint8_t b[sizeof(unsigned int)];
- lock_guard<mutex> lg(rand_lba_mutex);
-
- int fd = open(URANDOM_DEV, O_RDONLY);
- if (fd >= 0) {
- n = read(fd, b, sizeof(unsigned int));
- if (sizeof(unsigned int) == n)
- memcpy(&res, b, sizeof(unsigned int));
- close(fd);
- }
- return res;
-}
-
static void
fetch_sg_version(void)
{
@@ -860,7 +851,7 @@ usage(int pg_num)
"[coe=0|1]\n"
" [deb=VERB] [dio=0|1] [elemsz_kb=ESK] "
"[fail_mask=FM]\n"
- " [fua=0|1|2|3] [mrq=[IO,]NRQS[,C]] [noshare=0|1] "
+ " [fua=0|1|2|3] [mrq=[I|O,]NRQS[,C]] [noshare=0|1] "
"[of2=OFILE2]\n"
" [ofreg=OFREG] [ofsplit=OSP] [sync=0|1] [thr=THR] "
"[time=0|1]\n"
@@ -934,7 +925,10 @@ page2:
" 3->OFILE+IFILE\n"
" mrq number of cmds placed in each sg call "
"(def: 0);\n"
- " may have trailing ',C', to send bulk cdb_s\n"
+ " may have trailing ',C', to send bulk cdb_s; "
+ "if preceded\n"
+ " by 'I' then mrq only on IFILE, likewise 'O' "
+ "for OFILE\n"
" noshare 0->use request sharing(def), 1->don't\n"
" ofreg OFREG is regular file or pipe to send what is "
"read from\n"
@@ -960,6 +954,8 @@ page3:
pr2serr("Syntax: sgh_dd [operands] [options]\n\n"
" where: 'iflag=<arg>' and 'oflag=<arg>' arguments are listed "
"below:\n\n"
+ " 00 use all zeros instead of if=IFILE (only in "
+ "iflags)\n"
" append append output to OFILE (assumes OFILE is "
"regular file)\n"
" coe continue of error (reading, fills with zeros)\n"
@@ -971,6 +967,8 @@ page3:
"and WRITEs\n"
" dsync sets the O_SYNC flag on open()\n"
" excl sets the O_EXCL flag on open()\n"
+ " ff use all 0xff bytes instead of if=IFILE (only in "
+ "iflags)\n"
" fua sets the FUA (force unit access) in SCSI READs "
"and WRITEs\n"
" masync set 'more async' flag on this sg device\n"
@@ -985,9 +983,12 @@ page3:
" nodur turns off command duration calculations\n"
" no_waitq when non-blocking (async) don't use wait "
"queue\n"
+ " noxfer no transfer to/from the user space\n"
" qhead queue new request at head of block queue\n"
" qtail queue new request at tail of block queue (def: "
"q at head)\n"
+ " random use random data instead of if=IFILE (only in "
+ "iflags)\n"
" same_fds each thread use the same IFILE and OFILE(2) "
"file\n"
" descriptors (def: each threads has own file "
@@ -1149,11 +1150,17 @@ mrq_abort_thread(void * v_maip)
{
int res, err;
int n = 0;
- int seed = get_urandom_uint();
+ int seed;
unsigned int rn;
+ ssize_t ssz;
Mrq_abort_info l_mai = *(Mrq_abort_info *)v_maip;
struct sg_io_v4 ctl_v4;
+ ssz = getrandom(&rn, sizeof(rn), 0);
+ if (ssz < (ssize_t)sizeof(rn))
+ pr2serr_lk("%s: getrandom() failed, returned %d\n", __func__,
+ (int)ssz);
+ seed = rn;
if (l_mai.debug)
pr2serr_lk("%s: from_id=%d: to abort mrq_pack_id=%d\n", __func__,
l_mai.from_tid, l_mai.mrq_id);
@@ -1393,6 +1400,17 @@ read_write_thread(void * v_tip)
else if (out_is_sg)
rep->only_out_sg = true;
+ if (clp->in_flags.random) {
+ ssize_t ssz;
+
+ ssz = getrandom(&rep->seed, sizeof(rep->seed), 0);
+ if (ssz < (ssize_t)sizeof(rep->seed))
+ pr2serr_lk("thread=%d: getrandom() failed, ret=%d\n", rep->id,
+ (int)ssz);
+ if (vb > 1)
+ pr2serr_lk("thread=%d: seed=%ld\n", rep->id, rep->seed);
+ srand48_r(rep->seed, &rep->drand);
+ }
if (clp->in_flags.same_fds || clp->out_flags.same_fds) {
/* we are sharing a single pair of fd_s across all threads */
if (clp->out_flags.swait && (! swait_reported)) {
@@ -1532,6 +1550,8 @@ read_write_thread(void * v_tip)
if (0 != status) err_exit(status, "lock out_mutex");
/* Make sure the OFILE (+ OFREG) are in same sequence as IFILE */
+ if (clp->in_flags.random)
+ goto skip_force_out_sequence;
if ((rep->outregfd < 0) && in_is_sg && out_is_sg)
goto skip_force_out_sequence;
if (share_and_ofreg || (FT_DEV_NULL != clp->out_type)) {
@@ -1693,6 +1713,8 @@ fini:
return stop_after_write ? NULL : clp;
}
+/* N.B. A return of true means it wants to stop the copy. So false is the
+ * 'good' reply (i.e. keep going). */
static bool
normal_in_rd(Rq_elem * rep, int blocks)
{
@@ -1705,6 +1727,28 @@ normal_in_rd(Rq_elem * rep, int blocks)
if (clp->debug > 4)
pr2serr_lk("%s: tid=%d: iblk=%" PRIu64 ", blocks=%d\n", __func__,
rep->id, rep->iblk, blocks);
+ if (FT_RANDOM_0_FF == clp->in_type) {
+ int k, j;
+ const int jbump = sizeof(uint32_t);
+ long rn;
+ uint8_t * bp;
+
+ if (clp->in_flags.zero)
+ memset(rep->buffp, 0, blocks * clp->bs);
+ else if (clp->in_flags.ff)
+ memset(rep->buffp, 0xff, blocks * clp->bs);
+ else {
+ for (k = 0, bp = rep->buffp; k < blocks; ++k, bp += clp->bs) {
+ for (j = 0; j < clp->bs; j += jbump) {
+ /* mrand48 takes uniformly from [-2^31, 2^31) */
+ mrand48_r(&rep->drand, &rn);
+ *((uint32_t *)(bp + j)) = (uint32_t)rn;
+ }
+ }
+ }
+ clp->in_rem_count -= blocks;
+ return stop_after_write;
+ }
if (! same_fds) { /* each has own file pointer, so we need to move it */
int64_t pos = rep->iblk * clp->bs;
@@ -1766,7 +1810,7 @@ normal_out_wr(Rq_elem * rep, int blocks)
if (clp->debug > 4)
pr2serr_lk("%s: tid=%d: oblk=%" PRIu64 ", blocks=%d\n", __func__,
rep->id, rep->oblk, blocks);
- while (((res = write(clp->outfd, rep->buffp, rep->num_blks * clp->bs))
+ while (((res = write(clp->outfd, rep->buffp, blocks * clp->bs))
< 0) && ((EINTR == errno) || (EAGAIN == errno)))
std::this_thread::yield();/* another thread may be able to progress */
if (res < 0) {
@@ -2501,7 +2545,7 @@ sgh_do_deferred_mrq(Rq_elem * rep, mrq_arr_t & def_arr)
struct sg_io_v4 ctl_v4;
uint8_t * cmd_ap = NULL;
struct global_collection * clp = rep->clp;
- const char * iosub_str;
+ const char * iosub_str = "svb ?";
char b[80];
id = rep->id;
@@ -3563,7 +3607,9 @@ process_flags(const char * arg, struct flags_t * fp)
np = strchr(cp, ',');
if (np)
*np++ = '\0';
- if (0 == strcmp(cp, "append"))
+ if (0 == strcmp(cp, "00"))
+ fp->zero = true;
+ else if (0 == strcmp(cp, "append"))
fp->append = true;
else if (0 == strcmp(cp, "coe"))
fp->coe = true;
@@ -3579,6 +3625,8 @@ process_flags(const char * arg, struct flags_t * fp)
fp->dsync = true;
else if (0 == strcmp(cp, "excl"))
fp->excl = true;
+ else if (0 == strcmp(cp, "ff"))
+ fp->ff = true;
else if (0 == strcmp(cp, "fua"))
fp->fua = true;
else if (0 == strcmp(cp, "masync"))
@@ -3617,6 +3665,8 @@ process_flags(const char * arg, struct flags_t * fp)
fp->qhead = true;
else if (0 == strcmp(cp, "qtail"))
fp->qtail = true;
+ else if (0 == strcmp(cp, "random"))
+ fp->random = true;
else if (0 == strcmp(cp, "same_fds"))
fp->same_fds = true;
else if (0 == strcmp(cp, "swait"))
@@ -4116,6 +4166,8 @@ main(int argc, char * argv[])
int64_t out_num_sect = 0;
int in_sect_sz, out_sect_sz, status, flags;
void * vp;
+ const char * ccp = NULL;
+ const char * cc2p;
struct global_collection * clp = &gcoll;
Thread_info thread_arr[MAX_NUM_THREADS];
char ebuff[EBUFF_SZ];
@@ -4166,7 +4218,25 @@ main(int argc, char * argv[])
clp->infd = STDIN_FILENO;
clp->outfd = STDOUT_FILENO;
- if (inf[0] && ('-' != inf[0])) {
+ if (clp->in_flags.ff) {
+ ccp = "<0xff bytes>";
+ cc2p = "ff";
+ } else if (clp->in_flags.random) {
+ ccp = "<random>";
+ cc2p = "random";
+ } else if (clp->in_flags.zero) {
+ ccp = "<zero bytes>";
+ cc2p = "00";
+ }
+ if (ccp) {
+ if (inf[0]) {
+ pr2serr("%siflag=%s and if=%s contradict\n", my_name, cc2p, inf);
+ return SG_LIB_CONTRADICT;
+ }
+ clp->in_type = FT_RANDOM_0_FF;
+ clp->infp = ccp;
+ clp->infd = -1;
+ } else if (inf[0] && ('-' != inf[0])) {
clp->in_type = dd_filetype(inf);
if (FT_ERROR == clp->in_type) {
diff --git a/testing/uapi_sg.h b/testing/uapi_sg.h
index 955790fa..d9401af9 100644
--- a/testing/uapi_sg.h
+++ b/testing/uapi_sg.h
@@ -127,6 +127,7 @@ typedef struct sg_io_hdr {
#define SGV4_FLAG_NO_DXFER SG_FLAG_NO_DXFER /* needed for sharing */
#define SGV4_FLAG_MULTIPLE_REQS 0x20000 /* n sg_io_v4s in data-in */
#define SGV4_FLAG_EVENTFD 0x40000 /* signal completion on ... */
+#define SGV4_FLAG_ORDERED_SLV 0x80000 /* svb: issue in-order writes */
/* Output (potentially OR-ed together) in v3::info or v4::info field */
#define SG_INFO_OK_MASK 0x1