blob: 83d921a2ed7d1427d30897f2e91c0ad087917cb8 [file] [log] [blame]
/*
* 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.
*
* sg_mrq_dd uses C++ threads and MRQ (multiple requests (in one invocation))
* facilities in the sg version 4 driver to do "dd" type copies and verifies.
*
*/
static const char * version_str = "1.04 20200720";
#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 */
// C++ local header
#include "sg_scat_gath.h"
#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"
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 cdbsz_out;
struct flags_t out_flags;
atomic<int64_t> out_rem_count; /* | count of remaining out blocks */
atomic<int> out_partial; /* | */
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;
int dry_run;
bool cdbsz_given;
bool count_given;
bool flexible;
bool ofile_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;
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 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 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);
#if 0
static atomic<long> num_waiting_calls(0);
#endif
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_WR & flags) { /* 0x80000 */
n += sg_scnpr(b + n, b_len - n, "OWR|");
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;
}
/* Info field decoded into abbreviations for those bits that are set,
* separated by '|' . */
static char *
sg_info_str(int info, int b_len, char * b)
{
int n = 0;
if ((b_len < 1) || (! b))
return b;
b[0] = '\0';
if (SG_INFO_CHECK & info) { /* 0x1 */
n += sg_scnpr(b + n, b_len - n, "CHK|");
if (n >= b_len)
goto fini;
}
if (SG_INFO_DIRECT_IO & info) { /* 0x2 */
n += sg_scnpr(b + n, b_len - n, "DIO|");
if (n >= b_len)
goto fini;
}
if (SG_INFO_MIXED_IO & info) { /* 0x4 */
n += sg_scnpr(b + n, b_len - n, "MIO|");
if (n >= b_len)
goto fini;
}
if (SG_INFO_DEVICE_DETACHING & info) { /* 0x8 */
n += sg_scnpr(b + n, b_len - n, "DETA|");
if (n >= b_len)
goto fini;
}
if (SG_INFO_ABORTED & info) { /* 0x10 */
n += sg_scnpr(b + n, b_len - n, "ABRT|");
if (n >= b_len)
goto fini;
}
if (SG_INFO_MRQ_FINI & info) { /* 0x20 */
n += sg_scnpr(b + n, b_len - n, "MRQF|");
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, bool chk_info)
{
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(" %s OUT:\n", leadin);
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);
if (chk_info && (SG_INFO_CHECK & h4p->info))
pr2serr(" >>>> info: %s\n", sg_info_str(h4p->info, sizeof(b), b));
}
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 (NULL == fp)
pr2serr("The sg driver may not be loaded\n");
}
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());
if (gcoll.out_type == FT_DEV_NULL)
pr2serr("%s0+0 records out\n", str);
else {
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] [dio=0|1] "
"[fua=0|1|2|3]\n"
" [mrq=MRQ] [ofreg=OFREG] [sync=0|1] [thr=THR] "
"[time=0|1]\n"
" [verbose=VERB] [--dry-run] [--verbose] "
"[--verify]\n"
" [--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"
" 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"
" 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 the 'multiple requests' "
"(mrq) in a single invocation\nfacility in version 4 of the sg "
"driver. Usually one or both IFILE and\nOFILE will be sg "
"devices. With the --verify option it does a\n"
"verify/compare operation instead of a copy. This utility is "
"Linux\n specific. Use '-hh', '-hhh' or '-hhhh' for 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"
" 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 increase verbosity (def: VERB=0)\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. "
"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 read- and write- "
"sides of a request\nshare show they are in 'active' state "
"which should not be possible.\nIt occurs because the read-side "
"probably jumped out of active state and\nthe write-side "
"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;
}
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);
}
/* 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 write_side_fd, int read_side_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 = read_side_fd;
if (ioctl(write_side_fd, SG_SET_GET_EXTENDED, seip) < 0) {
pr2serr_lk("tid=%d: ioctl(EXTENDED(shared_fd=%d), failed "
"errno=%d %s\n", id, read_side_fd, errno,
strerror(errno));
return false;
}
if (vb_b)
pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(shared_fd)) ok, "
"read_side_fd=%d, write_side_fd=%d\n", __func__, id,
read_side_fd, write_side_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 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->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 (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 (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)) {
lock_guard<mutex> lk(strerr_mut);
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) {
#if 0
++num_waiting_calls;
#endif
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) {
#if 0
++num_waiting_calls;
#endif
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);
}
/* pass stats back to read-side */
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;
}
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,
bool & last_err_on_in)
{
struct global_collection * clp = rep->clp;
bool ok, all_good;
bool sb_in_co = !!(ctl_v4p->response);
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 hole_count = 0;
int vb = clp->verbose;
int k, j, f1, slen, sstatus, blen;
char b[160];
blen = sizeof(b);
good_inblks = 0;
good_outblks = 0;
if (vb > 2)
pr2serr_lk("[thread_id=%d] %s: num_mrq=%d, n_subm=%d, n_cmpl=%d\n",
id, __func__, num_mrq, n_subm, n_cmpl);
if (n_subm < 0) {
pr2serr_lk("[%d] co.dout_resid(%d) > num_mrq(%d)\n", id,
ctl_v4p->dout_resid, num_mrq);
return -1;
}
if (n_cmpl != (num_mrq - resid))
pr2serr_lk("[%d] co.info(%d) != (num_mrq(%d) - co.din_resid(%d))\n"
"will use co.info\n", id, n_cmpl, num_mrq, resid);
if (n_cmpl > n_subm) {
pr2serr_lk("[%d] n_cmpl(%d) > n_subm(%d), use n_subm for both\n",
id, n_cmpl, n_subm);
n_cmpl = n_subm;
}
if (sres) {
pr2serr_lk("[%d] secondary error: %s [%d], info=0x%x\n", id,
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. N.B. If there has been
* an error then there may be "holes" (i.e. info=0x0) in the array due
* to completions being out-of-order. */
for (k = 0, j = 0; ((k < num_mrq) && (j < n_subm));
++k, j += f1, ++a_v4p) {
slen = a_v4p->response_len;
if (! (SG_INFO_MRQ_FINI & a_v4p->info))
++hole_count;
ok = true;
f1 = !!(a_v4p->info); /* want to skip n_subm count if info is 0x0 */
if (SG_INFO_CHECK & a_v4p->info) {
ok = false;
pr2serr_lk("[%d] a_v4[%d]: SG_INFO_CHECK set [%s]\n", id, k,
sg_info_str(a_v4p->info, sizeof(b), b));
}
sstatus = a_v4p->device_status;
if ((sstatus && (SAM_STAT_CONDITION_MET != sstatus)) ||
a_v4p->transport_status || a_v4p->driver_status) {
ok = false;
last_err_on_in = ! (a_v4p->flags & SGV4_FLAG_DO_ON_OTHER);
if (SAM_STAT_CHECK_CONDITION != a_v4p->device_status) {
pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
if (vb)
lk_chk_n_print4(" >>", a_v4p, vb > 4);
}
}
if (slen > 0) {
struct sg_scsi_sense_hdr ssh;
const uint8_t *sbp = (const uint8_t *)
(sb_in_co ? ctl_v4p->response : a_v4p->response);
if (sg_scsi_normalize_sense(sbp, slen, &ssh) &&
(ssh.response_code >= 0x70)) {
char b[256];
if (ssh.response_code & 0x1) {
ok = true;
last_err_on_in = false;
}
if (vb) {
sg_get_sense_str(" ", sbp, slen, false, blen, b);
pr2serr_lk("[%d] a_v4[%d]:\n%s\n", id, k, b);
}
}
}
if (ok && f1) {
++n_good;
if (a_v4p->dout_xfer_len >= (uint32_t)clp->bs) {
if (a_v4p->dout_resid)
good_outblks +=
(a_v4p->dout_xfer_len - a_v4p->dout_resid) / clp->bs;
else /* avoid division in common case of resid==0 */
good_outblks += (uint32_t)a_v4p->usr_ptr;
}
if (a_v4p->din_xfer_len >= (uint32_t)clp->bs) {
if (a_v4p->din_resid)
good_inblks += (a_v4p->din_xfer_len - a_v4p->din_resid) /
clp->bs;
else
good_inblks += (uint32_t)a_v4p->usr_ptr;
}
}
} /* end of request array scan loop */
if ((n_subm == num_mrq) || (vb < 3))
goto fini;
if (vb)
pr2serr_lk("[%d] checking response array _beyond_ number of "
"submissions [%d] to num_mrq:\n", id, k);
for (all_good = true; k < num_mrq; ++k, ++a_v4p) {
if (SG_INFO_MRQ_FINI & a_v4p->info) {
pr2serr_lk("[%d] a_v4[%d]: unexpected SG_INFO_MRQ_FINI set [%s]\n",
id, k, sg_info_str(a_v4p->info, sizeof(b), b));
all_good = false;
}
if (a_v4p->device_status || a_v4p->transport_status ||
a_v4p->driver_status) {
pr2serr_lk("[%d] a_v4[%d]:\n", id, k);
lk_chk_n_print4(" ", a_v4p, vb > 4);
all_good = false;
}
}
if (all_good)
pr2serr_lk(" ... all good\n");
fini:
return n_good;
}
/* 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;
bool err_on_in = false;
int vb = clp->verbose;
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 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 (vb > 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;
if (! flagsp->coe)
ctl_v4.flags |= 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 (vb > 4) {
pr2serr_lk("[%d] %s: >> Control object _before_ ioctl(SG_IO%s):\n",
id, __func__, iosub_str);
if (vb > 5)
hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
v4hdr_out_lk(">> Control object before", &ctl_v4, id, false);
}
try_again:
if (!after1 && (vb > 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 (vb > 4) {
pr2serr_lk("%s: >> Control object after ioctl(%s) seg_blks=%d:\n",
__func__, iosub_str, o_seg_blks);
if (vb > 5)
hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
v4hdr_out_lk(">> Control object after", &ctl_v4, id, false);
if (vb > 5) {
for (k = 0; k < num_mrq; ++k) {
if ((vb > 6) || a_v4p[k].info) {
snprintf(b, b_len, "a_v4[%d/%d]", k, num_mrq);
v4hdr_out_lk(b, (a_v4p + k), id, true);
}
}
}
}
num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks,
out_fin_blks, err_on_in);
if (is_wr)
out_mrq_q_blks = mrq_q_blks;
else
in_mrq_q_blks = mrq_q_blks;
if (vb > 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;
rep->stop_after_write = ! (err_on_in && clp->in_flags.coe);
}
resid_blks = out_mrq_q_blks - out_fin_blks;
if (resid_blks > 0) {
rep->out_rem_count += resid_blks;
rep->stop_after_write = ! (! err_on_in && clp->out_flags.coe);
}
}
}
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) {
out_fin_blks = seg_blks;/* so finish logic doesn't suspect ... */
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)
{
bool err_on_in = false;
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;
int vb = clp->verbose;
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 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 (vb > 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 write-side (WRITE or VERIFY) */
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 (vb > 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);
i_sg_it.add_blks(num);
o_sg_it.add_blks(num);
}
if (vb > 6) {
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);
}
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_SHARE;
if (! (iflagsp->coe || oflagsp->coe))
ctl_v4.flags |= SGV4_FLAG_STOP_IF;
if ((! clp->verify) && clp->out_flags.order)
ctl_v4.flags |= SGV4_FLAG_ORDERED_WR;
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 (vb > 4) {
pr2serr_lk("%s: >> Control object _before_ ioctl(SG_IO%s):\n",
__func__, iosub_str);
if (vb > 5)
hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
v4hdr_out_lk(">> Control object before", &ctl_v4, id, false);
}
try_again:
if (!after1 && (vb > 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 (vb > 4) {
pr2serr_lk("%s: >> Control object after ioctl(%s) seg_blks=%d:\n",
__func__, iosub_str, o_seg_blks);
if (vb > 5)
hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1);
v4hdr_out_lk(">> Control object after", &ctl_v4, id, false);
if (vb > 5) {
for (k = 0; k < num_mrq; ++k) {
if ((vb > 6) || a_v4p[k].info) {
snprintf(b, b_len, "a_v4[%d/%d]", k, num_mrq);
v4hdr_out_lk(b, (a_v4p + k), id, true);
}
}
}
}
num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks,
out_fin_blks, err_on_in);
if (vb > 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) { /* reduced number completed */
int resid_blks = in_mrq_q_blks - in_fin_blks;
if (resid_blks > 0) {
rep->in_rem_count += resid_blks;
rep->stop_after_write = ! (err_on_in && clp->in_flags.coe);
}
resid_blks = out_mrq_q_blks - out_fin_blks;
if (resid_blks > 0) {
rep->out_rem_count += resid_blks;
rep->stop_after_write = ! (! err_on_in && clp->out_flags.coe);
}
}
}
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));
}
}
#if 0
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");
#endif
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) {
lock_guard<mutex> lk(strerr_mut);
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 * 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, "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 == 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, "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 (0 == strncmp(key, "verb", 4))
clp->verbose = 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[])
{
bool fail_after_cli = false;
char inf[INOUTF_SZ];
char outf[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->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';
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\n",
my_name);
fail_after_cli = true;
}
res = parse_cmdline_sanity(argc, argv, clp, inf, outf, outregf);
if (SG_LIB_OK_FALSE == res)
return 0;
if (res)
return res;
if (fail_after_cli) {
pr2serr("%scommand line parsing was okay but sg driver is too old\n",
my_name);
return SG_LIB_SYNTAX_ERROR;
}
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 (clp->verify && (clp->out_type == FT_DEV_NULL)) {
pr2serr("Can't do verify when OFILE not given\n");
return SG_LIB_SYNTAX_ERROR;
}
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 (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);
res = sigprocmask(SIG_BLOCK, &signal_set, NULL);
if (res < 0) {
pr2serr("sigprocmask failed: %s\n", safe_strerror(errno));
goto fini;
}
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");
}
}
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->outregfd >= 0) && (STDOUT_FILENO != clp->outregfd) &&
(FT_DEV_NULL != clp->outreg_type))
close(clp->outregfd);
res = exit_status;
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 0
if (clp->verbose > 1) {
pr2serr("Number of SG_GET_NUM_WAITING calls=%ld\n",
num_waiting_calls.load());
}
#endif
if (clp->verify && (SG_LIB_CAT_MISCOMPARE == res))
pr2serr("Verify/compare failed due to miscompare\n");
return (res >= 0) ? res : SG_LIB_CAT_OTHER;
}