diff options
author | Douglas Gilbert <dgilbert@interlog.com> | 2020-07-02 00:15:50 +0000 |
---|---|---|
committer | Douglas Gilbert <dgilbert@interlog.com> | 2020-07-02 00:15:50 +0000 |
commit | 2c59b7853f594c9b3fa7e3f259f7b45ab6d47903 (patch) | |
tree | 7b26b7143a3f058954fdea2e5a6071b3844b726b /testing/sg_mrq_dd.cpp | |
parent | 62c85c807d9d57f6bcf9a731d0126feaab661d93 (diff) | |
download | sg3_utils-2c59b7853f594c9b3fa7e3f259f7b45ab6d47903.tar.gz |
asc/ascq match asc-num.txt @t10 20200624; testing/sg_mrq_testing: new, for blocking mrq usage
git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@853 6180dd3e-e324-4e3e-922d-17de1ae2f315
Diffstat (limited to 'testing/sg_mrq_dd.cpp')
-rw-r--r-- | testing/sg_mrq_dd.cpp | 4907 |
1 files changed, 4907 insertions, 0 deletions
diff --git a/testing/sg_mrq_dd.cpp b/testing/sg_mrq_dd.cpp new file mode 100644 index 00000000..a42cff74 --- /dev/null +++ b/testing/sg_mrq_dd.cpp @@ -0,0 +1,4907 @@ +/* + * A utility program for copying files. Specialised for "files" that + * represent devices that understand the SCSI command set. + * + * Copyright (C) 2018-2020 D. Gilbert + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is a specialisation of the Unix "dd" command in which + * one or both of the given files is a scsi generic device. + * A logical block size ('bs') is assumed to be 512 if not given. This + * program complains if 'ibs' or 'obs' are given with some other value + * than 'bs'. If 'if' is not given or 'if=-' then stdin is assumed. If + * 'of' is not given or 'of=-' then stdout assumed. + * + * A non-standard argument "bpt" (blocks per transfer) is added to control + * the maximum number of blocks in each transfer. The default value is 128. + * For example if "bs=512" and "bpt=32" then a maximum of 32 blocks (16 KiB + * in this case) are transferred to or from the sg device in a single SCSI + * command. + * + * This version is designed for the linux kernel 4 and 5 series. + * + * sgp_dd is a Posix threads specialization of the sg_dd utility. Both + * sgp_dd and sg_dd only perform special tasks when one or both of the given + * devices belong to the Linux sg driver. + * + * sgh_dd further extends sgp_dd to use the experimental kernel buffer + * sharing feature added in 3.9.02 . + * N.B. This utility was previously called sgs_dd but there was already an + * archived version of a dd variant called sgs_dd so this utility name was + * renamed [20181221] + */ + +static const char * version_str = "1.01 20200701"; + +#define _XOPEN_SOURCE 600 +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <poll.h> +#include <limits.h> +// #include <pthread.h> +#include <signal.h> +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#ifndef major +#include <sys/types.h> +#endif +#include <sys/time.h> +#include <linux/major.h> /* for MEM_MAJOR, SCSI_GENERIC_MAJOR, etc */ +#include <linux/fs.h> /* for BLKSSZGET and friends */ +#include <sys/mman.h> /* for mmap() system call */ +#include <sys/random.h> /* for getrandom() system call */ + +#include <vector> +#include <array> +#include <atomic> // C++ header replacing <stdatomic.h> +#include <random> +#include <thread> // needed for std::this_thread::yield() +#include <mutex> +#include <condition_variable> +#include <chrono> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef HAVE_LINUX_SG_V4_HDR +/* Kernel uapi header contain __user decorations on user space pointers + * to indicate they are unsafe in the kernel space. However glibc takes + * all those __user decorations out from headers in /usr/include/linux . + * So to stop compile errors when directly importing include/uapi/scsi/sg.h + * undef __user before doing that include. */ +#define __user + +/* Want to block the original sg.h header from also being included. That + * causes lots of multiple definition errors. This will only work if this + * header is included _before_ the original sg.h header. */ +#define _SCSI_GENERIC_H /* original kernel header guard */ +#define _SCSI_SG_H /* glibc header guard */ + +#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */ + +#else +#define __user +#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */ + +#include "sg_lib.h" +#include "sg_cmds_basic.h" +#include "sg_io_linux.h" +#include "sg_unaligned.h" +#include "sg_pr2serr.h" + + +using namespace std; + +#ifdef __GNUC__ +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wclobbered" +#endif +#endif + + +#define MAX_SGL_NUM_VAL (INT32_MAX - 1) /* should reduce for testing */ +// #define MAX_SGL_NUM_VAL 7 /* should reduce for testing */ +#if MAX_SGL_NUM_VAL > INT32_MAX +#error "MAX_SGL_NUM_VAL cannot exceed 2^31 - 1" +#endif + +#define DEF_BLOCK_SIZE 512 +#define DEF_BLOCKS_PER_TRANSFER 128 +#define DEF_BLOCKS_PER_2048TRANSFER 32 +#define DEF_SCSI_CDB_SZ 10 +#define MAX_SCSI_CDB_SZ 16 +#define PACK_ID_TID_MULTIPLIER (0x1000000) /* 16,777,216 */ + +#define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */ +#define READ_CAP_REPLY_LEN 8 +#define RCAP16_REPLY_LEN 32 + +#define DEF_TIMEOUT 60000 /* 60,000 millisecs == 60 seconds */ + +#define SGP_READ10 0x28 +#define SGP_PRE_FETCH10 0x34 +#define SGP_PRE_FETCH16 0x90 +#define SGP_VERIFY10 0x2f +#define SGP_WRITE10 0x2a +#define DEF_NUM_THREADS 4 +#define MAX_NUM_THREADS 1024 /* was SG_MAX_QUEUE with v3 driver */ +#define DEF_MRQ_NUM 16 + +#ifndef RAW_MAJOR +#define RAW_MAJOR 255 /*unlikely value */ +#endif + +#define FT_OTHER 1 /* filetype other than one of the following */ +#define FT_SG 2 /* filetype is sg char device */ +#define FT_RAW 4 /* filetype is raw char device */ +#define FT_DEV_NULL 8 /* either "/dev/null" or "." as filename */ +#define FT_ST 16 /* filetype is st char device (tape) */ +#define FT_BLOCK 32 /* filetype is a block device */ +#define FT_FIFO 64 /* fifo (named or unnamed pipe (stdout)) */ +#define FT_RANDOM_0_FF 128 /* iflag=00, iflag=ff and iflag=random + override if=IFILE */ +#define FT_ERROR 256 /* couldn't "stat" file */ + +#define DEV_NULL_MINOR_NUM 3 + +#define EBUFF_SZ 768 + +#define PROC_SCSI_SG_VERSION "/proc/scsi/sg/version" +#define SYS_SCSI_SG_VERSION "/sys/module/sg/version" + +#define SG_SGL_MAX_ELEMENTS 16384 + +#define SG_COUNT_INDEFINITE (-1) +#define SG_LBA_INVALID SG_COUNT_INDEFINITE + +/* Sizing matches largest SCSI READ and WRITE commands plus those of Unix + * read(2)s and write(2)s. User can give larger than 31 bit 'num's but they + * are split into several consecutive elements. */ +struct scat_gath_elem { + uint64_t lba; /* of start block */ + uint32_t num; /* number of blocks from and including start block */ + + void make_bad() { lba = UINT64_MAX; num = UINT32_MAX; } + bool is_bad() const { return (lba == UINT64_MAX && num == UINT32_MAX); } +}; + +/* Consider "linearity" as a scatter gather list property. Elements of this + * of from the strongest form to the weakest. */ +enum sgl_linearity_e { + SGL_LINEAR = 0, /* empty list and 0,0 considered linear */ + SGL_MONOTONIC, /* since not linear, implies holes */ + SGL_MONO_OVERLAP, /* monotonic but same LBA in two or more elements */ + SGL_NON_MONOTONIC /* weakest */ +}; + + +/* Holds one scatter gather list and its associated metadata */ +class scat_gath_list { +public: + scat_gath_list() : linearity(SGL_LINEAR), sum_hard(false), m_errno(0), + high_lba_p1(0), lowest_lba(0), sum(0) { } + + scat_gath_list(const scat_gath_list &) = default; + scat_gath_list & operator=(const scat_gath_list &) = default; + ~scat_gath_list() = default; + + bool empty() const; + bool empty_or_00() const; + int num_elems() const; + int64_t get_lowest_lba(bool ignore_degen, bool always_last) const; + int64_t get_low_lba_from_linear() const; + bool is_pipe_suitable() const; + + friend bool sgls_eq_off(const scat_gath_list &left, int l_e_ind, + int l_blk_off, + const scat_gath_list &right, int r_e_ind, + int r_blk_off, bool allow_partial); + + bool load_from_cli(const char * cl_p, bool b_vb); + bool load_from_file(const char * file_name, bool def_hex, bool flexible, + bool b_vb); + int append_1or(int64_t extra_blks, int64_t start_lba); + int append_1or(int64_t extra_blks); + + void dbg_print(bool skip_meta, const char * id_str, bool to_stdout, + bool show_sgl, bool lock = true) const; + + /* calculates and sets following bool-s and int64_t-s */ + void sum_scan(const char * id_str, bool show_sgl, bool b_verbose); + + void set_weaker_linearity(enum sgl_linearity_e lin); + enum sgl_linearity_e linearity; + const char * linearity_as_str() const; + + bool sum_hard; /* 'num' in last element of 'sgl' is > 0 */ + int m_errno; /* OS failure errno */ + int64_t high_lba_p1; /* highest LBA plus 1, next write from and above */ + int64_t lowest_lba; /* initialized to 0 */ + int64_t sum; /* of all 'num' elements in 'sgl' */ + + friend int diff_between_iters(const struct scat_gath_iter & left, + const struct scat_gath_iter & right); + +private: + friend class scat_gath_iter; + + bool file2sgl_helper(FILE * fp, const char * fnp, bool def_hex, + bool flexible, bool b_vb); + + vector<scat_gath_elem> sgl; /* an array on heap [0..num_elems()) */ +}; + + +class scat_gath_iter { +public: + scat_gath_iter(const scat_gath_list & my_scat_gath_list); + scat_gath_iter(const scat_gath_iter & src) = default; + scat_gath_iter& operator=(const scat_gath_iter&) = delete; + ~scat_gath_iter() = default; + + int64_t current_lba() const; + int64_t current_lba_rem_num(int & rem_num) const; + struct scat_gath_elem current_elem() const; + bool at_end() const; + bool is_sgl_linear() const; /* the whole list */ + int linear_for_n_blks(int max_n) const; + + bool set_by_blk_idx(int64_t _blk_idx); + /* add/sub blocks return true if they reach EOL, else false */ + bool add_blks(uint64_t blk_count); + bool sub_blks(uint64_t blk_count); + + void dbg_print(const char * id_str, bool to_stdout, int verbose) const; + + friend int diff_between_iters(const struct scat_gath_iter & left, + const struct scat_gath_iter & right); + + friend bool sgls_eq_from_iters(const struct scat_gath_iter & left, + const struct scat_gath_iter & right, + bool allow_partial); + +private: + const scat_gath_list &sglist; + + /* dual representation: either it_el_ind,it_blk_off or blk_idx */ + int it_el_ind; /* refers to sge==sglist[it_el_ind] */ + int it_blk_off; /* refers to LBA==(sge.lba + it_blk_off) */ + int64_t blk_idx; /* in range: [0 .. sglist.sum) */ + bool extend_last; +}; + + +struct flags_t { + bool append; + bool coe; + bool dio; + bool direct; + bool dpo; + bool dsync; + bool excl; + bool ff; + bool fua; + bool masync; /* more async sg v4 driver fd flag */ + bool no_dur; + bool order; + bool qhead; + bool qtail; + bool random; + bool serial; + bool wq_excl; + bool zero; + int mmap; +}; + +typedef pair<int64_t, int> get_next_res; +typedef array<uint8_t, MAX_SCSI_CDB_SZ> cdb_arr_t; + +/* There is one instance of this structure and it is at file scope so it is + * initialized to zero. The design of this copy multi-threaded copy algorithm + * attempts to have no locks on the fast path. Contention in gcoll.get_next() + * is resolved by the loser repeating its operation. Statistics and error + * information is held in each thread until it shuts down and contention + * can occur at that point. */ +struct global_collection /* one instance visible to all threads */ +{ + /* get_next() is the pivotal function for multi-threaded safety. It can + * be safely called from all threads with the desired number of blocks + * (typically mrq*bpt) and this function returns a pair. The first pair + * value is the starting count value/index [0..dd_count) and the second + * pair value is the number of blocks to copy. If desired_num_blks is + * negative this flags an error has occurred. If the second value in the + * returned pair is 0 then the calling thread should shutdown; a + * negative value indicates an error has occurred (e.g. in another + * thread) and the calling thread should shutdown. */ + get_next_res get_next(int desired_num_blks); + atomic<int64_t> next_count_pos; + + int infd; + int64_t dd_count; + int in_type; + int cdbsz_in; + int help; + struct flags_t in_flags; + atomic<int64_t> in_rem_count; /* | count of remaining in blocks */ + atomic<int> in_partial; /* | */ + off_t in_st_size; /* Only for FT_OTHER (regular) file */ + int mrq_num; /* Number of multi-reqs for sg v4 */ + int outfd; + int out_type; + int out2fd; + int out2_type; + off_t out2_st_size; /* Only for FT_OTHER (regular) file */ + int cdbsz_out; + struct flags_t out_flags; + atomic<int64_t> out_rem_count; /* | count of remaining out blocks */ + atomic<int> out_partial; /* | */ + // pthread_mutex_t out2_mutex; + off_t out_st_size; /* Only for FT_OTHER (regular) file */ + condition_variable infant_cv; /* after thread:0 does first segment */ + mutex infant_mut; + bool processed; + int bs; + int bpt; + int outregfd; + int outreg_type; + off_t outreg_st_size; + atomic<int> dio_incomplete_count; + atomic<int> sum_of_resids; + int verbose; /* both -v and deb=VERB bump this field */ + int dry_run; + bool cdbsz_given; + bool count_given; + bool flexible; + bool ofile_given; + bool ofile2_given; + bool unit_nanosec; /* default duration unit is millisecond */ + bool mrq_cmds; /* mrq=<NRQS>,C given */ + bool verify; /* don't copy, verify like Unix: cmp */ + bool prefetch; /* for verify: do PF(b),RD(a),V(b)_a_data */ + const char * infp; + const char * outfp; + const char * out2fp; + class scat_gath_list i_sgl; + class scat_gath_list o_sgl; +}; + +typedef struct request_element +{ /* one instance per worker thread */ + struct global_collection *clp; + bool has_share; + bool both_sg; + bool same_sg; + bool only_in_sg; + bool only_out_sg; + bool stop_after_write; + int id; + int infd; + int outfd; + int out2fd; + int outregfd; + uint8_t * buffp; + uint8_t * alloc_bp; + struct sg_io_v4 io_hdr4[2]; + uint8_t cmd[MAX_SCSI_CDB_SZ]; + uint8_t sb[SENSE_BUFF_LEN]; + int dio_incomplete_count; + int mmap_active; + int resid; + int rd_p_id; + int rep_count; + int rq_id; + int mmap_len; + int mrq_id; + int mrq_index; + int mrq_pack_id_off; + int64_t in_follow_on; + int64_t out_follow_on; + int64_t in_local_count; + int64_t out_local_count; + int64_t in_rem_count; + int64_t out_rem_count; + int in_local_partial; + int out_local_partial; + int in_resid_bytes; + long seed; + struct drand48_data drand; /* opaque, used by srand48_r and mrand48_r */ +} Rq_elem; + +/* Additional parameters for sg_start_io() and sg_finish_io() */ +struct sg_io_extra { + bool is_wr2; + bool prefetch; + bool dout_is_split; + int hpv4_ind; + int blk_offset; + int blks; +}; + +#define MONO_MRQ_ID_INIT 0x10000 + +// typedef vector< pair<int, struct sg_io_v4> > mrq_arr_t; +typedef array<uint8_t, 32> big_cdb; /* allow up to a 32 byte cdb */ +typedef pair< vector<struct sg_io_v4>, vector<big_cdb> > mrq_arr_t; + + +/* Use this class to wrap C++11 <random> features to produce uniform random + * unsigned ints in the range [lo, hi] (inclusive) given a_seed */ +class Rand_uint { +public: + Rand_uint(unsigned int lo, unsigned int hi, unsigned int a_seed) + : uid(lo, hi), dre(a_seed) { } + /* uid ctor takes inclusive range when integral type */ + + unsigned int get() { return uid(dre); } + +private: + uniform_int_distribution<unsigned int> uid; + default_random_engine dre; +}; + +static atomic<long int> pos_index(0); + +static atomic<int> num_ebusy(0); +static atomic<int> num_start_eagain(0); +static atomic<int> num_fin_eagain(0); +static atomic<long> num_waiting_calls(0); + +static sigset_t signal_set; + +static const char * proc_allow_dio = "/proc/scsi/sg/allow_dio"; + +static int sg_in_open(struct global_collection *clp, const char *inf, + uint8_t **mmpp, int *mmap_len); +static int sg_out_open(struct global_collection *clp, const char *outf, + uint8_t **mmpp, int *mmap_len); +static int do_both_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it, + scat_gath_iter & o_sg_it, int seg_blks, + vector<cdb_arr_t> & a_cdb, + vector<struct sg_io_v4> & a_v4); +static int do_normal_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it, + scat_gath_iter & o_sg_it, int seg_blks, + vector<cdb_arr_t> & a_cdb, + vector<struct sg_io_v4> & a_v4); +static int do_normal_normal_segment(Rq_elem * rep, scat_gath_iter & i_sg_it, + scat_gath_iter & o_sg_it, int seg_blks); + +#define STRERR_BUFF_LEN 128 + +static mutex strerr_mut; + +static bool have_sg_version = false; +static int sg_version = 0; +static bool sg_version_ge_40030 = false; +static atomic<bool> shutting_down = false; +static bool do_sync = false; +static int do_time = 1; +static struct global_collection gcoll; +static struct timeval start_tm; +static int num_threads = DEF_NUM_THREADS; +static int exit_status = 0; +static bool after1 = false; + +static mutex rand_lba_mutex; + +static const char * my_name = "sg_mrq_dd: "; + +// static const char * mrq_blk_s = "mrq: ordinary blocking"; +static const char * mrq_svb_s = "mrq: shared variable blocking (svb)"; +static const char * mrq_ob_s = "mrq: ordered blocking"; +static const char * mrq_vb_s = "mrq: variable blocking"; + + +#ifdef __GNUC__ +static int pr2serr_lk(const char * fmt, ...) + __attribute__ ((format (printf, 1, 2))); +#if 0 +static void pr_errno_lk(int e_no, const char * fmt, ...) + __attribute__ ((format (printf, 2, 3))); +#endif +#else +static int pr2serr_lk(const char * fmt, ...); +#if 0 +static void pr_errno_lk(int e_no, const char * fmt, ...); +#endif +#endif + + +static int +pr2serr_lk(const char * fmt, ...) +{ + int n; + va_list args; + lock_guard<mutex> lk(strerr_mut); + + va_start(args, fmt); + n = vfprintf(stderr, fmt, args); + va_end(args); + return n; +} + +#if 0 // not used yet +static void +pr_errno_lk(int e_no, const char * fmt, ...) +{ + char b[180]; + va_list args; + lock_guard<mutex> lk(strerr_mut); + + va_start(args, fmt); + vsnprintf(b, sizeof(b), fmt, args); + fprintf(stderr, "%s: %s\n", b, strerror(e_no)); + va_end(args); +} +#endif + +static void +lk_print_command_len(const char *prefix, uint8_t * cmdp, int len, bool lock) +{ + if (lock) { + lock_guard<mutex> lk(strerr_mut); + + if (prefix && *prefix) + fputs(prefix, stderr); + sg_print_command_len(cmdp, len); + } else { + if (prefix && *prefix) + fputs(prefix, stderr); + sg_print_command_len(cmdp, len); + } +} + +static void +lk_chk_n_print4(const char * leadin, const struct sg_io_v4 * h4p, + bool raw_sinfo) +{ + lock_guard<mutex> lk(strerr_mut); + + sg_linux_sense_print(leadin, h4p->device_status, h4p->transport_status, + h4p->driver_status, (const uint8_t *)h4p->response, + h4p->response_len, raw_sinfo); +} + +static void +hex2stderr_lk(const uint8_t * b_str, int len, int no_ascii) +{ + lock_guard<mutex> lk(strerr_mut); + + hex2stderr(b_str, len, no_ascii); +} + +/* Flags decoded into abbreviations for those that are set, separated by + * '|' . */ +static char * +sg_flags_str(int flags, int b_len, char * b) +{ + int n = 0; + + if ((b_len < 1) || (! b)) + return b; + b[0] = '\0'; + if (SG_FLAG_DIRECT_IO & flags) { /* 0x1 */ + n += sg_scnpr(b + n, b_len - n, "DIO|"); + if (n >= b_len) + goto fini; + } + if (SG_FLAG_MMAP_IO & flags) { /* 0x4 */ + n += sg_scnpr(b + n, b_len - n, "MMAP|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_YIELD_TAG & flags) { /* 0x8 */ + n += sg_scnpr(b + n, b_len - n, "YTAG|"); + if (n >= b_len) + goto fini; + } + if (SG_FLAG_Q_AT_TAIL & flags) { /* 0x10 */ + n += sg_scnpr(b + n, b_len - n, "QTAI|"); + if (n >= b_len) + goto fini; + } + if (SG_FLAG_Q_AT_HEAD & flags) { /* 0x20 */ + n += sg_scnpr(b + n, b_len - n, "QHEA|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_NO_WAITQ & flags) { /* 0x40 */ + n += sg_scnpr(b + n, b_len - n, "NWTQ|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_DOUT_OFFSET & flags) { /* 0x80 */ + n += sg_scnpr(b + n, b_len - n, "DOFF|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_COMPLETE_B4 & flags) { /* 0x100 */ + n += sg_scnpr(b + n, b_len - n, "NWTQ|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_SIGNAL & flags) { /* 0x200 */ + n += sg_scnpr(b + n, b_len - n, "SIGNAL|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_IMMED & flags) { /* 0x400 */ + n += sg_scnpr(b + n, b_len - n, "IMM|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_STOP_IF & flags) { /* 0x800 */ + n += sg_scnpr(b + n, b_len - n, "STOPIF|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_DEV_SCOPE & flags) { /* 0x1000 */ + n += sg_scnpr(b + n, b_len - n, "DEV_SC|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_SHARE & flags) { /* 0x2000 */ + n += sg_scnpr(b + n, b_len - n, "SHARE|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_DO_ON_OTHER & flags) { /* 0x4000 */ + n += sg_scnpr(b + n, b_len - n, "DO_OTH|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_KEEP_SHARE & flags) { /* 0x8000 */ + n += sg_scnpr(b + n, b_len - n, "KEEP_SH|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_NO_DXFER & flags) { /* 0x10000 */ + n += sg_scnpr(b + n, b_len - n, "NDXFER|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_MULTIPLE_REQS & flags) { /* 0x20000 */ + n += sg_scnpr(b + n, b_len - n, "MRQS|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_EVENTFD & flags) { /* 0x40000 */ + n += sg_scnpr(b + n, b_len - n, "EVFD|"); + if (n >= b_len) + goto fini; + } + if (SGV4_FLAG_ORDERED_SLV & flags) { /* 0x80000 */ + n += sg_scnpr(b + n, b_len - n, "OSLV|"); + if (n >= b_len) + goto fini; + } +fini: + if (n < b_len) { /* trim trailing '\' */ + if ('|' == b[n - 1]) + b[n - 1] = '\0'; + } else if ('|' == b[b_len - 1]) + b[b_len - 1] = '\0'; + return b; +} + +static void +v4hdr_out_lk(const char * leadin, const sg_io_v4 * h4p, int id) +{ + lock_guard<mutex> lk(strerr_mut); + char b[80]; + + if (leadin) + pr2serr("%s [id=%d]:\n", leadin, id); + if (('Q' != h4p->guard) || (0 != h4p->protocol) || + (0 != h4p->subprotocol)) + pr2serr(" <<<sg_io_v4 _NOT_ properly set>>>\n"); + pr2serr(" pointers: cdb=%s sense=%s din=%p dout=%p\n", + (h4p->request ? "y" : "NULL"), (h4p->response ? "y" : "NULL"), + (void *)h4p->din_xferp, (void *)h4p->dout_xferp); + pr2serr(" lengths: cdb=%u sense=%u din=%u dout=%u\n", + h4p->request_len, h4p->max_response_len, h4p->din_xfer_len, + h4p->dout_xfer_len); + pr2serr(" flags=0x%x request_extra{pack_id}=%d\n", + h4p->flags, h4p->request_extra); + pr2serr(" flags set: %s\n", sg_flags_str(h4p->flags, sizeof(b), b)); + pr2serr(" OUT:\n"); + pr2serr(" response_len=%d driver/transport/device_status=" + "0x%x/0x%x/0x%x\n", h4p->response_len, h4p->driver_status, + h4p->transport_status, h4p->device_status); + pr2serr(" info=0x%x din_resid=%u dout_resid=%u spare_out=%u " + "dur=%u\n", + h4p->info, h4p->din_resid, h4p->dout_resid, h4p->spare_out, + h4p->duration); +} + +static void +fetch_sg_version(void) +{ + FILE * fp; + char b[96]; + + have_sg_version = false; + sg_version = 0; + fp = fopen(PROC_SCSI_SG_VERSION, "r"); + if (fp && fgets(b, sizeof(b) - 1, fp)) { + if (1 == sscanf(b, "%d", &sg_version)) + have_sg_version = !!sg_version; + } else { + int j, k, l; + + if (fp) + fclose(fp); + fp = fopen(SYS_SCSI_SG_VERSION, "r"); + if (fp && fgets(b, sizeof(b) - 1, fp)) { + if (3 == sscanf(b, "%d.%d.%d", &j, &k, &l)) { + sg_version = (j * 10000) + (k * 100) + l; + have_sg_version = !!sg_version; + } + } + } + if (fp) + fclose(fp); +} + +static void +calc_duration_throughput(int contin) +{ + struct timeval end_tm, res_tm; + double a, b; + + gettimeofday(&end_tm, NULL); + res_tm.tv_sec = end_tm.tv_sec - start_tm.tv_sec; + res_tm.tv_usec = end_tm.tv_usec - start_tm.tv_usec; + if (res_tm.tv_usec < 0) { + --res_tm.tv_sec; + res_tm.tv_usec += 1000000; + } + a = res_tm.tv_sec; + a += (0.000001 * res_tm.tv_usec); + b = (double)gcoll.bs * (gcoll.dd_count - gcoll.out_rem_count.load()); + pr2serr("time to transfer data %s %d.%06d secs", + (contin ? "so far" : "was"), (int)res_tm.tv_sec, + (int)res_tm.tv_usec); + if ((a > 0.00001) && (b > 511)) + pr2serr(", %.2f MB/sec\n", b / (a * 1000000.0)); + else + pr2serr("\n"); +} + +static void +print_stats(const char * str) +{ + int64_t infull, outfull; + + if (0 != gcoll.out_rem_count.load()) + pr2serr(" remaining block count=%" PRId64 "\n", + gcoll.out_rem_count.load()); + infull = gcoll.dd_count - gcoll.in_rem_count.load(); + pr2serr("%s%" PRId64 "+%d records in\n", str, + infull, gcoll.in_partial.load()); + + outfull = gcoll.dd_count - gcoll.out_rem_count.load(); + pr2serr("%s%" PRId64 "+%d records %s\n", str, + outfull, gcoll.out_partial.load(), + (gcoll.verify ? "verified" : "out")); +} + +static void +interrupt_handler(int sig) +{ + struct sigaction sigact; + + sigact.sa_handler = SIG_DFL; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction(sig, &sigact, NULL); + pr2serr("Interrupted by signal,"); + if (do_time > 0) + calc_duration_throughput(0); + print_stats(""); + kill(getpid (), sig); +} + +static void +siginfo_handler(int sig) +{ + if (sig) { ; } /* unused, dummy to suppress warning */ + pr2serr("Progress report, continuing ...\n"); + if (do_time > 0) + calc_duration_throughput(1); + print_stats(" "); +} + +static void +siginfo2_handler(int sig) +{ + if (sig) { ; } /* unused, dummy to suppress warning */ + pr2serr("Progress report, continuing ...\n"); + if (do_time > 0) + calc_duration_throughput(1); + print_stats(" "); +} + +static void +install_handler(int sig_num, void (*sig_handler) (int sig)) +{ + struct sigaction sigact; + sigaction (sig_num, NULL, &sigact); + if (sigact.sa_handler != SIG_IGN) + { + sigact.sa_handler = sig_handler; + sigemptyset (&sigact.sa_mask); + sigact.sa_flags = 0; + sigaction (sig_num, &sigact, NULL); + } +} + +#if 0 /* SG_LIB_ANDROID */ +static void +thread_exit_handler(int sig) +{ + pthread_exit(0); +} +#endif + +/* Make safe_strerror() thread safe */ +static char * +tsafe_strerror(int code, char * ebp) +{ + lock_guard<mutex> lk(strerr_mut); + char * cp; + + cp = safe_strerror(code); + strncpy(ebp, cp, STRERR_BUFF_LEN); + ebp[STRERR_BUFF_LEN - 1] = '\0'; + return ebp; +} + + +/* Following macro from D.R. Butenhof's POSIX threads book: + * ISBN 0-201-63392-2 . Changed __FILE__ to __func__ */ +#define err_exit(code,text) do { \ + char strerr_buff[STRERR_BUFF_LEN]; \ + pr2serr("%s at \"%s\":%d: %s\n", \ + text, __func__, __LINE__, tsafe_strerror(code, strerr_buff)); \ + exit(1); \ + } while (0) + + +static int +dd_filetype(const char * filename, off_t & st_size) +{ + struct stat st; + size_t len = strlen(filename); + + if ((1 == len) && ('.' == filename[0])) + return FT_DEV_NULL; + if (stat(filename, &st) < 0) + return FT_ERROR; + if (S_ISCHR(st.st_mode)) { + if ((MEM_MAJOR == major(st.st_rdev)) && + (DEV_NULL_MINOR_NUM == minor(st.st_rdev))) + return FT_DEV_NULL; + if (RAW_MAJOR == major(st.st_rdev)) + return FT_RAW; + if (SCSI_GENERIC_MAJOR == major(st.st_rdev)) + return FT_SG; + if (SCSI_TAPE_MAJOR == major(st.st_rdev)) + return FT_ST; + } else if (S_ISBLK(st.st_mode)) + return FT_BLOCK; + else if (S_ISFIFO(st.st_mode)) + return FT_FIFO; + st_size = st.st_size; + return FT_OTHER; +} + +static void +usage(int pg_num) +{ + if (pg_num > 3) + goto page4; + else if (pg_num > 2) + goto page3; + else if (pg_num > 1) + goto page2; + + pr2serr("Usage: sg_mrq_dd [bs=BS] [count=COUNT] [ibs=BS] [if=IFILE]" + " [iflag=FLAGS]\n" + " [obs=BS] [of=OFILE] [oflag=FLAGS] " + "[seek=SEEK]\n" + " [skip=SKIP] [--help] [--version]\n\n"); + pr2serr(" [bpt=BPT] [cdbsz=6|10|12|16] " + "[coe=0|1]\n" + " [deb=VERB] [dio=0|1]\n" + " [fua=0|1|2|3] [mrq=MRQ[,C]] " + "[of2=OFILE2]\n" + " [ofreg=OFREG] [sync=0|1] [thr=THR] " + "[time=0|1]\n" + " [verbose=VERB] [--dry-run] " + "\n" + " [--verbose] [--verify] [--version]\n\n" + " where the main options (shown in first group above) are:\n" + " bs must be device logical block size (default " + "512)\n" + " count number of blocks to copy (def: device size)\n" + " if file or device to read from (def: stdin)\n" + " iflag comma separated list from: [coe,dio," + "direct,dpo,\n" + " dsync,excl,fua,masync,mmap,nodur,\n" + " null,order,qtail,serial,wq_excl]\n" + " mrq number of cmds placed in each sg call " + "(def: 16);\n" + " may have trailing ',C', to send bulk cdb_s\n" + " of file or device to write to (def: /dev/null " + "N.B. different\n" + " from dd it defaults to stdout). If 'of=.' " + "uses /dev/null\n" + " of2 second file or device to write to (def: " + "/dev/null)\n" + " oflag comma separated list from: [append,<<list from " + "iflag>>]\n" + " seek block position to start writing to OFILE\n" + " skip block position to start reading from IFILE\n" + " --help|-h output this usage message then exit\n" + " --prefetch|-p with verify: do pre-fetch first\n" + " --verify|-x do a verify (compare) operation [def: do a " + "copy]\n" + " --version|-V output version string then exit\n\n" + "Copy IFILE to OFILE, similar to dd command. This utility is " + "specialized for\nSCSI devices and uses multiple POSIX threads. " + "It expects one or both IFILE\nand OFILE to be sg devices. With " + "--verify option does a verify/compare\noperation instead of a " + "copy. This utility is Linux specific and uses the\nv4 sg " + "driver 'share' capability if available. Use '-hh', '-hhh' or " + "'-hhhh'\nfor more information.\n" + ); + return; +page2: + pr2serr("Syntax: sgh_dd [operands] [options]\n\n" + " where: operands have the form name=value and are pecular to " + "'dd'\n" + " style commands, and options start with one or " + "two hyphens;\n" + " the lesser used operands and option are:\n\n" + " bpt is blocks_per_transfer (default is 128)\n" + " cdbsz size of SCSI READ, WRITE or VERIFY cdb_s " + "(default is 10)\n" + " coe continue on error, 0->exit (def), " + "1->zero + continue\n" + " deb for debug, 0->none (def), > 0->varying degrees " + "of debug\n" + " dio is direct IO, 1->attempt, 0->indirect IO (def)\n" + " fua force unit access: 0->don't(def), 1->OFILE, " + "2->IFILE,\n" + " 3->OFILE+IFILE\n" + " ofreg OFREG is regular file or pipe to send what is " + "read from\n" + " IFILE in the first half of each shared element\n" + " sync 0->no sync(def), 1->SYNCHRONIZE CACHE on OFILE " + "after copy\n" + " thr is number of threads, must be > 0, default 4, " + "max 1024\n" + " time 0->no timing, 1->calc throughput(def), " + "2->nanosec precision\n" + " verbose same as 'deb=VERB': increase verbosity\n" + " --dry-run|-d prepare but bypass copy/read\n" + " --verbose|-v increase verbosity of utility\n\n" + "Use '-hhh' or '-hhhh' for more information about flags.\n" + ); + return; +page3: + pr2serr("Syntax: sgh_dd [operands] [options]\n\n" + " where: 'iflag=<arg>' and 'oflag=<arg>' arguments are listed " + "below:\n\n" + " 00 use all zeros instead of if=IFILE (only in " + "iflags)\n" + " append append output to OFILE (assumes OFILE is " + "regular file)\n" + " coe continue of error (reading, fills with zeros)\n" + " dio sets the SG_FLAG_DIRECT_IO in sg requests\n" + " direct sets the O_DIRECT flag on open()\n" + " dpo sets the DPO (disable page out) in SCSI READs " + "and WRITEs\n" + " dsync sets the O_SYNC flag on open()\n" + " excl sets the O_EXCL flag on open()\n" + " ff use all 0xff bytes instead of if=IFILE (only in " + "iflags)\n" + " fua sets the FUA (force unit access) in SCSI READs " + "and WRITEs\n" + " masync set 'more async' flag on this sg device\n" + " mmap setup mmap IO on IFILE or OFILE\n" + " mmap,mmap when used twice, doesn't call munmap()\n" + " mrq_svb if mrq and sg->sg copy, do shared_variable_" + "blocking\n" + " nodur turns off command duration calculations\n" + " order require write ordering on sg->sg copy; only " + "for oflag\n" + " qhead queue new request at head of block queue\n" + " qtail queue new request at tail of block queue (def: " + "q at head)\n" + " random use random data instead of if=IFILE (only in " + "iflags)\n" + " serial serialize sg command execution (def: overlap)\n" + " wq_excl set SG_CTL_FLAGM_EXCL_WAITQ on this sg fd\n" + "\n" + "Copies IFILE to OFILE (and to OFILE2 if given). If IFILE and " + "OFILE are sg\ndevices 'shared' mode is selected. of2=OFILE2 " + "uses 'oflag=FLAGS'. When sharing, the data stays in a\nsingle " + "in-kernel buffer which is copied (or mmap-ed) to the user " + "space\nif the 'ofreg=OFREG' is given. Use '-hhhh' for more " + "information.\n" + ); + return; +page4: + pr2serr("pack_id:\n" + "These are ascending integers, starting at 1, associated with " + "each issued\nSCSI command. When both IFILE and OFILE are sg " + "devices, then the READ in\neach read-write pair is issued an " + "even pack_id and its WRITE pair is\ngiven the pack_id one " + "higher (i.e. an odd number). This enables a\n'cat '" + "/proc/scsi/sg/debug' user to see that progress is being " + "made.\n\n"); + pr2serr("Debugging:\n" + "Apart from using one or more '--verbose' options which gets a " + "bit noisy\n'cat /proc/scsi/sg/debug' can give a good overview " + "of what is happening.\nThat does a sg driver object tree " + "traversal that does minimal locking\nto make sure that each " + "traversal is 'safe'. So it is important to note\nthe whole " + "tree is not locked. This means for fast devices the overall\n" + "tree state may change while the traversal is occurring. For " + "example,\nit has been observed that both the master and slave " + "sides of a request\nshare show they are in 'active' state " + "which should not be possible.\nIt occurs because the master " + "probably jumped out of active state and\nthe slave request " + "entered it while some other nodes were being printed.\n\n"); + pr2serr("Busy state:\n" + "Busy state (abbreviated to 'bsy' in the /proc/scsi/sg/debug " + "output)\nis entered during request setup and completion. It " + "is intended to be\na temporary state. It should not block " + "but does sometimes (e.g. in\nblock_get_request()). Even so " + "that blockage should be short and if not\nthere is a " + "problem.\n\n"); + pr2serr("--verify :\n" + "For comparing IFILE with OFILE. Does repeated sequences of: " + "READ(ifile)\nand uses data returned to send to VERIFY(ofile, " + "BYTCHK=1). So the OFILE\ndevice/disk is doing the actual " + "comparison. Stops on first miscompare.\n\n"); + pr2serr("--prefetch :\n" + "Used with --verify option. Prepends a PRE-FETCH(ofile, IMMED) " + "to verify\nsequence. This should speed the trailing VERIFY by " + "making sure that\nthe data it needs for the comparison is " + "already in its cache.\n"); + return; +} + + +bool +scat_gath_list::empty() const +{ + return sgl.empty(); +} + +bool +scat_gath_list::empty_or_00() const +{ + if (sgl.empty()) + return true; + return ((sgl.size() == 1) && (sgl[0].lba == 0) && (sgl[0].num == 0)); +} + +int +scat_gath_list::num_elems() const +{ + return sgl.size(); +} + + +/* Read numbers (up to 64 bits in size) from command line (comma (or + * (single) space **) separated list). Assumed decimal unless prefixed + * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex). + * Returns 0 if ok, or 1 if error. Assumed to be LBA (64 bit) and + * number_of_block (32 bit) pairs. ** Space on command line needs to + * be escaped, otherwise it is an operand/option separator. */ +bool +scat_gath_list::load_from_cli(const char * cl_p, bool b_vb) +{ + bool split, full_pair; + int in_len, k, j; + const int max_nbs = MAX_SGL_NUM_VAL; + int64_t ll, large_num; + uint64_t prev_lba; + char * cp; + char * c2p; + const char * lcp; + struct scat_gath_elem sge; + + if (NULL == cl_p) { + pr2serr("%s: bad arguments\n", __func__); + goto err_out; + } + lcp = cl_p; + in_len = strlen(cl_p); + if ('-' == cl_p[0]) { /* read from stdin */ + pr2serr("%s: logic error: no stdin here\n", __func__); + goto err_out; + } else { /* list of numbers (default decimal) on command line */ + k = strspn(cl_p, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, "); + if (in_len != k) { + if (b_vb) + pr2serr("%s: error at pos %d\n", __func__, k + 1); + goto err_out; + } + j = 0; + full_pair = true; + for (k = 0, split = false; ; ++k) { + if (split) { + /* splitting given elem with large number_of_blocks into + * multiple elems within array being built */ + ++j; + sge.lba = prev_lba + (uint64_t)max_nbs; + if (large_num > max_nbs) { + sge.num = (uint32_t)max_nbs; + prev_lba = sge.lba; + large_num -= max_nbs; + sgl.push_back(sge); + } else { + sge.num = (uint32_t)large_num; + split = false; + if (b_vb) + pr2serr("%s: split large sg elem into %d element%s\n", + __func__, j, (j == 1 ? "" : "s")); + sgl.push_back(sge); + goto check_for_next; + } + continue; + } + full_pair = false; + ll = sg_get_llnum(lcp); + if (-1 != ll) { + sge.lba = (uint64_t)ll; + cp = (char *)strchr(lcp, ','); + c2p = (char *)strchr(lcp, ' '); + if (NULL == cp) { + cp = c2p; + if (NULL == cp) + break; + } + if (c2p && (c2p < cp)) + cp = c2p; + lcp = cp + 1; + } else { + if (b_vb) + pr2serr("%s: error at pos %d\n", __func__, + (int)(lcp - cl_p + 1)); + goto err_out; + } + ll = sg_get_llnum(lcp); + if (ll >= 0) { + full_pair = true; + if (ll > max_nbs) { + sge.num = (uint32_t)max_nbs; + prev_lba = sge.lba; + large_num = ll - max_nbs; + split = true; + j = 1; + continue; + } + sge.num = (uint32_t)ll; + } else { /* bad or negative number as number_of_blocks */ + if (b_vb) + pr2serr("%s: bad number at pos %d\n", __func__, + (int)(lcp - cl_p + 1)); + goto err_out; + } + sgl.push_back(sge); +check_for_next: + cp = (char *)strchr(lcp, ','); + c2p = (char *)strchr(lcp, ' '); + if (NULL == cp) { + cp = c2p; + if (NULL == cp) + break; + } + if (c2p && (c2p < cp)) + cp = c2p; + lcp = cp + 1; + } /* end of for loop over items in operand */ + /* other than first pair, expect even number of items */ + if ((k > 0) && (! full_pair)) { + if (b_vb) + pr2serr("%s: expected even number of items: " + "LBA0,NUM0,LBA1,NUM1...\n", __func__); + goto err_out; + } + } + return true; +err_out: + if (0 == m_errno) + m_errno = SG_LIB_SYNTAX_ERROR; + return false; +} + +bool +scat_gath_list::file2sgl_helper(FILE * fp, const char * fnp, bool def_hex, + bool flexible, bool b_vb) +{ + bool bit0; + bool pre_addr1 = true; + bool pre_hex_seen = false; + int in_len, k, j, m, ind; + const int max_nbs = MAX_SGL_NUM_VAL; + int off = 0; + int64_t ll; + uint64_t ull, prev_lba; + char * lcp; + struct scat_gath_elem sge; + char line[1024]; + + for (j = 0 ; ; ++j) { + if (NULL == fgets(line, sizeof(line), fp)) + break; + // could improve with carry_over logic if sizeof(line) too small + in_len = strlen(line); + if (in_len > 0) { + if ('\n' == line[in_len - 1]) { + --in_len; + line[in_len] = '\0'; + } else { + m_errno = SG_LIB_SYNTAX_ERROR; + if (b_vb) + pr2serr("%s: %s: line too long, max %d bytes\n", + __func__, fnp, (int)(sizeof(line) - 1)); + goto err_out; + } + } + if (in_len < 1) + continue; + lcp = line; + m = strspn(lcp, " \t"); + if (m == in_len) + continue; + lcp += m; + in_len -= m; + if ('#' == *lcp) + continue; + if (pre_addr1 || pre_hex_seen) { + /* Accept lines with leading 'HEX' and ignore as long as there + * is one _before_ any LBA,NUM lines in the file. This allows + * HEX marked sgls to be concaternated together. */ + if (('H' == toupper(lcp[0])) && ('E' == toupper(lcp[1])) && + ('X' == toupper(lcp[2]))) { + pre_hex_seen = true; + if (def_hex) + continue; /* bypass 'HEX' marker line if expecting hex */ + else { + if (flexible) { + def_hex = true; /* okay, switch to hex parse */ + continue; + } else { + pr2serr("%s: %s: 'hex' string detected on line %d, " + "expecting decimal\n", __func__, fnp, j + 1); + m_errno = EINVAL; + goto err_out; + } + } + } + } + k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXbBdDiIkKmMgGtTpP, \t"); + if ((k < in_len) && ('#' != lcp[k])) { + m_errno = EINVAL; + if (b_vb) + pr2serr("%s: %s: syntax error at line %d, pos %d\n", + __func__, fnp, j + 1, m + k + 1); + goto err_out; + } + for (k = 0; k < 256; ++k) { + /* limit parseable items on one line to 256 */ + if (def_hex) { /* don't accept negatives or multipliers */ + if (1 == sscanf(lcp, "%" SCNx64, &ull)) + ll = (int64_t)ull; + else + ll = -1; /* use (2**64 - 1) as error flag */ + } else + ll = sg_get_llnum(lcp); + if (-1 != ll) { + ind = ((off + k) >> 1); + bit0 = !! (0x1 & (off + k)); + if (ind >= SG_SGL_MAX_ELEMENTS) { + m_errno = EINVAL; + if (b_vb) + pr2serr("%s: %s: array length exceeded\n", __func__, + fnp); + goto err_out; + } + if (bit0) { /* bit0 set when decoding a NUM */ + if (ll < 0) { + m_errno = EINVAL; + if (b_vb) + pr2serr("%s: %s: bad number in line %d, at pos " + "%d\n", __func__, fnp, j + 1, + (int)(lcp - line + 1)); + goto err_out; + } + if (ll > max_nbs) { + int h = 1; + + /* split up this elem into multiple, smaller elems */ + do { + sge.num = (uint32_t)max_nbs; + prev_lba = sge.lba; + sgl.push_back(sge); + sge.lba = prev_lba + (uint64_t)max_nbs; + ++h; + off += 2; + ll -= max_nbs; + } while (ll > max_nbs); + if (b_vb) + pr2serr("%s: split large sg elem into %d " + "elements\n", __func__, h); + } + sge.num = (uint32_t)ll; + sgl.push_back(sge); + } else { /* bit0 clear when decoding a LBA */ + if (pre_addr1) + pre_addr1 = false; + sge.lba = (uint64_t)ll; + } + } else { /* failed to decode number on line */ + if ('#' == *lcp) { /* numbers before #, rest of line comment */ + --k; + break; /* goes to next line */ + } + m_errno = EINVAL; + if (b_vb) + pr2serr("%s: %s: error in line %d, at pos %d\n", + __func__, fnp, j + 1, (int)(lcp - line + 1)); + goto err_out; + } + lcp = strpbrk(lcp, " ,\t#"); + if ((NULL == lcp) || ('#' == *lcp)) + break; + lcp += strspn(lcp, " ,\t"); + if ('\0' == *lcp) + break; + } /* <<< end of for(k < 256) loop */ + off += (k + 1); + } /* <<< end of for loop, one iteration per line */ + /* allow one items, but not higher odd number of items */ + if ((off > 1) && (0x1 & off)) { + m_errno = EINVAL; + if (b_vb) + pr2serr("%s: %s: expect even number of items: " + "LBA0,NUM0,LBA1,NUM1...\n", __func__, fnp); + goto err_out; + } + clearerr(fp); /* even EOF on first pass needs this before rescan */ + return true; +err_out: + clearerr(fp); + return false; +} + +/* Read numbers from filename (or stdin), line by line (comma (or (single) + * space) separated list); places starting_LBA,number_of_block pairs in an + * array of scat_gath_elem elements pointed to by the returned value. If + * this fails NULL is returned and an error number is written to errp (if it + * is non-NULL). Assumed decimal (and may have suffix multipliers) when + * def_hex==false; if a number is prefixed by '0x', '0X' or contains trailing + * 'h' or 'H' that denotes a hex number. When def_hex==true all numbers are + * assumed to be hex (ignored '0x' prefixes and 'h' suffixes) and multiplers + * are not permitted. Heap allocates an array just big enough to hold all + * elements if the file is countable. Pipes and stdin are not considered + * countable. In the non-countable case an array of MAX_FIXED_SGL_ELEMS + * elements is pre-allocated; if it is exceeded sg_convert_errno(EDOM) is + * placed in *errp (if it is non-NULL). One of the first actions is to write + * 0 to *errp (if it is non-NULL) so the caller does not need to zero it + * before calling. */ +bool +scat_gath_list::load_from_file(const char * file_name, bool def_hex, + bool flexible, bool b_vb) +{ + bool have_stdin; + bool have_err = false; + FILE * fp; + const char * fnp; + + have_stdin = ((1 == strlen(file_name)) && ('-' == file_name[0])); + if (have_stdin) { + fp = stdin; + fnp = "<stdin>"; + } else { + fnp = file_name; + fp = fopen(fnp, "r"); + if (NULL == fp) { + m_errno = errno; + if (b_vb) + pr2serr("%s: opening %s: %s\n", __func__, fnp, + safe_strerror(m_errno)); + return false; + } + } + if (! file2sgl_helper(fp, fnp, def_hex, flexible, b_vb)) + have_err = true; + if (! have_stdin) + fclose(fp); + return have_err ? false : true; +} + +const char * +scat_gath_list::linearity_as_str() const +{ + switch (linearity) { + case SGL_LINEAR: + return "linear"; + case SGL_MONOTONIC: + return "monotonic"; + case SGL_MONO_OVERLAP: + return "monotonic, overlapping"; + case SGL_NON_MONOTONIC: + return "non-monotonic"; + default: + return "unknown"; + } +} + +void +scat_gath_list::set_weaker_linearity(enum sgl_linearity_e lin) +{ + int i_lin = (int)lin; + + if (i_lin > (int)linearity) + linearity = lin; +} + +/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose + * output. */ +void +scat_gath_list::dbg_print(bool skip_meta, const char * id_str, bool to_stdout, + bool show_sgl, bool lock) const +{ + int k; + if (lock) + strerr_mut.lock(); + int num = sgl.size(); + const char * caller = id_str ? id_str : "unknown"; + FILE * fp = to_stdout ? stdout : stderr; + + if (! skip_meta) { + fprintf(fp, "%s: elems=%d, sgl %spresent, linearity=%s\n", + caller, num, (sgl.empty() ? "not " : ""), + linearity_as_str()); + fprintf(fp, " sum=%" PRId64 ", sum_hard=%s lowest=0x%" PRIx64 + ", high_lba_p1=", sum, (sum_hard ? "true" : "false"), + lowest_lba); + fprintf(fp, "0x%" PRIx64 "\n", high_lba_p1); + } + fprintf(fp, " >> %s scatter gather list (%d element%s):\n", caller, num, + (num == 1 ? "" : "s")); + if (show_sgl) { + for (k = 0; k < num; ++k) { + const struct scat_gath_elem & sge = sgl[k]; + + fprintf(fp, " lba: 0x%" PRIx64 ", number: 0x%" PRIx32, + sge.lba, sge.num); + if (sge.lba > 0) + fprintf(fp, " [next lba: 0x%" PRIx64 "]", sge.lba + sge.num); + fprintf(fp, "\n"); + } + } + if (lock) + strerr_mut.unlock(); +} + +/* Assumes sgl array (vector) is setup. The other fields in this object are + * set by analyzing sgl in a single pass. The fields that are set are: + * fragmented, lowest_lba, high_lba_p1, monotonic, overlapping, sum and + * sum_hard. Degenerate elements (i.e. those with 0 blocks) are ignored apart + * from when one is last which makes sum_hard false and its LBA becomes + * high_lba_p1 if it is the highest in the list. An empty sgl is equivalent + * to a 1 element list with [0, 0], so sum_hard==false, monit==true, + * fragmented==false and overlapping==false . id_str may be NULL, present + * to enhance verbose output. */ +void +scat_gath_list::sum_scan(const char * id_str, bool show_sgl, bool b_vb) +{ + bool degen = false; + bool first = true; + bool regular = true; /* no overlapping segments detected */ + int k; + int elems = sgl.size(); + uint32_t prev_num, t_num; + uint64_t prev_lba, t_lba, low, high, end; + + sum = 0; + for (k = 0, low = 0, high = 0; k < elems; ++k) { + const struct scat_gath_elem & sge = sgl[k]; + + degen = false; + t_num = sge.num; + if (0 == t_num) { + degen = true; + if (! first) + continue; /* ignore degen element that not first */ + } + if (first) { + low = sge.lba; + sum = t_num; + high = sge.lba + sge.num; + first = false; + } else { + t_lba = sge.lba; + if ((prev_lba + prev_num) != t_lba) + set_weaker_linearity(SGL_MONOTONIC); + sum += t_num; + end = t_lba + t_num; + if (end > high) + high = end; /* high is one plus highest LBA */ + if (prev_lba < t_lba) + ; + else if (prev_lba == t_lba) { + if (prev_num > 0) { + set_weaker_linearity(SGL_MONO_OVERLAP); + break; + } + } else { + low = t_lba; + set_weaker_linearity(SGL_NON_MONOTONIC); + break; + } + if (regular) { + if ((prev_lba + prev_num) > t_lba) + regular = false; + } + } + prev_lba = sge.lba; + prev_num = sge.num; + } /* end of for loop while still elements and monot true */ + + if (k < elems) { /* only here if above breaks are taken */ + prev_lba = t_lba; + ++k; + for ( ; k < elems; ++k) { + const struct scat_gath_elem & sge = sgl[k]; + + degen = false; + t_lba = sge.lba; + t_num = sge.num; + if (0 == t_num) { + degen = true; + continue; + } + sum += t_num; + end = t_lba + t_num; + if (end > high) + high = end; + if (prev_lba > t_lba) { + if (t_lba < low) + low = t_lba; + } + prev_lba = t_lba; + } + } else + if (! regular) + set_weaker_linearity(SGL_MONO_OVERLAP); + + lowest_lba = low; + if (degen && (elems > 0)) { /* last element always impacts high_lba_p1 */ + t_lba = sgl[elems - 1].lba; + high_lba_p1 = (t_lba > high) ? t_lba : high; + } else + high_lba_p1 = high; + sum_hard = (elems > 0) ? ! degen : false; + if (b_vb) + dbg_print(false, id_str, false, show_sgl); +} + +/* Usually will append (or add to start if empty) sge unless 'extra_blks' + * exceeds MAX_SGL_NUM_VAL. In that case multiple sge_s are added with + * sge.num = MAX_SGL_NUM_VAL or less (for final sge) until extra_blks is + * exhausted. Returns new size of scatter gather list. */ +int +scat_gath_list::append_1or(int64_t extra_blks, int64_t start_lba) +{ + int o_num = sgl.size(); + const int max_nbs = MAX_SGL_NUM_VAL; + int64_t cnt = 0; + struct scat_gath_elem sge; + + if ((extra_blks <= 0) || (start_lba < 0)) + return o_num; /* nothing to do */ + if ((o_num > 0) && (! sum_hard)) { + sge = sgl[o_num - 1]; /* assume sge.num==0 */ + if (sge.lba == (uint64_t)start_lba) { + if (extra_blks <= max_nbs) + sge.num = extra_blks; + else + sge.num = max_nbs; + sgl[o_num - 1] = sge; + cnt = sge.num; + sum += cnt; + sum_hard = true; + if (cnt <= extra_blks) { + high_lba_p1 = sge.lba + cnt; + return o_num; + } + } + } else if (0 == o_num) + lowest_lba = start_lba; + + for ( ; cnt < extra_blks; cnt += max_nbs) { + sge.lba = start_lba + cnt; + if ((extra_blks - cnt) <= max_nbs) + sge.num = extra_blks - cnt; + else + sge.num = max_nbs; + sgl.push_back(sge); + sum += sge.num; + } /* always loops at least once */ + sum_hard = true; + high_lba_p1 = sge.lba + sge.num; + return sgl.size(); +} + +int +scat_gath_list::append_1or(int64_t extra_blks) +{ + int o_num = sgl.size(); + if (o_num < 1) + return append_1or(extra_blks, 0); + + struct scat_gath_elem sge = sgl[o_num - 1]; + return append_1or(extra_blks, sge.lba + sge.num); +} + +bool +sgls_eq_off(const scat_gath_list & left, int l_e_ind, int l_blk_off, + const scat_gath_list & right, int r_e_ind, int r_blk_off, + bool allow_partial) +{ + int lrem, rrem; + int lelems = left.sgl.size(); + int relems = right.sgl.size(); + + while ((l_e_ind < lelems) && (r_e_ind < relems)) { + if ((left.sgl[l_e_ind].lba + l_blk_off) != + (right.sgl[r_e_ind].lba + r_blk_off)) + return false; + lrem = left.sgl[l_e_ind].num - l_blk_off; + rrem = right.sgl[r_e_ind].num - r_blk_off; + if (lrem == rrem) { + ++l_e_ind; + l_blk_off = 0; + ++r_e_ind; + r_blk_off = 0; + } else if (lrem < rrem) { + ++l_e_ind; + l_blk_off = 0; + r_blk_off += lrem; + } else { + ++r_e_ind; + r_blk_off = 0; + l_blk_off += rrem; + } + } + if ((l_e_ind >= lelems) && (r_e_ind >= relems)) + return true; + return allow_partial; +} + +/* If bad arguments returns -1, otherwise returns the lowest LBA in *sglp . + * If no elements considered returns 0. If ignore_degen is true than + * ignores all elements with sge.num zero unless always_last is also + * true in which case the last element is always considered. */ +int64_t +scat_gath_list::get_lowest_lba(bool ignore_degen, bool always_last) const +{ + int k; + const int num_elems = sgl.size(); + bool some = (num_elems > 0); + int64_t res = INT64_MAX; + + for (k = 0; k < num_elems; ++k) { + if ((0 == sgl[k].num) && ignore_degen) + continue; + if ((int64_t)sgl[k].lba < res) + res = sgl[k].lba; + } + if (always_last && some) { + if ((int64_t)sgl[k - 1].lba < res) + res = sgl[k - 1].lba; + } + return (INT64_MAX == res) ? 0 : res; +} + +/* Returns >= 0 if sgl can be simplified to a single LBA. So an empty sgl + * will return 0; a one element sgl will return its LBA. A multiple element + * sgl only returns the first element's LBA (that is not degenerate) if the + * sgl is monotonic and not fragmented. In the extreme case takes last + * element's LBA if all prior elements are degenerate. Else returns -1 . + * Assumes sgl_sum_scan() has been called. */ +int64_t +scat_gath_list::get_low_lba_from_linear() const +{ + const int num_elems = sgl.size(); + int k; + + if (num_elems <= 1) + return (1 == num_elems) ? sgl[0].lba : 0; + else { + if (linearity == SGL_LINEAR) { + for (k = 0; k < (num_elems - 1); ++k) { + if (sgl[k].num > 0) + return sgl[k].lba; + } + /* take last element's LBA if all earlier are degenerate */ + return sgl[k].lba; + } else + return -1; + } +} + +bool +scat_gath_list::is_pipe_suitable() const +{ + return (lowest_lba == 0) && (linearity == SGL_LINEAR); +} + +scat_gath_iter::scat_gath_iter(const scat_gath_list & parent) + : sglist(parent), it_el_ind(0), it_blk_off(0), blk_idx(0) +{ + int elems = sglist.num_elems(); + + if (elems > 0) + extend_last = (0 == sglist.sgl[elems - 1].num); +} + +bool +scat_gath_iter::set_by_blk_idx(int64_t _blk_idx) +{ + bool first; + int k; + const int elems = sglist.sgl.size(); + const int last_ind = elems - 1; + uint32_t num; + int64_t bc = _blk_idx; + + if (bc < 0) + return false; + + if (bc == blk_idx) + return true; + else if (bc > blk_idx) { + k = it_el_ind; + bc -= blk_idx; + } else + k = 0; + for (first = true; k < elems; ++k, first = false) { + num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL : + sglist.sgl[k].num; + if (first) { + if ((int64_t)(num - it_blk_off) < bc) + bc -= (num - it_blk_off); + else { + it_blk_off = bc + it_blk_off; + break; + } + } else { + if ((int64_t)num < bc) + bc -= num; + else { + it_blk_off = (uint32_t)bc; + break; + } + } + } + it_el_ind = k; + blk_idx = _blk_idx; + + if (k < elems) + return true; + else if ((k == elems) && (0 == it_blk_off)) + return true; /* EOL */ + else + return false; +} + +/* Given a blk_count, the iterator (*iter_p) is moved toward the EOL. + * Returns true unless blk_count takes iterator two or more past the last + * element. So if blk_count takes the iterator to the EOL, this function + * returns true. Takes into account iterator's extend_last flag. */ +bool +scat_gath_iter::add_blks(uint64_t blk_count) +{ + bool first; + int k; + const int elems = sglist.sgl.size(); + const int last_ind = elems - 1; + uint32_t num; + uint64_t bc = blk_count; + + if (0 == bc) + return true; + for (first = true, k = it_el_ind; k < elems; ++k, first = false) { + num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL : + sglist.sgl[k].num; + if (first) { + if ((uint64_t)(num - it_blk_off) < bc) + bc -= (num - it_blk_off); + else { + it_blk_off = bc + it_blk_off; + break; + } + } else { + if ((uint64_t)num < bc) + bc -= num; + else { + it_blk_off = (uint32_t)bc; + break; + } + } + } + it_el_ind = k; + blk_idx += blk_count; + + if (k < elems) + return true; + else if ((k == elems) && (0 == it_blk_off)) + return true; /* EOL */ + else + return false; +} + +/* Move the iterator from its current position (which may be to EOL) towards + * the start of the sgl (i.e. backwards) for blk_count blocks. Returns true + * if iterator is valid after the move, else returns false. N.B. if false is + * returned, then the iterator is invalid and may need to set it to a valid + * value. */ +bool +scat_gath_iter::sub_blks(uint64_t blk_count) +{ + bool first; + int k = it_el_ind; + uint64_t bc = 0; + const uint64_t orig_blk_count = blk_count; + + if (0 == blk_count) + return true; + for (first = true; k >= 0; --k) { + if (first) { + if (blk_count > (uint64_t)it_blk_off) + blk_count -= it_blk_off; + else { + it_blk_off -= blk_count; + break; + } + first = false; + } else { + uint32_t off = sglist.sgl[k].num; + + bc = blk_count; + if (bc > (uint64_t)off) + blk_count -= off; + else { + bc = off - bc; + break; + } + } + } + if (k < 0) { + blk_idx = 0; + return false; /* bad situation */ + } + if ((int64_t)orig_blk_count <= blk_idx) + blk_idx -= orig_blk_count; + else + blk_idx = 0; + it_el_ind = k; + if (! first) + it_blk_off = (uint32_t)bc; + return true; +} + +/* Returns LBA referred to by iterator if valid or returns SG_LBA_INVALID + * (-1) if at end or invalid. */ +int64_t +scat_gath_iter::current_lba() const +{ + const int elems = sglist.sgl.size(); + int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */ + + if (it_el_ind < elems) { + struct scat_gath_elem sge = sglist.sgl[it_el_ind]; + + if ((uint32_t)it_blk_off < sge.num) + return sge.lba + it_blk_off; + else if (((uint32_t)it_blk_off == sge.num) && + ((it_el_ind + 1) < elems)) { + class scat_gath_iter iter(*this); + + ++iter.it_el_ind; + iter.it_blk_off = 0; + /* worst case recursion will stop at end of sgl */ + return iter.current_lba(); + } + } + return res; +} + +int64_t +scat_gath_iter::current_lba_rem_num(int & rem_num) const +{ + const int elems = sglist.sgl.size(); + int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */ + + if (it_el_ind < elems) { + struct scat_gath_elem sge = sglist.sgl[it_el_ind]; + + if ((uint32_t)it_blk_off < sge.num) { + rem_num = sge.num - it_blk_off; + return sge.lba + it_blk_off; + } else if (((uint32_t)it_blk_off == sge.num) && + ((it_el_ind + 1) < elems)) { + class scat_gath_iter iter(*this); + + ++iter.it_el_ind; + iter.it_blk_off = 0; + /* worst case recursion will stop at end of sgl */ + return iter.current_lba_rem_num(rem_num); + } + } + rem_num = -1; + return res; +} + +struct scat_gath_elem +scat_gath_iter::current_elem() const +{ + const int elems = sglist.sgl.size(); + struct scat_gath_elem sge; + + sge.make_bad(); + if (it_el_ind < elems) + return sglist.sgl[it_el_ind]; + return sge; +} + +/* Returns true of no sgl or sgl is at the end [elems, 0], otherwise it + * returns false. */ +bool +scat_gath_iter::at_end() const +{ + const int elems = sglist.sgl.size(); + + return ((0 == elems) || ((it_el_ind == elems) && (0 == it_blk_off))); +} + +/* Returns true if associated iterator is monotonic (increasing) and not + * fragmented. Empty sgl and single element degenerate considered linear. + * Assumes sgl_sum_scan() has been called on sgl. */ +bool +scat_gath_iter::is_sgl_linear() const +{ + return sglist.linearity == SGL_LINEAR; +} + +int +scat_gath_iter::linear_for_n_blks(int max_n) const +{ + int k, rem; + const int elems = sglist.sgl.size(); + uint64_t prev_lba; + struct scat_gath_elem sge; + + if (at_end() || (max_n <= 0)) + return 0; + sge = sglist.sgl[it_el_ind]; + rem = (int)sge.num - it_blk_off; + if (max_n <= rem) + return max_n; + prev_lba = sge.lba + sge.num; + for (k = it_el_ind + 1; k < elems; ++k) { + sge = sglist.sgl[k]; + if (sge.lba != prev_lba) + return rem; + rem += sge.num; + if (max_n <= rem) + return max_n; + prev_lba = sge.lba + sge.num; + } + return rem; +} + +/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose + * output. */ +void +scat_gath_iter::dbg_print(const char * id_str, bool to_stdout, + int verbose) const +{ + const char * caller = id_str ? id_str : "unknown"; + FILE * fp = to_stdout ? stdout : stderr; + lock_guard<mutex> lk(strerr_mut); + + fprintf(fp, "%s: it_el_ind=%d, it_blk_off=%d, blk_idx=%" PRId64 "\n", + caller, it_el_ind, it_blk_off, blk_idx); + fprintf(fp, " extend_last=%d\n", extend_last); + if (verbose) + sglist.dbg_print(false, " iterator's", to_stdout, verbose > 1, false); +} + +/* Calculates difference between iterators, logically: res <-- lhs - rhs + * Checks that lhsp and rhsp have same underlying sgl, if not returns + * INT_MIN. Assumes iterators close enough for result to lie in range + * from (-INT_MAX) to INT_MAX (inclusive). */ +int +diff_between_iters(const struct scat_gath_iter & left, + const struct scat_gath_iter & right) +{ + int res, k, r_e_ind, l_e_ind; + + if (&left.sglist != &right.sglist) { + pr2serr("%s: bad args\n", __func__); + return INT_MIN; + } + r_e_ind = right.it_el_ind; + l_e_ind = left.it_el_ind; + if (l_e_ind < r_e_ind) { /* so difference will be negative */ + res = diff_between_iters(right, left); /* cheat */ + if (INT_MIN == res) + return res; + return -res; + } else if (l_e_ind == r_e_ind) + return (int)left.it_blk_off - (int)right.it_blk_off; + /* (l_e_ind > r_e_ind) so (lhs > rhs) */ + res = (int)right.sglist.sgl[r_e_ind].num - right.it_blk_off; + for (k = 1; (r_e_ind + k) < l_e_ind; ++k) { + // pr2serr("%s: k=%d, res=%d, num=%d\n", __func__, k, res, + // (int)right.sglist.sgl[r_e_ind + k].num); + res += (int)right.sglist.sgl[r_e_ind + k].num; + } + res += left.it_blk_off; + // pr2serr("%s: at exit res=%d\n", __func__, res); + return res; +} + +/* Compares from the current iterator positions of left and left until + * the shorter list is exhausted. Returns false on the first inequality. + * If no inequality and both remaining lists are same length then returns + * true. If no inequality but remaining lists differ in length then returns + * allow_partial. */ +bool +sgls_eq_from_iters(const struct scat_gath_iter & left, + const struct scat_gath_iter & right, + bool allow_partial) +{ + return sgls_eq_off(left.sglist, left.it_el_ind, left.it_blk_off, + right.sglist, right.it_el_ind, right.it_blk_off, + allow_partial); +} + +get_next_res +global_collection::get_next(int desired_num_blks) +{ + int64_t expected, desired; + + if (desired_num_blks <= 0) { + if (desired_num_blks < 0) { + if (next_count_pos.load() >= 0) /* flag error detection */ + next_count_pos.store(desired_num_blks); + } + return make_pair(next_count_pos.load(), 0); + } + + expected = next_count_pos.load(); + do { /* allowed to race with other threads */ + if (expected < 0) + return make_pair(0, (int)expected); + else if (expected >= dd_count) + return make_pair(expected, 0); /* clean finish */ + desired = expected + desired_num_blks; + if (desired > dd_count) + desired = dd_count; + } while (! next_count_pos.compare_exchange_strong(expected, desired)); + return make_pair(expected, desired - expected); +} + +#if 0 +/* Returns the number of times either 'ch1' or 'ch2' is found in + * string 's' given the string's length. */ +static int +num_either_ch_in_str(const char * s, int slen, int ch1, int ch2) +{ + int k; + int res = 0; + + while (--slen >= 0) { + k = s[slen]; + if ((ch1 == k) || (ch2 == k)) + ++res; + } + return res; +} +#endif + +/* Return of 0 -> success, see sg_ll_read_capacity*() otherwise */ +static int +scsi_read_capacity(int sg_fd, int64_t * num_sect, int * sect_sz) +{ + int res; + uint8_t rcBuff[RCAP16_REPLY_LEN]; + + res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, false, 0); + if (0 != res) + return res; + + if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) && + (0xff == rcBuff[3])) { + + res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, false, + 0); + if (0 != res) + return res; + *num_sect = sg_get_unaligned_be64(rcBuff + 0) + 1; + *sect_sz = sg_get_unaligned_be32(rcBuff + 8); + } else { + /* take care not to sign extend values > 0x7fffffff */ + *num_sect = (int64_t)sg_get_unaligned_be32(rcBuff + 0) + 1; + *sect_sz = sg_get_unaligned_be32(rcBuff + 4); + } + return 0; +} + +/* Return of 0 -> success, -1 -> failure. BLKGETSIZE64, BLKGETSIZE and */ +/* BLKSSZGET macros problematic (from <linux/fs.h> or <sys/mount.h>). */ +static int +read_blkdev_capacity(int sg_fd, int64_t * num_sect, int * sect_sz) +{ +#ifdef BLKSSZGET + if ((ioctl(sg_fd, BLKSSZGET, sect_sz) < 0) && (*sect_sz > 0)) { + perror("BLKSSZGET ioctl error"); + return -1; + } else { + #ifdef BLKGETSIZE64 + uint64_t ull; + + if (ioctl(sg_fd, BLKGETSIZE64, &ull) < 0) { + + perror("BLKGETSIZE64 ioctl error"); + return -1; + } + *num_sect = ((int64_t)ull / (int64_t)*sect_sz); + #else + unsigned long ul; + + if (ioctl(sg_fd, BLKGETSIZE, &ul) < 0) { + perror("BLKGETSIZE ioctl error"); + return -1; + } + *num_sect = (int64_t)ul; + #endif + } + return 0; +#else + *num_sect = 0; + *sect_sz = 0; + return -1; +#endif +} + +static void +sig_listen_thread(struct global_collection * clp) +{ + int sig_number; + + while (1) { + sigwait(&signal_set, &sig_number); + if (shutting_down) + break; + if (SIGINT == sig_number) { + pr2serr_lk("%sinterrupted by SIGINT\n", my_name); + clp->next_count_pos.store(-1); + } + } +} + +static bool +sg_share_prepare(int slave_wr_fd, int master_rd_fd, int id, bool vb_b) +{ + struct sg_extended_info sei; + struct sg_extended_info * seip; + + seip = &sei; + memset(seip, 0, sizeof(*seip)); + seip->sei_wr_mask |= SG_SEIM_SHARE_FD; + seip->sei_rd_mask |= SG_SEIM_SHARE_FD; + seip->share_fd = master_rd_fd; + if (ioctl(slave_wr_fd, SG_SET_GET_EXTENDED, seip) < 0) { + pr2serr_lk("tid=%d: ioctl(EXTENDED(shared_fd=%d), failed " + "errno=%d %s\n", id, master_rd_fd, errno, + strerror(errno)); + return false; + } + if (vb_b) + pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(shared_fd)) ok, master_fd=%d, " + "slave_fd=%d\n", __func__, id, master_rd_fd, slave_wr_fd); + return true; +} + +static void +sg_take_snap(int sg_fd, int id, bool vb_b) +{ + struct sg_extended_info sei; + struct sg_extended_info * seip; + + seip = &sei; + memset(seip, 0, sizeof(*seip)); + seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS; + seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS; + seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_SNAP_DEV; + seip->ctl_flags &= SG_CTL_FLAGM_SNAP_DEV; /* 0 --> don't append */ + if (ioctl(sg_fd, SG_SET_GET_EXTENDED, seip) < 0) { + pr2serr_lk("tid=%d: ioctl(EXTENDED(SNAP_DEV), failed errno=%d %s\n", + id, errno, strerror(errno)); + return; + } + if (vb_b) + pr2serr_lk("tid=%d: ioctl(SNAP_DEV) ok\n", id); +} + +static inline uint8_t * +get_buffp(Rq_elem * rep) +{ + return rep->buffp; +} + +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< +/* Each thread's "main" function */ +static void +read_write_thread(struct global_collection * clp, int id, bool singleton) +{ + Rq_elem rel; + Rq_elem * rep = &rel; + int n, sz, fd, vb, err, seg_blks; + int res = 0; + int num_sg = 0; + // volatile bool stop_after_write = false; + bool own_infd = false; + bool in_is_sg, in_mmap, out_is_sg, out_mmap; + bool own_outfd = false; + bool own_out2fd = false; + bool only_one_sg = false; + // bool share_and_ofreg; + class scat_gath_iter i_sg_it(clp->i_sgl); + class scat_gath_iter o_sg_it(clp->o_sgl); + vector<cdb_arr_t> a_cdb; + vector<struct sg_io_v4> a_v4; + // mrq_arr_t deferred_arr; /* MRQ deferred array (vector) */ + + vb = clp->verbose; + sz = clp->mrq_num * clp->bpt * clp->bs; + in_is_sg = (FT_SG == clp->in_type); + in_mmap = (in_is_sg && (clp->in_flags.mmap > 0)); + out_is_sg = (FT_SG == clp->out_type); + out_mmap = (out_is_sg && (clp->out_flags.mmap > 0)); + memset(rep, 0, sizeof(Rq_elem)); + rep->clp = clp; + rep->id = id; + + if (in_is_sg && out_is_sg) + rep->both_sg = true; + else if (in_is_sg || out_is_sg) { + only_one_sg = true; + if (in_is_sg) + rep->only_in_sg = true; + else + rep->only_out_sg = true; + } + + if (vb > 2) + pr2serr_lk("%d <-- Starting worker thread\n", id); + if (! rep->both_sg) { + rep->buffp = sg_memalign(sz, 0 /* page align */, &rep->alloc_bp, + false); + if (NULL == rep->buffp) + err_exit(ENOMEM, "out of memory creating user buffers\n"); + } + rep->infd = clp->infd; + rep->outfd = clp->outfd; + rep->out2fd = clp->out2fd; + rep->outregfd = clp->outregfd; + rep->rep_count = 0; + rep->in_follow_on = -1; + rep->out_follow_on = -1; + + if (rep->infd == rep->outfd) { + if (in_is_sg) + rep->same_sg = true; + } + if (clp->in_flags.random) { + ssize_t ssz; + + ssz = getrandom(&rep->seed, sizeof(rep->seed), 0); + if (ssz < (ssize_t)sizeof(rep->seed)) + pr2serr_lk("[%d] %s: getrandom() failed, ret=%d\n", id, __func__, + (int)ssz); + if (vb > 1) + pr2serr_lk("[%d] %s: seed=%ld\n", id, __func__, rep->seed); + srand48_r(rep->seed, &rep->drand); + } + + if (in_is_sg && clp->infp) { + fd = sg_in_open(clp, clp->infp, (in_mmap ? &rep->buffp : NULL), + (in_mmap ? &rep->mmap_len : NULL)); + if (fd < 0) + goto fini; + rep->infd = fd; + rep->mmap_active = in_mmap ? clp->in_flags.mmap : 0; + if (in_mmap && (vb > 4)) + pr2serr_lk("[%d] %s: mmap buffp=%p\n", id, __func__, rep->buffp); + own_infd = true; + ++num_sg; + if (vb > 2) + pr2serr_lk("[%d]: opened local sg IFILE\n", id); + } + if (out_is_sg && clp->outfp) { + fd = sg_out_open(clp, clp->outfp, (out_mmap ? &rep->buffp : NULL), + (out_mmap ? &rep->mmap_len : NULL)); + if (fd < 0) + goto fini; + rep->outfd = fd; + if (! rep->mmap_active) + rep->mmap_active = out_mmap ? clp->out_flags.mmap : 0; + if (out_mmap && (vb > 4)) + pr2serr_lk("[%d]: mmap buffp=%p\n", id, rep->buffp); + own_outfd = true; + ++num_sg; + if (vb > 2) + pr2serr_lk("[%d]: opened local sg OFILE\n", id); + } + if ((FT_SG == clp->out2_type) && clp->out2fp) { + fd = sg_out_open(clp, clp->out2fp, + (out_mmap ? &rep->buffp : NULL), + (out_mmap ? &rep->mmap_len : NULL)); + if (fd < 0) + goto fini; + rep->out2fd = fd; + own_out2fd = true; + if (vb > 2) + pr2serr_lk("[%d]: opened local sg OFILE2\n", id); + } + if (vb > 2) { + if (in_is_sg && (! own_infd)) + pr2serr_lk("[%d]: using global sg IFILE, fd=%d\n", id, rep->infd); + if (out_is_sg && (! own_outfd)) + pr2serr_lk("[%d]: using global sg OFILE, fd=%d\n", id, rep->outfd); + if ((FT_SG == clp->out2_type) && (! own_out2fd)) + pr2serr_lk("[%d]: using global sg OFILE2, fd=%d\n", id, + rep->out2fd); + } + if (rep->both_sg) + rep->has_share = sg_share_prepare(rep->outfd, rep->infd, id, vb > 9); + if (vb > 9) + pr2serr_lk("[%d]: has_share=%s\n", id, + (rep->has_share ? "true" : "false")); + // share_and_ofreg = (rep->has_share && (rep->outregfd >= 0)); + + /* vvvvvvvvvvvvvv Main segment copy loop vvvvvvvvvvvvvvvvvvvvvvv */ + while (1) { + get_next_res gnr = clp->get_next(clp->mrq_num * clp->bpt); + + seg_blks = gnr.second; + if (seg_blks <= 0) { + if (seg_blks < 0) + res = -seg_blks; + break; + } + if (! i_sg_it.set_by_blk_idx(gnr.first)) { + pr2serr_lk("[%d]: input set_by_blk_idx() failed\n", id); + i_sg_it.dbg_print("input after set_by_blk_idx", false, vb > 5); + res = 2; + break; + } + if (! o_sg_it.set_by_blk_idx(gnr.first)) { + pr2serr_lk("[%d]: output set_by_blk_idx() failed\n", id); + res = 3; + break; + } + if (rep->both_sg) { + uint32_t nn = (2 * clp->mrq_num) + 4; + + if (a_cdb.capacity() < nn) + a_cdb.reserve(nn); + if (a_v4.capacity() < nn) + a_v4.reserve(nn); + res = do_both_sg_segment(rep, i_sg_it, o_sg_it, seg_blks, a_cdb, + a_v4); + if (res < 0) + break; + } else if (only_one_sg) { + uint32_t nn = clp->mrq_num + 4; + + if (a_cdb.capacity() < nn) + a_cdb.reserve(nn); + if (a_v4.capacity() < nn) + a_v4.reserve(nn); + res = do_normal_sg_segment(rep, i_sg_it, o_sg_it, seg_blks, a_cdb, + a_v4); + if (res < 0) + break; + } else { + res = do_normal_normal_segment(rep, i_sg_it, o_sg_it, seg_blks); + if (res < 0) + break; + } + if (singleton) { + { + lock_guard<mutex> lk(clp->infant_mut); + + clp->processed = true; + } /* this unlocks lk */ + clp->infant_cv.notify_one(); + singleton = false; + } + if (rep->stop_after_write) + break; + } /* ^^^^^^^^^^ end of main while loop which copies segments ^^^^^^ */ + if (singleton) { + { + lock_guard<mutex> lk(clp->infant_mut); + + clp->processed = true; + } /* this unlocks lk */ + clp->infant_cv.notify_one(); + singleton = false; + } + if (res < 0) { + if (seg_blks >= 0) + clp->get_next(-1); /* flag error to main */ + pr2serr_lk("%s: t=%d: aborting, res=%d\n", __func__, rep->id, res); + } + +fini: + +#if 0 + if ((rep->mmap_active == 0) && rep->alloc_bp) + free(rep->alloc_bp); + if ((1 == rep->mmap_active) && (rep->mmap_len > 0)) { + if (munmap(rep->buffp, rep->mmap_len) < 0) { + int err = errno; + char bb[64]; + + pr2serr_lk("thread=%d: munmap() failed: %s\n", rep->id, + tsafe_strerror(err, bb)); + } + if (vb > 4) + pr2serr_lk("thread=%d: munmap(%p, %d)\n", rep->id, rep->buffp, + rep->mmap_len); + rep->mmap_active = 0; + } +#endif + + if (own_infd && (rep->infd >= 0)) { + if (vb && in_is_sg) { + ++num_waiting_calls; + if (ioctl(rep->infd, SG_GET_NUM_WAITING, &n) >= 0) { + if (n > 0) + pr2serr_lk("%s: tid=%d: num_waiting=%d prior close(in)\n", + __func__, rep->id, n); + } else { + err = errno; + pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: " + "%s\n", __func__, rep->id, err, strerror(err)); + } + } + close(rep->infd); + } + if (own_outfd && (rep->outfd >= 0)) { + if (vb && out_is_sg) { + ++num_waiting_calls; + if (ioctl(rep->outfd, SG_GET_NUM_WAITING, &n) >= 0) { + if (n > 0) + pr2serr_lk("%s: tid=%d: num_waiting=%d prior " + "close(out)\n", __func__, rep->id, n); + } else { + err = errno; + pr2serr_lk("%s: [%d] ioctl(SG_GET_NUM_WAITING) errno=%d: " + "%s\n", __func__, rep->id, err, strerror(err)); + } + } + close(rep->outfd); + } + if (own_out2fd && (rep->out2fd >= 0)) + close(rep->out2fd); + /* pass stats back to master */ + clp->in_rem_count -= rep->in_local_count; + clp->out_rem_count -= rep->out_local_count; + clp->in_partial += rep->in_local_partial; + clp->out_partial += rep->out_local_partial; +} + +/* N.B. Returns 'blocks' is successful, lesser positive number if there was + * a short read, or an error code which is negative. */ +static int +normal_in_rd(Rq_elem * rep, int64_t lba, int blocks, int d_boff) +{ + struct global_collection * clp = rep->clp; + int res, err; + int id = rep->id; + uint8_t * bp; + char strerr_buff[STRERR_BUFF_LEN]; + + if (clp->verbose > 4) + pr2serr_lk("[%d] %s: lba=%" PRIu64 ", blocks=%d, d_boff=%d\n", id, + __func__, lba, blocks, d_boff); + if (FT_RANDOM_0_FF == clp->in_type) { + int k, j; + const int jbump = sizeof(uint32_t); + long rn; + uint8_t * bp; + + if (clp->in_flags.zero) + memset(rep->buffp + d_boff, 0, blocks * clp->bs); + else if (clp->in_flags.ff) + memset(rep->buffp + d_boff, 0xff, blocks * clp->bs); + else { + bp = rep->buffp + d_boff; + for (k = 0; k < blocks; ++k, bp += clp->bs) { + for (j = 0; j < clp->bs; j += jbump) { + /* mrand48 takes uniformly from [-2^31, 2^31) */ + mrand48_r(&rep->drand, &rn); + *((uint32_t *)(bp + j)) = (uint32_t)rn; + } + } + } + return blocks; + } + + if (clp->in_type != FT_FIFO) { + int64_t pos = lba * clp->bs; + + if (rep->in_follow_on != pos) { + if (lseek64(rep->infd, pos, SEEK_SET) < 0) { + err = errno; + pr2serr_lk("[%d] %s: >> lseek64(%" PRId64 "): %s\n", id, + __func__, pos, safe_strerror(err)); + return -err; + } + rep->in_follow_on = pos; + } + } + bp = rep->buffp + d_boff; + while (((res = read(clp->infd, bp, blocks * clp->bs)) < 0) && + ((EINTR == errno) || (EAGAIN == errno))) + std::this_thread::yield();/* another thread may be able to progress */ + if (res < 0) { + err = errno; + if (clp->in_flags.coe) { + memset(bp, 0, blocks * clp->bs); + pr2serr_lk("[%d] %s : >> substituted zeros for in blk=%" PRId64 + " for %d bytes, %s\n", id, __func__, lba, + blocks * clp->bs, + tsafe_strerror(err, strerr_buff)); + res = blocks * clp->bs; + } else { + pr2serr_lk("[%d] %s: error in normal read, %s\n", id, __func__, + tsafe_strerror(err, strerr_buff)); + return -err; + } + } + rep->in_follow_on += res; + if (res < blocks * clp->bs) { + blocks = res / clp->bs; + if ((res % clp->bs) > 0) { + rep->in_local_partial++; + rep->in_resid_bytes = res % clp->bs; + } + } + return blocks; +} + +/* N.B. Returns 'blocks' is successful, lesser positive number if there was + * a short write, or an error code which is negative. */ +static int +normal_out_wr(Rq_elem * rep, int64_t lba, int blocks, int d_boff) +{ + int res, err; + int id = rep->id; + struct global_collection * clp = rep->clp; + uint8_t * bp = rep->buffp + d_boff; + char strerr_buff[STRERR_BUFF_LEN]; + + if (clp->verbose > 4) + pr2serr_lk("[%d] %s: lba=%" PRIu64 ", blocks=%d, d_boff=%d\n", id, + __func__, lba, blocks, d_boff); + + if (clp->in_type != FT_FIFO) { + int64_t pos = lba * clp->bs; + + if (rep->out_follow_on != pos) { + if (lseek64(rep->outfd, pos, SEEK_SET) < 0) { + err = errno; + pr2serr_lk("[%d] %s: >> lseek64(%" PRId64 "): %s\n", id, + __func__, pos, safe_strerror(err)); + return -err; + } + rep->out_follow_on = pos; + } + } + while (((res = write(clp->outfd, bp, blocks * clp->bs)) + < 0) && ((EINTR == errno) || (EAGAIN == errno))) + std::this_thread::yield();/* another thread may be able to progress */ + if (res < 0) { + err = errno; + if (clp->out_flags.coe) { + pr2serr_lk("[%d] %s: >> ignored error for out lba=%" PRId64 + " for %d bytes, %s\n", id, __func__, lba, + blocks * clp->bs, tsafe_strerror(err, strerr_buff)); + res = blocks * clp->bs; + } + else { + pr2serr_lk("[%d] %s: error normal write, %s\n", id, __func__, + tsafe_strerror(err, strerr_buff)); + return -err; + } + } + rep->out_follow_on += res; + if (res < blocks * clp->bs) { + blocks = res / clp->bs; + if ((res % clp->bs) > 0) { + blocks++; + rep->out_local_partial++; + } + } + return blocks; +} + +static int +extra_out_wr(Rq_elem * rep, int num_bytes, int d_boff) +{ + int res, err; + int id = rep->id; + struct global_collection * clp = rep->clp; + uint8_t * bp = rep->buffp + d_boff; + char strerr_buff[STRERR_BUFF_LEN]; + + if (clp->verbose > 4) + pr2serr_lk("[%d] %s: num_bytes=%d, d_boff=%d\n", id, __func__, + num_bytes, d_boff); + + while (((res = write(clp->outfd, bp, num_bytes)) + < 0) && ((EINTR == errno) || (EAGAIN == errno))) + std::this_thread::yield();/* another thread may be able to progress */ + if (res < 0) { + err = errno; + pr2serr_lk("[%d] %s: error normal write, %s\n", id, __func__, + tsafe_strerror(err, strerr_buff)); + return -err; + } + if (res > 0) + rep->out_local_partial++; + return res; +} + +static int +sg_build_scsi_cdb(uint8_t * cdbp, int cdb_sz, unsigned int blocks, + int64_t start_block, bool ver_true, bool write_true, + bool fua, bool dpo) +{ + int rd_opcode[] = {0x8, 0x28, 0xa8, 0x88}; + int ve_opcode[] = {0xff /* no VER(6) */, 0x2f, 0xaf, 0x8f}; + int wr_opcode[] = {0xa, 0x2a, 0xaa, 0x8a}; + int sz_ind; + + memset(cdbp, 0, cdb_sz); + if (ver_true) { /* only support VERIFY(10) */ + if (cdb_sz < 10) { + pr2serr_lk("%s only support VERIFY(10)\n", my_name); + return 1; + } + cdb_sz = 10; + fua = false; + cdbp[1] |= 0x2; /* BYTCHK=1 --> sending dout for comparison */ + cdbp[0] = ve_opcode[1]; + } + if (dpo) + cdbp[1] |= 0x10; + if (fua) + cdbp[1] |= 0x8; + switch (cdb_sz) { + case 6: + sz_ind = 0; + cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] : + rd_opcode[sz_ind]); + sg_put_unaligned_be24(0x1fffff & start_block, cdbp + 1); + cdbp[4] = (256 == blocks) ? 0 : (uint8_t)blocks; + if (blocks > 256) { + pr2serr_lk("%sfor 6 byte commands, maximum number of blocks is " + "256\n", my_name); + return 1; + } + if ((start_block + blocks - 1) & (~0x1fffff)) { + pr2serr_lk("%sfor 6 byte commands, can't address blocks beyond " + "%d\n", my_name, 0x1fffff); + return 1; + } + if (dpo || fua) { + pr2serr_lk("%sfor 6 byte commands, neither dpo nor fua bits " + "supported\n", my_name); + return 1; + } + break; + case 10: + if (! ver_true) { + sz_ind = 1; + cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] : + rd_opcode[sz_ind]); + } + sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2); + sg_put_unaligned_be16((uint16_t)blocks, cdbp + 7); + if (blocks & (~0xffff)) { + pr2serr_lk("%sfor 10 byte commands, maximum number of blocks is " + "%d\n", my_name, 0xffff); + return 1; + } + break; + case 12: + sz_ind = 2; + cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] : + rd_opcode[sz_ind]); + sg_put_unaligned_be32((uint32_t)start_block, cdbp + 2); + sg_put_unaligned_be32((uint32_t)blocks, cdbp + 6); + break; + case 16: + sz_ind = 3; + cdbp[0] = (uint8_t)(write_true ? wr_opcode[sz_ind] : + rd_opcode[sz_ind]); + sg_put_unaligned_be64((uint64_t)start_block, cdbp + 2); + sg_put_unaligned_be32((uint32_t)blocks, cdbp + 10); + break; + default: + pr2serr_lk("%sexpected cdb size of 6, 10, 12, or 16 but got %d\n", + my_name, cdb_sz); + return 1; + } + return 0; +} + +#if 0 + +static bool +sg_wr_swap_share(Rq_elem * rep, int to_fd, bool before) +{ + bool not_first = false; + int err = 0; + int k; + int master_fd = rep->infd; /* in (READ) side is master */ + struct global_collection * clp = rep->clp; + struct sg_extended_info sei; + struct sg_extended_info * seip = &sei; + + if (rep->clp->verbose > 2) + pr2serr_lk("%s: tid=%d: to_fd=%d, before=%d\n", __func__, rep->id, + to_fd, (int)before); + memset(seip, 0, sizeof(*seip)); + seip->sei_wr_mask |= SG_SEIM_CHG_SHARE_FD; + seip->sei_rd_mask |= SG_SEIM_CHG_SHARE_FD; + seip->share_fd = to_fd; + if (before) { + /* clear MASTER_FINI bit to put master in SG_RQ_SHR_SWAP state */ + seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS; + seip->sei_rd_mask |= SG_SEIM_CTL_FLAGS; + seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MASTER_FINI; + seip->ctl_flags &= SG_CTL_FLAGM_MASTER_FINI;/* would be 0 anyway */ + } + for (k = 0; (ioctl(master_fd, SG_SET_GET_EXTENDED, seip) < 0) && + (EBUSY == errno); ++k) { + err = errno; + if (k > 10000) + break; + if (! not_first) { + if (clp->verbose > 3) + pr2serr_lk("tid=%d: ioctl(EXTENDED(change_shared_fd=%d), " + "failed errno=%d %s\n", rep->id, master_fd, err, + strerror(err)); + not_first = true; + } + err = 0; + std::this_thread::yield();/* another thread may be able to progress */ + } + if (err) { + pr2serr_lk("tid=%d: ioctl(EXTENDED(change_shared_fd=%d), failed " + "errno=%d %s\n", rep->id, master_fd, err, strerror(err)); + return false; + } + if (clp->verbose > 15) + pr2serr_lk("%s: tid=%d: ioctl(EXTENDED(change_shared_fd)) ok, " + "master_fd=%d, to_slave_fd=%d\n", __func__, rep->id, + master_fd, to_fd); + return true; +} + +#endif + +static int +process_mrq_response(Rq_elem * rep, const struct sg_io_v4 * ctl_v4p, + const struct sg_io_v4 * a_v4p, int num_mrq, + uint32_t & good_inblks, uint32_t & good_outblks) +{ + struct global_collection * clp = rep->clp; + bool ok; + int id = rep->id; + int resid = ctl_v4p->din_resid; + int sres = ctl_v4p->spare_out; + int n_subm = num_mrq - ctl_v4p->dout_resid; + int n_cmpl = ctl_v4p->info; + int n_good = 0; + int vb = clp->verbose; + int k, slen, sstatus; + const struct sg_io_v4 * a_np = a_v4p; + + good_inblks = 0; + good_outblks = 0; + if (n_subm < 0) { + pr2serr_lk("[%d] %s: co.dout_resid(%d) > num_mrq(%d)\n", id, __func__, + ctl_v4p->dout_resid, num_mrq); + return -1; + } + if (n_cmpl != (num_mrq - resid)) + pr2serr_lk("[%d] %s: co.info(%d) != (num_mrq(%d) - co.din_resid(%d))\n" + "will use co.info\n", id, __func__, n_cmpl, num_mrq, resid); + if (n_cmpl > n_subm) { + pr2serr_lk("[%d] %s: n_cmpl(%d) > n_subm(%d), use n_subm for both\n", + id, __func__, n_cmpl, n_subm); + n_cmpl = n_subm; + } + if (sres) { + pr2serr_lk("[%d] %s: secondary error: %s [%d], info=0x%x\n", id, + __func__, strerror(sres), sres, ctl_v4p->info); + if (E2BIG == sres) { + sg_take_snap(rep->infd, id, true); + sg_take_snap(rep->outfd, id, true); + } + } + /* Check if those submitted have finished or not */ + for (k = 0; k < n_subm; ++k, ++a_np) { + slen = a_np->response_len; + if (! (SG_INFO_MRQ_FINI & a_np->info)) { + pr2serr_lk("[%d] %s, a_n[%d]: missing SG_INFO_MRQ_FINI ? ?\n", + id, __func__, k); + v4hdr_out_lk("a_np", a_np, id); + v4hdr_out_lk("cop", ctl_v4p, id); + } + ok = true; + sstatus = a_np->device_status; + if ((sstatus && (SAM_STAT_CONDITION_MET != sstatus)) || + a_np->transport_status || a_np->driver_status) { + ok = false; + if (SAM_STAT_CHECK_CONDITION != a_np->device_status) { + pr2serr_lk("[%d] %s, a_n[%d]:\n", id, __func__, k); + if (vb) + lk_chk_n_print4(" >>", a_np, false); + } + } + if (slen > 0) { + struct sg_scsi_sense_hdr ssh; + const uint8_t *sbp = (const uint8_t *)a_np->response; + + if (sg_scsi_normalize_sense(sbp, slen, &ssh) && + (ssh.response_code >= 0x70)) { + char b[256]; + + if (ssh.response_code & 0x1) + ok = true; + if (vb) { + sg_get_sense_str(" ", sbp, slen, false, sizeof(b), b); + pr2serr_lk("[%d] %s, a_n[%d]:\n%s\n", id, __func__, k, b); + } + } + } + if (ok) { + ++n_good; + if (a_np->dout_xfer_len >= (uint32_t)clp->bs) { + if (a_np->dout_resid) + good_outblks += (a_np->dout_xfer_len - a_np->dout_resid) / + clp->bs; + else /* avoid division in common case of resid==0 */ + good_outblks += (uint32_t)a_np->usr_ptr; + } + if (a_np->din_xfer_len >= (uint32_t)clp->bs) { + if (a_np->din_resid) + good_inblks += (a_np->din_xfer_len - a_np->din_resid) / + clp->bs; + else + good_inblks += (uint32_t)a_np->usr_ptr; + } + } + } + if ((n_subm == num_mrq) || (vb < 3)) + goto fini; + pr2serr_lk("[%d] %s: checking response array beyond number of " + "submissions:\n", id, __func__); + for (k = n_subm; k < num_mrq; ++k, ++a_np) { + if (SG_INFO_MRQ_FINI & a_np->info) + pr2serr_lk("[%d] %s, a_n[%d]: unexpected SG_INFO_MRQ_FINI set\n", + id, __func__, k); + if (a_np->device_status || a_np->transport_status || + a_np->driver_status) { + pr2serr_lk("[%d] %s, a_n[%d]:\n", id, __func__, k); + lk_chk_n_print4(" ", a_np, false); + } + } +fini: + return n_good; +} + + +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< zzzzzzzzz + +/* Returns number of blocks successfully processed or a negative error + * number. */ +static int +sg_half_segment(Rq_elem * rep, scat_gath_iter & sg_it, bool is_wr, + int seg_blks, uint8_t *dp, + vector<cdb_arr_t> & a_cdb, + vector<struct sg_io_v4> & a_v4) +{ + int num_mrq, k, res, fd, mrq_pack_id_base, id, b_len, rflags; + int num, kk, lin_blks, cdbsz, num_good; + int o_seg_blks = seg_blks; + uint32_t in_fin_blks, out_fin_blks; + uint32_t mrq_q_blks = 0; + uint32_t in_mrq_q_blks = 0; + uint32_t out_mrq_q_blks = 0; + const int max_cdb_sz = MAX_SCSI_CDB_SZ; + struct sg_io_v4 * a_v4p; + struct sg_io_v4 ctl_v4; /* MRQ control object */ + struct global_collection * clp = rep->clp; + const char * iosub_str = "SUBMIT(variable blocking)"; + char b[80]; + cdb_arr_t t_cdb = {}; + struct sg_io_v4 t_v4; + struct sg_io_v4 * t_v4p = &t_v4; + struct flags_t * flagsp = is_wr ? &clp->out_flags : &clp->in_flags; + bool serial = flagsp->serial; + + id = rep->id; + b_len = sizeof(b); + if (serial) + iosub_str = "(ordered blocking)"; + + a_cdb.clear(); + a_v4.clear(); + mrq_pack_id_base = id * PACK_ID_TID_MULTIPLIER; + + rflags = 0; + if (flagsp->mmap && (rep->outregfd >= 0)) + rflags |= SGV4_FLAG_MMAP_IO; + if (flagsp->dio) + rflags |= SGV4_FLAG_DIRECT_IO; + if (flagsp->qhead) + rflags |= SGV4_FLAG_Q_AT_HEAD; + if (flagsp->qtail) + rflags |= SGV4_FLAG_Q_AT_TAIL; + + for (k = 0; seg_blks > 0; ++k, seg_blks -= num) { + kk = min<int>(seg_blks, clp->bpt); + lin_blks = sg_it.linear_for_n_blks(kk); + num = lin_blks; + if (num <= 0) { + res = 0; + pr2serr_lk("[%d] %s: unexpected num=%d\n", id, __func__, num); + break; + } + + /* First build the command/request for the master (READ) side */ + cdbsz = is_wr ? clp->cdbsz_out : clp->cdbsz_in; + res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num, sg_it.current_lba(), + false, is_wr, flagsp->fua, flagsp->dpo); + if (res) { + pr2serr_lk("[%d] %s: sg_build_scsi_cdb() failed\n", id, __func__); + break; + } else if (clp->verbose > 3) + lk_print_command_len("cdb: ", t_cdb.data(), cdbsz, true); + a_cdb.push_back(t_cdb); + + memset(t_v4p, 0, sizeof(*t_v4p)); + t_v4p->guard = 'Q'; + t_v4p->flags = rflags; + t_v4p->request_len = cdbsz; + if (is_wr) { + t_v4p->dout_xfer_len = num * clp->bs; + t_v4p->dout_xferp = (uint64_t)(dp + (mrq_q_blks * clp->bs)); + } else { + t_v4p->din_xfer_len = num * clp->bs; + t_v4p->din_xferp = (uint64_t)(dp + (mrq_q_blks * clp->bs)); + } + t_v4p->timeout = DEF_TIMEOUT; + t_v4p->usr_ptr = num; /* pass number blocks requested */ + mrq_q_blks += num; + t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off; + a_v4.push_back(t_v4); + + sg_it.add_blks(num); + } + + if (rep->only_in_sg) + fd = rep->infd; + else if (rep->only_out_sg) + fd = rep->outfd; + else { + pr2serr_lk("[%d] %s: why am I here? No sg devices\n", id, __func__); + return -EINVAL; + } + num_mrq = a_v4.size(); + a_v4p = a_v4.data(); + res = 0; + memset(&ctl_v4, 0, sizeof(ctl_v4)); + ctl_v4.guard = 'Q'; + ctl_v4.request_len = a_cdb.size() * max_cdb_sz; + ctl_v4.request = (uint64_t)a_cdb.data(); + ctl_v4.max_response_len = sizeof(rep->sb); + ctl_v4.response = (uint64_t)rep->sb; + ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_STOP_IF; + ctl_v4.dout_xferp = (uint64_t)a_v4.data(); /* request array */ + ctl_v4.dout_xfer_len = a_v4.size() * sizeof(struct sg_io_v4); + ctl_v4.din_xferp = (uint64_t)a_v4.data(); /* response array */ + ctl_v4.din_xfer_len = a_v4.size() * sizeof(struct sg_io_v4); + if (false /* allow_mrq_abort */) + ctl_v4.request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off; + + if (clp->verbose > 4) { + pr2serr_lk("[%d] %s: Controlling object _before_ ioctl(SG_IO%s):\n", + id, __func__, iosub_str); + if (clp->verbose > 5) + hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1); + v4hdr_out_lk("Controlling object before", &ctl_v4, id); + } + +try_again: + if (!after1 && (clp->verbose > 1)) { + after1 = true; + pr2serr_lk("%s: %s\n", __func__, serial ? mrq_ob_s : mrq_vb_s); + } + if (serial) + res = ioctl(fd, SG_IO, &ctl_v4); + else + res = ioctl(fd, SG_IOSUBMIT, &ctl_v4); /* overlapping commands */ + if (res < 0) { + int err = errno; + + if (E2BIG == err) + sg_take_snap(fd, id, true); + else if (EBUSY == err) { + ++num_ebusy; + std::this_thread::yield();/* allow another thread to progress */ + goto try_again; + } + pr2serr_lk("[%d] %s: ioctl(SG_IO%s, %s)-->%d, errno=%d: %s\n", id, + __func__, iosub_str, sg_flags_str(ctl_v4.flags, b_len, b), + res, err, strerror(err)); + return -err; + } + if (clp->verbose > 4) { + pr2serr_lk("%s: Controlling object output by ioctl(%s):\n", __func__, + iosub_str); + if (clp->verbose > 5) + hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1); + v4hdr_out_lk("Controlling object after", &ctl_v4, id); + if (clp->verbose > 5) { + for (k = 0; k < num_mrq; ++k) { + pr2serr_lk("AFTER: def_arr[%d]:\n", k); + v4hdr_out_lk("normal v4 object", (a_v4p + k), id); + // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p), + // 1); + } + } + } + num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks, + out_fin_blks); + if (is_wr) + out_mrq_q_blks = mrq_q_blks; + else + in_mrq_q_blks = mrq_q_blks; + if (clp->verbose > 2) + pr2serr_lk("%s: >>> seg_blks=%d, num_good=%d, in_q/fin blks=%u/%u; " + "out_q/fin blks=%u/%u\n", __func__, o_seg_blks, num_good, + in_mrq_q_blks, in_fin_blks, out_mrq_q_blks, out_fin_blks); + + if (num_good < 0) + return -ENODATA; + else { + if (num_good < num_mrq) { + int resid_blks = in_mrq_q_blks - in_fin_blks; + + if (resid_blks > 0) + rep->in_rem_count += resid_blks; + + resid_blks = out_mrq_q_blks - out_fin_blks; + if (resid_blks > 0) + rep->out_rem_count += resid_blks; + rep->stop_after_write = true; + } + } + return is_wr ? out_fin_blks : in_fin_blks; +} + +/* Returns number of blocks successfully processed or a negative error + * number. */ +static int +do_normal_normal_segment(Rq_elem * rep, scat_gath_iter & i_sg_it, + scat_gath_iter & o_sg_it, int seg_blks) +{ + int k, kk, res, id, num, d_off; + int o_seg_blks = seg_blks; + uint32_t in_fin_blks = 0; + uint32_t out_fin_blks = 0; + struct global_collection * clp = rep->clp; + + id = rep->id; + d_off = 0; + for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) { + kk = min<int>(seg_blks, clp->bpt); + num = i_sg_it.linear_for_n_blks(kk); + res = normal_in_rd(rep, i_sg_it.current_lba(), num, + d_off * clp->bs); + if (res < 0) { + pr2serr_lk("[%d] %s: normal in failed d_off=%d, err=%d\n", + id, __func__, d_off, -res); + break; + } + i_sg_it.add_blks(res); + if (res < num) { + d_off += res; + rep->stop_after_write = true; + break; + } + } + seg_blks = d_off; + in_fin_blks = seg_blks; + + if (FT_DEV_NULL == clp->out_type) + goto fini; + d_off = 0; + for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) { + kk = min<int>(seg_blks, clp->bpt); + num = o_sg_it.linear_for_n_blks(kk); + res = normal_out_wr(rep, o_sg_it.current_lba(), num, + d_off * clp->bs); + if (res < num) { + if (res < 0) { + pr2serr_lk("[%d] %s: normal out failed d_off=%d, err=%d\n", + id, __func__, d_off, -res); + break; + } + } + o_sg_it.add_blks(res); + if (res < num) { + d_off += res; + rep->stop_after_write = true; + break; + } + } + if (rep->in_resid_bytes > 0) { + res = extra_out_wr(rep, rep->in_resid_bytes, d_off * clp->bs); + if (res < 0) + pr2serr_lk("[%d] %s: extr out failed d_off=%d, err=%d\n", id, + __func__, d_off, -res); + rep->in_resid_bytes = 0; + } + seg_blks = d_off; + out_fin_blks = seg_blks; + +fini: + rep->in_local_count += in_fin_blks; + rep->out_local_count += out_fin_blks; + + if ((in_fin_blks + out_fin_blks) < (uint32_t)o_seg_blks) { + int resid_blks = o_seg_blks - in_fin_blks; + + if (resid_blks > 0) + rep->in_rem_count += resid_blks; + resid_blks = o_seg_blks - out_fin_blks; + if (resid_blks > 0) + rep->out_rem_count += resid_blks; + } + return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks)); +} + +/* Returns number of blocks successfully processed or a negative error + * number. */ +static int +do_normal_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it, + scat_gath_iter & o_sg_it, int seg_blks, + vector<cdb_arr_t> & a_cdb, + vector<struct sg_io_v4> & a_v4) +{ + bool in_is_normal = ! rep->only_in_sg; + int k, kk, res, id, num, d_off; + int o_seg_blks = seg_blks; + uint32_t in_fin_blks = 0; + uint32_t out_fin_blks = 0; + struct global_collection * clp = rep->clp; + + id = rep->id; + a_cdb.clear(); + a_v4.clear(); + + if (in_is_normal) { /* in: normal --> out : sg */ + d_off = 0; + for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) { + kk = min<int>(seg_blks, clp->bpt); + num = i_sg_it.linear_for_n_blks(kk); + res = normal_in_rd(rep, i_sg_it.current_lba(), num, + d_off * clp->bs); + if (res < 0) { + pr2serr_lk("[%d] %s: normal in failed d_off=%d, err=%d\n", + id, __func__, d_off, -res); + break; + } + i_sg_it.add_blks(res); + if (res < num) { + d_off += res; + rep->stop_after_write = true; + break; + } + } + seg_blks = d_off; + in_fin_blks = seg_blks; + + if (rep->in_resid_bytes > 0) { + ++seg_blks; + rep->in_resid_bytes = 0; + } + res = sg_half_segment(rep, o_sg_it, true /* is_wr */, seg_blks, + rep->buffp, a_cdb, a_v4); + if (res < seg_blks) { + if (res < 0) { + pr2serr_lk("[%d] %s: sg out failed d_off=%d, err=%d\n", + id, __func__, d_off, -res); + goto fini; + } + rep->stop_after_write = true; + } + seg_blks = res; + out_fin_blks = seg_blks; + + } else { /* in: sg --> out: normal */ + res = sg_half_segment(rep, i_sg_it, false, seg_blks, rep->buffp, + a_cdb, a_v4); + if (res < seg_blks) { + if (res < 0) { + pr2serr_lk("[%d] %s: sg in failed, err=%d\n", id, __func__, + -res); + goto fini; + } + rep->stop_after_write = true; + } + seg_blks = res; + in_fin_blks = seg_blks; + + if (FT_DEV_NULL == clp->out_type) + goto bypass; + d_off = 0; + for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) { + kk = min<int>(seg_blks, clp->bpt); + num = o_sg_it.linear_for_n_blks(kk); + res = normal_out_wr(rep, o_sg_it.current_lba(), num, + d_off * clp->bs); + if (res < num) { + if (res < 0) { + pr2serr_lk("[%d] %s: normal out failed d_off=%d, err=%d\n", + id, __func__, d_off, -res); + break; + } + } + o_sg_it.add_blks(res); + if (res < num) { + d_off += res; + rep->stop_after_write = true; + break; + } + } + seg_blks = d_off; + out_fin_blks = seg_blks; + } +bypass: + rep->in_local_count += in_fin_blks; + rep->out_local_count += out_fin_blks; + + if ((in_fin_blks + out_fin_blks) < (uint32_t)o_seg_blks) { + int resid_blks = o_seg_blks - in_fin_blks; + + if (resid_blks > 0) + rep->in_rem_count += resid_blks; + resid_blks = o_seg_blks - out_fin_blks; + if (resid_blks > 0) + rep->out_rem_count += resid_blks; + } +fini: + return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks)); +} + +/* This function sets up a multiple request (mrq) transaction and sends it + * to the pass-through. Returns number of blocks processed (==seg_blks for + * all good) or a negative error number. */ +static int +do_both_sg_segment(Rq_elem * rep, scat_gath_iter & i_sg_it, + scat_gath_iter & o_sg_it, int seg_blks, + vector<cdb_arr_t> & a_cdb, + vector<struct sg_io_v4> & a_v4) +{ + int num_mrq, k, res, fd, mrq_pack_id_base, id, b_len, iflags, oflags; + int num, kk, i_lin_blks, o_lin_blks, cdbsz, num_good; + int o_seg_blks = seg_blks; + uint32_t in_fin_blks, out_fin_blks; + uint32_t in_mrq_q_blks = 0; + uint32_t out_mrq_q_blks = 0; + const int max_cdb_sz = MAX_SCSI_CDB_SZ; + struct sg_io_v4 * a_v4p; + struct sg_io_v4 ctl_v4; /* MRQ control object */ + struct global_collection * clp = rep->clp; + const char * iosub_str = "SUBMIT(svb)"; + char b[80]; + cdb_arr_t t_cdb = {}; + struct sg_io_v4 t_v4; + struct sg_io_v4 * t_v4p = &t_v4; + struct flags_t * iflagsp = &clp->in_flags; + struct flags_t * oflagsp = &clp->out_flags; + + id = rep->id; + b_len = sizeof(b); + + a_cdb.clear(); + a_v4.clear(); + mrq_pack_id_base = id * PACK_ID_TID_MULTIPLIER; + + iflags = SGV4_FLAG_SHARE; + if (iflagsp->mmap && (rep->outregfd >= 0)) + iflags |= SGV4_FLAG_MMAP_IO; + else + iflags |= SGV4_FLAG_NO_DXFER; + if (iflagsp->dio) + iflags |= SGV4_FLAG_DIRECT_IO; + if (iflagsp->qhead) + iflags |= SGV4_FLAG_Q_AT_HEAD; + if (iflagsp->qtail) + iflags |= SGV4_FLAG_Q_AT_TAIL; + + oflags = SGV4_FLAG_SHARE | SGV4_FLAG_NO_DXFER; + if (oflagsp->dio) + oflags |= SGV4_FLAG_DIRECT_IO; + if (oflagsp->qhead) + oflags |= SGV4_FLAG_Q_AT_HEAD; + if (oflagsp->qtail) + oflags |= SGV4_FLAG_Q_AT_TAIL; + oflags |= SGV4_FLAG_DO_ON_OTHER; + + for (k = 0; seg_blks > 0; ++k, seg_blks -= num) { + kk = min<int>(seg_blks, clp->bpt); + i_lin_blks = i_sg_it.linear_for_n_blks(kk); + o_lin_blks = o_sg_it.linear_for_n_blks(kk); + num = min<int>(i_lin_blks, o_lin_blks); + if (num <= 0) { + res = 0; + pr2serr_lk("[%d] %s: unexpected num=%d\n", id, __func__, num); + break; + } + + /* First build the command/request for the master (READ) side */ + cdbsz = clp->cdbsz_in; + res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num, + i_sg_it.current_lba(), false, false, + iflagsp->fua, iflagsp->dpo); + if (res) { + pr2serr_lk("%s: t=%d: input sg_build_scsi_cdb() failed\n", + __func__, id); + break; + } else if (clp->verbose > 3) + lk_print_command_len("input cdb: ", t_cdb.data(), cdbsz, true); + a_cdb.push_back(t_cdb); + + memset(t_v4p, 0, sizeof(*t_v4p)); + t_v4p->guard = 'Q'; + t_v4p->flags = iflags; + t_v4p->request_len = cdbsz; + t_v4p->din_xfer_len = num * clp->bs; + t_v4p->timeout = DEF_TIMEOUT; + t_v4p->usr_ptr = num; /* pass number blocks requested */ + in_mrq_q_blks += num; + t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off; + a_v4.push_back(t_v4); + + /* Now build the command/request for slave (WRITE or VERIFY) side */ + cdbsz = clp->cdbsz_out; + res = sg_build_scsi_cdb(t_cdb.data(), cdbsz, num, + o_sg_it.current_lba(), clp->verify, true, + oflagsp->fua, oflagsp->dpo); + if (res) { + pr2serr_lk("%s: t=%d: output sg_build_scsi_cdb() failed\n", + __func__, id); + break; + } else if (clp->verbose > 3) + lk_print_command_len("output cdb: ", t_cdb.data(), cdbsz, true); + a_cdb.push_back(t_cdb); + memset(t_v4p, 0, sizeof(*t_v4p)); + t_v4p->guard = 'Q'; + t_v4p->flags = oflags; + t_v4p->request_len = cdbsz; + t_v4p->dout_xfer_len = num * clp->bs; + t_v4p->timeout = DEF_TIMEOUT; + t_v4p->usr_ptr = num; /* pass number blocks requested */ + out_mrq_q_blks += num; + t_v4p->request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off; + a_v4.push_back(t_v4); + + if (clp->verbose > 5) { + pr2serr_lk("%s: t=%d: a_v4 array contents:\n", __func__, id); + hex2stderr_lk((const uint8_t *)a_v4.data(), + a_v4.size() * sizeof(struct sg_io_v4), 1); + } + i_sg_it.add_blks(num); + o_sg_it.add_blks(num); + } + + if (rep->both_sg || rep->same_sg) + fd = rep->infd; /* assume share to rep->outfd */ + else if (rep->only_in_sg) + fd = rep->infd; + else if (rep->only_out_sg) + fd = rep->outfd; + else { + pr2serr_lk("[%d] %s: why am I here? No sg devices\n", id, __func__); + res = -1; + goto fini; + } + num_mrq = a_v4.size(); + a_v4p = a_v4.data(); + res = 0; + memset(&ctl_v4, 0, sizeof(ctl_v4)); + ctl_v4.guard = 'Q'; + ctl_v4.request_len = a_cdb.size() * max_cdb_sz; + ctl_v4.request = (uint64_t)a_cdb.data(); + ctl_v4.max_response_len = sizeof(rep->sb); + ctl_v4.response = (uint64_t)rep->sb; + ctl_v4.flags = SGV4_FLAG_MULTIPLE_REQS | SGV4_FLAG_STOP_IF | + SGV4_FLAG_SHARE; + if ((! clp->verify) && clp->out_flags.order) + ctl_v4.flags |= SGV4_FLAG_ORDERED_SLV; + ctl_v4.dout_xferp = (uint64_t)a_v4.data(); /* request array */ + ctl_v4.dout_xfer_len = a_v4.size() * sizeof(struct sg_io_v4); + ctl_v4.din_xferp = (uint64_t)a_v4.data(); /* response array */ + ctl_v4.din_xfer_len = a_v4.size() * sizeof(struct sg_io_v4); + if (false /* allow_mrq_abort */) + ctl_v4.request_extra = mrq_pack_id_base + ++rep->mrq_pack_id_off; + + if (clp->verbose > 4) { + pr2serr_lk("%s: Controlling object _before_ ioctl(SG_IO%s):\n", + __func__, iosub_str); + if (clp->verbose > 5) + hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1); + v4hdr_out_lk("Controlling object before", &ctl_v4, id); + } + +try_again: + if (!after1 && (clp->verbose > 1)) { + after1 = true; + pr2serr_lk("%s: %s\n", __func__, mrq_svb_s); + } + res = ioctl(fd, SG_IOSUBMIT, &ctl_v4); + if (res < 0) { + int err = errno; + + if (E2BIG == err) + sg_take_snap(fd, id, true); + else if (EBUSY == err) { + ++num_ebusy; + std::this_thread::yield();/* allow another thread to progress */ + goto try_again; + } + pr2serr_lk("%s: ioctl(SG_IO%s, %s)-->%d, errno=%d: %s\n", __func__, + iosub_str, sg_flags_str(ctl_v4.flags, b_len, b), res, err, + strerror(err)); + res = -err; + goto fini; + } + if (clp->verbose > 4) { + pr2serr_lk("%s: Controlling object output by ioctl(%s):\n", __func__, + iosub_str); + if (clp->verbose > 5) + hex2stderr_lk((const uint8_t *)&ctl_v4, sizeof(ctl_v4), 1); + v4hdr_out_lk("Controlling object after", &ctl_v4, id); + if (clp->verbose > 5) { + for (k = 0; k < num_mrq; ++k) { + pr2serr_lk("AFTER: def_arr[%d]:\n", k); + v4hdr_out_lk("normal v4 object", (a_v4p + k), id); + // hex2stderr_lk((const uint8_t *)(a_v4p + k), sizeof(*a_v4p), + // 1); + } + } + } + num_good = process_mrq_response(rep, &ctl_v4, a_v4p, num_mrq, in_fin_blks, + out_fin_blks); + if (clp->verbose > 2) + pr2serr_lk("%s: >>> seg_blks=%d, num_good=%d, in_q/fin blks=%u/%u; " + "out_q/fin blks=%u/%u\n", __func__, o_seg_blks, num_good, + in_mrq_q_blks, in_fin_blks, out_mrq_q_blks, out_fin_blks); + + if (num_good < 0) + res = -ENODATA; + else { + rep->in_local_count += in_fin_blks; + rep->out_local_count += out_fin_blks; + + if (num_good < num_mrq) { + int resid_blks = in_mrq_q_blks - in_fin_blks; + + if (resid_blks > 0) + rep->in_rem_count += resid_blks; + resid_blks = out_mrq_q_blks - out_fin_blks; + if (resid_blks > 0) + rep->out_rem_count += resid_blks; + rep->stop_after_write = true; + } + } +fini: + return res < 0 ? res : (min<int>(in_fin_blks, out_fin_blks)); +} + +/* Returns reserved_buffer_size/mmap_size if success, else 0 for failure */ +static int +sg_prepare_resbuf(int fd, int bs, int bpt, bool unit_nano, bool no_dur, + bool masync, bool wq_excl, uint8_t **mmpp) +{ + static bool done = false; + int res, t, num; + uint8_t *mmp; + struct sg_extended_info sei; + struct sg_extended_info * seip; + + seip = &sei; + res = ioctl(fd, SG_GET_VERSION_NUM, &t); + if ((res < 0) || (t < 40000)) { + if (ioctl(fd, SG_GET_RESERVED_SIZE, &num) < 0) { + perror("SG_GET_RESERVED_SIZE ioctl failed"); + return 0; + } + if (! done) { + done = true; + pr2serr_lk("%ssg driver prior to 4.0.00, reduced functionality\n", + my_name); + } + goto bypass; + } + if (no_dur || masync) { + memset(seip, 0, sizeof(*seip)); + seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS; + if (no_dur) { + seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_NO_DURATION; + seip->ctl_flags |= SG_CTL_FLAGM_NO_DURATION; + } + if (masync) { + seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_MORE_ASYNC; + seip->ctl_flags |= SG_CTL_FLAGM_MORE_ASYNC; + } + if (wq_excl) { + seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_EXCL_WAITQ; + seip->ctl_flags |= SG_CTL_FLAGM_EXCL_WAITQ; + } + res = ioctl(fd, SG_SET_GET_EXTENDED, seip); + if (res < 0) + pr2serr_lk("sgh_dd: %s: SG_SET_GET_EXTENDED(NO_DURATION) " + "error: %s\n", __func__, strerror(errno)); + } +bypass: + num = bs * bpt; + res = ioctl(fd, SG_SET_RESERVED_SIZE, &num); + if (res < 0) { + perror("sgh_dd: SG_SET_RESERVED_SIZE error"); + return 0; + } else { + int nn; + + res = ioctl(fd, SG_GET_RESERVED_SIZE, &nn); + if (res < 0) { + perror("sgh_dd: SG_GET_RESERVED_SIZE error"); + return 0; + } + if (nn < num) { + pr2serr_lk("%s: SG_GET_RESERVED_SIZE shows size truncated, " + "wanted %d got %d\n", __func__, num, nn); + return 0; + } + if (mmpp) { + mmp = (uint8_t *)mmap(NULL, num, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (MAP_FAILED == mmp) { + int err = errno; + + pr2serr_lk("sgh_dd: %s: sz=%d, fd=%d, mmap() failed: %s\n", + __func__, num, fd, strerror(err)); + return 0; + } + *mmpp = mmp; + } + } + t = 1; + res = ioctl(fd, SG_SET_FORCE_PACK_ID, &t); + if (res < 0) + perror("sgh_dd: SG_SET_FORCE_PACK_ID error"); + if (unit_nano) { + memset(seip, 0, sizeof(*seip)); + seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS; + seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS; + seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS; + if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) { + res = -1; + pr2serr_lk("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n", + errno, strerror(errno)); + } + } + t = 1; + res = ioctl(fd, SG_SET_DEBUG, &t); /* more info in /proc/scsi/sg/debug */ + if (res < 0) + perror("sgh_dd: SG_SET_DEBUG error"); + return (res < 0) ? 0 : num; +} + +/* Returns the number of times 'ch' is found in string 's' given the + * string's length. */ +static int +num_chs_in_str(const char * s, int slen, int ch) +{ + int res = 0; + + while (--slen >= 0) { + if (ch == s[slen]) + ++res; + } + return res; +} + +/* Returns the number of times either 'ch1' or 'ch2' is found in + * string 's' given the string's length. */ +int +num_either_ch_in_str(const char * s, int slen, int ch1, int ch2) +{ + int k; + int res = 0; + + while (--slen >= 0) { + k = s[slen]; + if ((ch1 == k) || (ch2 == k)) + ++res; + } + return res; +} + +/* Allocates and then populates a scatter gether list (array) and returns + * it via *sgl_pp. Return of 0 is okay, else error number (in which case + * NULL is written to *sgl_pp) . */ +static int +skip_seek(struct global_collection *clp, const char * key, const char * buf, + bool is_skip, bool ignore_verbose) +{ + bool def_hex = false; + int len, err; + int vb = clp->verbose; /* needs to appear before skip/seek= on cl */ + int64_t ll; + const char * cp; + class scat_gath_list & either_list = is_skip ? clp->i_sgl : clp->o_sgl; + + if (ignore_verbose) + vb = 0; + len = (int)strlen(buf); + if ((('-' == buf[0]) && (1 == len)) || ((len > 1) && ('@' == buf[0])) || + ((len > 2) && ('H' == toupper(buf[0])) && ('@' == buf[1]))) { + if ('H' == toupper(buf[0])) { + cp = buf + 2; + def_hex = true; + } else if ('-' == buf[0]) + cp = buf; + else + cp = buf + 1; + if (! either_list.load_from_file(cp, def_hex, clp->flexible, true)) { + pr2serr("bad argument to '%s=' [err=%d]\n", key, + either_list.m_errno); + return err ? err : SG_LIB_SYNTAX_ERROR; + } + } else if (num_either_ch_in_str(buf, len, ',', ' ') > 0) { + if (! either_list.load_from_cli(buf, vb > 0)) { + pr2serr("bad command line argument to '%s='\n", key); + return SG_LIB_SYNTAX_ERROR; + } + } else { /* single number on command line (e.g. skip=1234) */ + ll = sg_get_llnum(buf); + if (-1LL == ll) { + pr2serr("bad argument to '%s='\n", key); + return SG_LIB_SYNTAX_ERROR; + } + either_list.append_1or(0, ll); + if (vb > 1) + pr2serr("%s: singleton, half a degenerate sgl element\n", key); + } + + either_list.sum_scan(key, vb > 3 /* bool show_sgl */, vb > 1); +#if 0 + if (vb > 3) { + pr2serr("%s: scatter gathet list:\n", is_skip ? ("skip" : "seek")); + either_list.dbg_print(false, is_skip ? ("skip" : "seek"), false, + bool show_sgl) +#endif + return 0; +} + +static bool +process_flags(const char * arg, struct flags_t * fp) +{ + char buff[256]; + char * cp; + char * np; + + strncpy(buff, arg, sizeof(buff)); + buff[sizeof(buff) - 1] = '\0'; + if ('\0' == buff[0]) { + pr2serr("no flag found\n"); + return false; + } + cp = buff; + do { + np = strchr(cp, ','); + if (np) + *np++ = '\0'; + if (0 == strcmp(cp, "00")) + fp->zero = true; + else if (0 == strcmp(cp, "append")) + fp->append = true; + else if (0 == strcmp(cp, "coe")) + fp->coe = true; + else if (0 == strcmp(cp, "dio")) + fp->dio = true; + else if (0 == strcmp(cp, "direct")) + fp->direct = true; + else if (0 == strcmp(cp, "dpo")) + fp->dpo = true; + else if (0 == strcmp(cp, "dsync")) + fp->dsync = true; + else if (0 == strcmp(cp, "excl")) + fp->excl = true; + else if (0 == strcmp(cp, "ff")) + fp->ff = true; + else if (0 == strcmp(cp, "fua")) + fp->fua = true; + else if (0 == strcmp(cp, "masync")) + fp->masync = true; + else if (0 == strcmp(cp, "mmap")) + ++fp->mmap; /* mmap > 1 stops munmap() being called */ + else if (0 == strcmp(cp, "nodur")) + fp->no_dur = true; + else if (0 == strcmp(cp, "no_dur")) + fp->no_dur = true; + else if (0 == strcmp(cp, "noxfer")) + ; /* accept but ignore */ + else if (0 == strcmp(cp, "null")) + ; + else if (0 == strcmp(cp, "ordered")) + fp->order = true; + else if (0 == strcmp(cp, "order")) + fp->order = true; + else if (0 == strcmp(cp, "qhead")) + fp->qhead = true; + else if (0 == strcmp(cp, "qtail")) + fp->qtail = true; + else if (0 == strcmp(cp, "random")) + fp->random = true; + else if (0 == strcmp(cp, "serial")) + fp->serial = true; + else if (0 == strcmp(cp, "swait")) + ; /* accept but ignore */ + else if (0 == strcmp(cp, "wq_excl")) + fp->wq_excl = true; + else { + pr2serr("unrecognised flag: %s\n", cp); + return false; + } + cp = np; + } while (cp); + return true; +} + +static int +sg_in_open(struct global_collection *clp, const char *inf, uint8_t **mmpp, + int * mmap_lenp) +{ + int fd, err, n; + int flags = O_RDWR; + char ebuff[EBUFF_SZ]; + + if (clp->in_flags.direct) + flags |= O_DIRECT; + if (clp->in_flags.excl) + flags |= O_EXCL; + if (clp->in_flags.dsync) + flags |= O_SYNC; + + if ((fd = open(inf, flags)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg reading", + __func__, inf); + perror(ebuff); + return -sg_convert_errno(err); + } + n = sg_prepare_resbuf(fd, clp->bs, clp->bpt, clp->unit_nanosec, + clp->in_flags.no_dur, clp->in_flags.masync, + clp->in_flags.wq_excl, mmpp); + if (n <= 0) + return -SG_LIB_FILE_ERROR; + if (mmap_lenp) + *mmap_lenp = n; + return fd; +} + +static int +sg_out_open(struct global_collection *clp, const char *outf, uint8_t **mmpp, + int * mmap_lenp) +{ + int fd, err, n; + int flags = O_RDWR; + char ebuff[EBUFF_SZ]; + + if (clp->out_flags.direct) + flags |= O_DIRECT; + if (clp->out_flags.excl) + flags |= O_EXCL; + if (clp->out_flags.dsync) + flags |= O_SYNC; + + if ((fd = open(outf, flags)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for sg %s", + __func__, outf, (clp->verify ? "verifying" : "writing")); + perror(ebuff); + return -sg_convert_errno(err); + } + n = sg_prepare_resbuf(fd, clp->bs, clp->bpt, clp->unit_nanosec, + clp->out_flags.no_dur, clp->out_flags.masync, + clp->out_flags.wq_excl, mmpp); + if (n <= 0) + return -SG_LIB_FILE_ERROR; + if (mmap_lenp) + *mmap_lenp = n; + return fd; +} + +#define STR_SZ 1024 +#define INOUTF_SZ 512 + +static int +parse_cmdline_sanity(int argc, char * argv[], struct global_collection * clp, + char * inf, char * outf, char * out2f, char * outregf) +{ + bool contra = false; + bool verbose_given = false; + bool version_given = false; + bool verify_given = false; + bool bpt_given = false; + int ibs = 0; + int obs = 0; + int k, keylen, n, res; + char str[STR_SZ]; + char * key; + char * buf; + char * skip_buf = NULL; + char * seek_buf = NULL; + const char * cp; + + for (k = 1; k < argc; k++) { + if (argv[k]) { + strncpy(str, argv[k], STR_SZ); + str[STR_SZ - 1] = '\0'; + } else + continue; + + for (key = str, buf = key; *buf && *buf != '=';) + buf++; + if (*buf) + *buf++ = '\0'; + keylen = strlen(key); + if (0 == strcmp(key, "bpt")) { + clp->bpt = sg_get_num(buf); + if (-1 == clp->bpt) { + pr2serr("%sbad argument to 'bpt='\n", my_name); + goto syn_err; + } + bpt_given = true; + } else if (0 == strcmp(key, "bs")) { + clp->bs = sg_get_num(buf); + if (-1 == clp->bs) { + pr2serr("%sbad argument to 'bs='\n", my_name); + goto syn_err; + } + } else if (0 == strcmp(key, "cdbsz")) { + clp->cdbsz_in = sg_get_num(buf); + clp->cdbsz_out = clp->cdbsz_in; + clp->cdbsz_given = true; + } else if (0 == strcmp(key, "coe")) { + clp->in_flags.coe = !! sg_get_num(buf); + clp->out_flags.coe = clp->in_flags.coe; + } else if (0 == strcmp(key, "count")) { + if (clp->count_given) { + pr2serr("second 'count=' argument detected, only one " + "please\n"); + contra = true; + goto syn_err; + } + if (0 != strcmp("-1", buf)) { + clp->dd_count = sg_get_llnum(buf); + if (-1LL == clp->dd_count) { + pr2serr("%sbad argument to 'count='\n", my_name); + goto syn_err; + } + } /* treat 'count=-1' as calculate count (same as not given) */ + clp->count_given = true; + } else if ((0 == strncmp(key, "deb", 3)) || + (0 == strncmp(key, "verb", 4))) + clp->verbose = sg_get_num(buf); + else if (0 == strcmp(key, "dio")) { + clp->in_flags.dio = !! sg_get_num(buf); + clp->out_flags.dio = clp->in_flags.dio; + } else if (0 == strcmp(key, "fua")) { + n = sg_get_num(buf); + if (n & 1) + clp->out_flags.fua = true; + if (n & 2) + clp->in_flags.fua = true; + } else if (0 == strcmp(key, "ibs")) { + ibs = sg_get_num(buf); + if (-1 == ibs) { + pr2serr("%sbad argument to 'ibs='\n", my_name); + goto syn_err; + } + } else if (0 == strcmp(key, "if")) { + if ('\0' != inf[0]) { + pr2serr("Second 'if=' argument??\n"); + goto syn_err; + } else { + memcpy(inf, buf, INOUTF_SZ); + inf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */ + } + } else if (0 == strcmp(key, "iflag")) { + if (! process_flags(buf, &clp->in_flags)) { + pr2serr("%sbad argument to 'iflag='\n", my_name); + goto syn_err; + } + } else if (0 == strcmp(key, "mrq")) { + if (isdigit(buf[0])) + cp = buf; + else { + pr2serr("%sonly mrq=NRQS which is a number allowed here\n", + my_name); + goto syn_err; + } + clp->mrq_num = sg_get_num(cp); + if (clp->mrq_num < 0) { + pr2serr("%sbad argument to 'mrq='\n", my_name); + goto syn_err; + } + } else if (0 == strcmp(key, "obs")) { + obs = sg_get_num(buf); + if (-1 == obs) { + pr2serr("%sbad argument to 'obs='\n", my_name); + goto syn_err; + } + } else if (strcmp(key, "of2") == 0) { + if ('\0' != out2f[0]) { + pr2serr("Second OFILE2 argument??\n"); + contra = true; + goto syn_err; + } else { + memcpy(out2f, buf, INOUTF_SZ); + out2f[INOUTF_SZ - 1] = '\0'; /* noisy compiler */ + } + } else if (strcmp(key, "ofreg") == 0) { + if ('\0' != outregf[0]) { + pr2serr("Second OFREG argument??\n"); + contra = true; + goto syn_err; + } else { + memcpy(outregf, buf, INOUTF_SZ); + outregf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */ + } + } else if (strcmp(key, "of") == 0) { + if ('\0' != outf[0]) { + pr2serr("Second 'of=' argument??\n"); + goto syn_err; + } else { + memcpy(outf, buf, INOUTF_SZ); + outf[INOUTF_SZ - 1] = '\0'; /* noisy compiler */ + } + } else if (0 == strcmp(key, "oflag")) { + if (! process_flags(buf, &clp->out_flags)) { + pr2serr("%sbad argument to 'oflag='\n", my_name); + goto syn_err; + } + } else if (0 == strcmp(key, "seek")) { + n = strlen(buf); + if (n < 1) { + pr2serr("%sneed argument to 'seek='\n", my_name); + goto syn_err; + } + seek_buf = (char *)calloc(n + 16, 1); + memcpy(seek_buf, buf, n + 1); + } else if (0 == strcmp(key, "skip")) { + n = strlen(buf); + if (n < 1) { + pr2serr("%sneed argument to 'skip='\n", my_name); + goto syn_err; + } + skip_buf = (char *)calloc(n + 16, 1); + memcpy(skip_buf, buf, n + 1); + } else if (0 == strcmp(key, "sync")) + do_sync = !! sg_get_num(buf); + else if (0 == strcmp(key, "thr")) + num_threads = sg_get_num(buf); + else if (0 == strcmp(key, "time")) + do_time = sg_get_num(buf); + else if ((keylen > 1) && ('-' == key[0]) && ('-' != key[1])) { + res = 0; + n = num_chs_in_str(key + 1, keylen - 1, 'd'); + clp->dry_run += n; + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'h'); + clp->help += n; + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'p'); + if (n > 0) + clp->prefetch = true; + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'v'); + if (n > 0) + verbose_given = true; + clp->verbose += n; /* -v ---> --verbose */ + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'V'); + if (n > 0) + version_given = true; + res += n; + n = num_chs_in_str(key + 1, keylen - 1, 'x'); + if (n > 0) + verify_given = true; + res += n; + + if (res < (keylen - 1)) { + pr2serr("Unrecognised short option in '%s', try '--help'\n", + key); + goto syn_err; + } + } else if ((0 == strncmp(key, "--dry-run", 9)) || + (0 == strncmp(key, "--dry_run", 9))) + ++clp->dry_run; + else if ((0 == strncmp(key, "--help", 6)) || + (0 == strcmp(key, "-?"))) + ++clp->help; + else if ((0 == strncmp(key, "--prefetch", 10)) || + (0 == strncmp(key, "--pre-fetch", 11))) + clp->prefetch = true; + else if (0 == strncmp(key, "--verb", 6)) { + verbose_given = true; + ++clp->verbose; /* --verbose */ + } else if (0 == strncmp(key, "--veri", 6)) + verify_given = true; + else if (0 == strncmp(key, "--vers", 6)) + version_given = true; + else { + pr2serr("Unrecognized option '%s'\n", key); + pr2serr("For more information use '--help'\n"); + goto syn_err; + } + } /* end of parsing for loop */ + + if (skip_buf) { + res = skip_seek(clp, "skip", skip_buf, true /* skip */, false); + free(skip_buf); + skip_buf = NULL; + if (res) { + pr2serr("%sbad argument to 'seek='\n", my_name); + goto syn_err; + } + } + if (seek_buf) { + res = skip_seek(clp, "seek", seek_buf, false /* skip */, false); + free(seek_buf); + seek_buf = NULL; + if (res) { + pr2serr("%sbad argument to 'seek='\n", my_name); + goto syn_err; + } + } + /* heap usage should be all freed up now */ + +#ifdef DEBUG + pr2serr("In DEBUG mode, "); + if (verbose_given && version_given) { + pr2serr("but override: '-vV' given, zero verbose and continue\n"); + verbose_given = false; + version_given = false; + clp->verbose = 0; + } else if (! verbose_given) { + pr2serr("set '-vv'\n"); + clp->verbose = 2; + } else + pr2serr("keep verbose=%d\n", clp->verbose); +#else + if (verbose_given && version_given) + pr2serr("Not in DEBUG mode, so '-vV' has no special action\n"); +#endif + if (version_given) { + pr2serr("%s%s\n", my_name, version_str); + return SG_LIB_OK_FALSE; + } + if (clp->help > 0) { + usage(clp->help); + return SG_LIB_OK_FALSE; + } + if (clp->bs <= 0) { + clp->bs = DEF_BLOCK_SIZE; + pr2serr("Assume default 'bs' ((logical) block size) of %d bytes\n", + clp->bs); + } + if (verify_given) { + pr2serr("Doing verify/cmp rather than copy\n"); + clp->verify = true; + } + if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) { + pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n"); + usage(0); + return SG_LIB_SYNTAX_ERROR; + } + if (clp->out_flags.append) { + if ((clp->o_sgl.lowest_lba > 0) || + (clp->o_sgl.linearity != SGL_LINEAR)) { + pr2serr("Can't use both append and seek switches\n"); + return SG_LIB_SYNTAX_ERROR; + } + if (verify_given) { + pr2serr("Can't use both append and verify switches\n"); + return SG_LIB_SYNTAX_ERROR; + } + } + if (clp->bpt < 1) { + pr2serr("bpt must be greater than 0\n"); + return SG_LIB_SYNTAX_ERROR; + } + if (clp->in_flags.mmap && clp->out_flags.mmap) { + pr2serr("mmap flag on both IFILE and OFILE doesn't work\n"); + return SG_LIB_SYNTAX_ERROR; + } +#if 0 + if (clp->out_flags.mmap) { + pr2serr("oflag=mmap needs either noshare=1\n"); + return SG_LIB_SYNTAX_ERROR; + } +#endif + /* defaulting transfer size to 128*2048 for CD/DVDs is too large + for the block layer in lk 2.6 and results in an EIO on the + SG_IO ioctl. So reduce it in that case. */ + if ((clp->bs >= 2048) && (! bpt_given)) + clp->bpt = DEF_BLOCKS_PER_2048TRANSFER; + if (clp->in_flags.order) + pr2serr("Warning iflag=order is ignored, use with oflag=\n"); + if ((num_threads < 1) || (num_threads > MAX_NUM_THREADS)) { + pr2serr("too few or too many threads requested\n"); + usage(1); + return SG_LIB_SYNTAX_ERROR; + } + clp->unit_nanosec = (do_time > 1) || !!getenv("SG3_UTILS_LINUX_NANO"); + return 0; + +syn_err: + if (seek_buf) + free(seek_buf); + if (skip_buf) + free(skip_buf); + return contra ? SG_LIB_CONTRADICT : SG_LIB_SYNTAX_ERROR; +} + +static int +calc_count(struct global_collection * clp, const char * inf, + int64_t & in_num_sect, const char * outf, int64_t & out_num_sect) +{ + int in_sect_sz, out_sect_sz, res; + + if (clp->dd_count < 0) { + in_num_sect = -1; + out_num_sect = -1; + } + if (FT_SG == clp->in_type) { + res = scsi_read_capacity(clp->infd, &in_num_sect, &in_sect_sz); + if (2 == res) { + pr2serr("Unit attention, media changed(in), continuing\n"); + res = scsi_read_capacity(clp->infd, &in_num_sect, + &in_sect_sz); + } + if (0 != res) { + if (res == SG_LIB_CAT_INVALID_OP) + pr2serr("read capacity not supported on %s\n", inf); + else if (res == SG_LIB_CAT_NOT_READY) + pr2serr("read capacity failed, %s not ready\n", inf); + else + pr2serr("Unable to read capacity on %s\n", inf); + return SG_LIB_FILE_ERROR; + } else if (clp->bs != in_sect_sz) { + pr2serr(">> warning: logical block size on %s confusion: " + "bs=%d, device claims=%d\n", clp->infp, clp->bs, + in_sect_sz); + return SG_LIB_FILE_ERROR; + } + } + if (FT_SG == clp->out_type) { + res = scsi_read_capacity(clp->outfd, &out_num_sect, &out_sect_sz); + if (2 == res) { + pr2serr("Unit attention, media changed(out), continuing\n"); + res = scsi_read_capacity(clp->outfd, &out_num_sect, + &out_sect_sz); + } + if (0 != res) { + if (res == SG_LIB_CAT_INVALID_OP) + pr2serr("read capacity not supported on %s\n", outf); + else if (res == SG_LIB_CAT_NOT_READY) + pr2serr("read capacity failed, %s not ready\n", outf); + else + pr2serr("Unable to read capacity on %s\n", outf); + out_num_sect = -1; + return SG_LIB_FILE_ERROR; + } else if (clp->bs != out_sect_sz) { + pr2serr(">> warning: logical block size on %s confusion: " + "bs=%d, device claims=%d\n", clp->outfp, clp->bs, + out_sect_sz); + return SG_LIB_FILE_ERROR; + } + } + + if (clp->dd_count < 0) { + if (FT_SG == clp->in_type) + ; + else if (FT_BLOCK == clp->in_type) { + if (0 != read_blkdev_capacity(clp->infd, &in_num_sect, + &in_sect_sz)) { + pr2serr("Unable to read block capacity on %s\n", inf); + in_num_sect = -1; + } + if (clp->bs != in_sect_sz) { + pr2serr("logical block size on %s confusion; bs=%d, from " + "device=%d\n", inf, clp->bs, in_sect_sz); + in_num_sect = -1; + } + } + + if (FT_SG == clp->out_type) + ; + else if (FT_BLOCK == clp->out_type) { + if (0 != read_blkdev_capacity(clp->outfd, &out_num_sect, + &out_sect_sz)) { + pr2serr("Unable to read block capacity on %s\n", outf); + out_num_sect = -1; + } + if (clp->bs != out_sect_sz) { + pr2serr("logical block size on %s confusion: bs=%d, from " + "device=%d\n", outf, clp->bs, out_sect_sz); + out_num_sect = -1; + } + } + } + return 0; +} + +static int +do_count_work(struct global_collection * clp, const char * inf, + int64_t & in_num_sect, const char * outf, + int64_t & out_num_sect) +{ + int res; + class scat_gath_list * isglp = &clp->i_sgl; + class scat_gath_list * osglp = &clp->o_sgl; + + res = calc_count(clp, inf, in_num_sect, outf, out_num_sect); + if (res) + return res; + + if ((-1 == in_num_sect) && (FT_OTHER == clp->in_type)) { + in_num_sect = clp->in_st_size / clp->bs; + if (clp->in_st_size % clp->bs) { + ++in_num_sect; + pr2serr("Warning: the file size of %s is not a multiple of BS " + "[%d]\n", inf, clp->bs); + } + } + if ((in_num_sect > 0) && (isglp->high_lba_p1 > in_num_sect)) { + pr2serr("%shighest LBA [0x%" PRIx64 "] exceeds input length: %" + PRIx64 " blocks\n", my_name, isglp->high_lba_p1 - 1, + in_num_sect); + return SG_LIB_CAT_OTHER; + } + if ((out_num_sect > 0) && (osglp->high_lba_p1 > out_num_sect)) { + pr2serr("%shighest LBA [0x%" PRIx64 "] exceeds output length: %" + PRIx64 " blocks\n", my_name, osglp->high_lba_p1 - 1, + out_num_sect); + return SG_LIB_CAT_OTHER; + } + + if (isglp->sum_hard || osglp->sum_hard) { + int64_t ccount; + + if (isglp->sum_hard && osglp->sum_hard) { + if (isglp->sum != osglp->sum) { + pr2serr("%stwo hard sgl_s, sum of blocks differ: in=%" PRId64 + ", out=%" PRId64 "\n", my_name , isglp->sum, + osglp->sum); + return SG_LIB_CAT_OTHER; + } + ccount = isglp->sum; + } else if (isglp->sum_hard) { + if (osglp->sum > isglp->sum) { + pr2serr("%soutput sgl already too many blocks [%" PRId64 + "]\n", my_name, osglp->sum); + return SG_LIB_CAT_OTHER; + } + if (osglp->linearity != SGL_NON_MONOTONIC) + osglp->append_1or(isglp->sum - osglp->sum); + else { + pr2serr("%soutput sgl non-montonic: can't extend\n", + my_name); + return SG_LIB_CAT_OTHER; + } + ccount = isglp->sum; + } else { /* only osglp hard */ + if (isglp->sum > osglp->sum) { + pr2serr("%sinput sgl already too many blocks [%" PRId64 + "]\n", my_name, isglp->sum); + return SG_LIB_CAT_OTHER; + } + if (isglp->linearity != SGL_NON_MONOTONIC) + isglp->append_1or(osglp->sum - isglp->sum); + else { + pr2serr("%sinput sgl non-monotonic: can't extend\n", + my_name); + return SG_LIB_CAT_OTHER; + } + ccount = osglp->sum; + } + if (SG_COUNT_INDEFINITE == clp->dd_count) + clp->dd_count = ccount; + else if (ccount != clp->dd_count) { + pr2serr("%scount=COUNT disagrees with scatter gather list " + "length [%" PRId64 "]\n", my_name, ccount); + return SG_LIB_CAT_OTHER; + } + } else if (clp->dd_count != 0) { /* and both input and output are soft */ + if (clp->dd_count > 0) { + if (isglp->sum > clp->dd_count) { + pr2serr("%sskip sgl sum [%" PRId64 "] exceeds COUNT\n", + my_name, isglp->sum); + return SG_LIB_CAT_OTHER; + } + if (osglp->sum > clp->dd_count) { + pr2serr("%sseek sgl sum [%" PRId64 "] exceeds COUNT\n", + my_name, osglp->sum); + return SG_LIB_CAT_OTHER; + } + goto fini; + } + + /* clp->dd_count == SG_COUNT_INDEFINITE */ + int64_t iposs = INT64_MAX; + int64_t oposs = INT64_MAX; + + if (in_num_sect > 0) + iposs = in_num_sect + isglp->sum - isglp->high_lba_p1; + if (out_num_sect > 0) + oposs = out_num_sect + osglp->sum - osglp->high_lba_p1; + clp->dd_count = iposs < oposs ? iposs : oposs; + if (INT64_MAX == clp->dd_count) { + pr2serr("%scan't deduce count=COUNT, please supply one\n", + my_name); + return SG_LIB_CAT_OTHER; + } + if (isglp->sum > clp->dd_count) { + pr2serr("%sdeduced COUNT [%" PRId64 "] exceeds skip sgl sum\n", + my_name, clp->dd_count); + return SG_LIB_CAT_OTHER; + } + if (osglp->sum > clp->dd_count) { + pr2serr("%sdeduced COUNT [%" PRId64 "] exceeds seek sgl sum\n", + my_name, clp->dd_count); + return SG_LIB_CAT_OTHER; + } + } + if (clp->dd_count == 0) + return 0; +fini: + if (clp->dd_count > isglp->sum) + isglp->append_1or(clp->dd_count - isglp->sum); + if (clp->dd_count > osglp->sum) + osglp->append_1or(clp->dd_count - osglp->sum); + return 0; +} + + +int +main(int argc, char * argv[]) +{ + char inf[INOUTF_SZ]; + char outf[INOUTF_SZ]; + char out2f[INOUTF_SZ]; + char outregf[INOUTF_SZ]; + int res, k, err, flags; + int64_t in_num_sect = -1; + int64_t out_num_sect = -1; + const char * ccp = NULL; + const char * cc2p; + struct global_collection * clp = &gcoll; + thread sig_listen_thr; + vector<thread> work_thr; + vector<thread> listen_thr; + char ebuff[EBUFF_SZ]; +#if 0 /* SG_LIB_ANDROID */ + struct sigaction actions; + + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = thread_exit_handler; + sigaction(SIGUSR1, &actions, NULL); + sigaction(SIGUSR2, &actions, NULL); +#endif + /* memset(clp, 0, sizeof(*clp)); */ + clp->dd_count = SG_COUNT_INDEFINITE; + clp->bpt = DEF_BLOCKS_PER_TRANSFER; + clp->in_type = FT_FIFO; + /* change dd's default: if of=OFILE not given, assume /dev/null */ + clp->out_type = FT_DEV_NULL; + clp->out2_type = FT_DEV_NULL; + clp->cdbsz_in = DEF_SCSI_CDB_SZ; + clp->cdbsz_out = DEF_SCSI_CDB_SZ; + clp->mrq_num = DEF_MRQ_NUM; + inf[0] = '\0'; + outf[0] = '\0'; + out2f[0] = '\0'; + outregf[0] = '\0'; + fetch_sg_version(); + if (sg_version >= 40030) + sg_version_ge_40030 = true; + else { + pr2serr("%srequires an sg driver version of 4.0.30 or later\n", + my_name); + return SG_LIB_SYNTAX_ERROR; + } + + res = parse_cmdline_sanity(argc, argv, clp, inf, outf, out2f, outregf); + if (SG_LIB_OK_FALSE == res) + return 0; + if (res) + return res; + + install_handler(SIGINT, interrupt_handler); + install_handler(SIGQUIT, interrupt_handler); + install_handler(SIGPIPE, interrupt_handler); + install_handler(SIGUSR1, siginfo_handler); + install_handler(SIGUSR2, siginfo2_handler); + + clp->infd = STDIN_FILENO; + clp->outfd = STDOUT_FILENO; + if (clp->in_flags.ff) { + ccp = "<0xff bytes>"; + cc2p = "ff"; + } else if (clp->in_flags.random) { + ccp = "<random>"; + cc2p = "random"; + } else if (clp->in_flags.zero) { + ccp = "<zero bytes>"; + cc2p = "00"; + } + if (ccp) { + if (inf[0]) { + pr2serr("%siflag=%s and if=%s contradict\n", my_name, cc2p, inf); + return SG_LIB_CONTRADICT; + } + clp->in_type = FT_RANDOM_0_FF; + clp->infp = ccp; + clp->infd = -1; + } else if (inf[0] && ('-' != inf[0])) { + clp->in_type = dd_filetype(inf, clp->in_st_size); + + if (FT_ERROR == clp->in_type) { + pr2serr("%sunable to access %s\n", my_name, inf); + return SG_LIB_FILE_ERROR; + } else if (FT_ST == clp->in_type) { + pr2serr("%sunable to use scsi tape device %s\n", my_name, inf); + return SG_LIB_FILE_ERROR; + } else if (FT_SG == clp->in_type) { + clp->infd = sg_in_open(clp, inf, NULL, NULL); + if (clp->infd < 0) + return -clp->infd; + } else { + flags = O_RDONLY; + if (clp->in_flags.direct) + flags |= O_DIRECT; + if (clp->in_flags.excl) + flags |= O_EXCL; + if (clp->in_flags.dsync) + flags |= O_SYNC; + + if ((clp->infd = open(inf, flags)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for reading", + my_name, inf); + perror(ebuff); + return sg_convert_errno(err); + } + } + clp->infp = inf; + } + if (outf[0]) { + clp->ofile_given = true; + if (('-' == outf[0])) + clp->out_type = FT_FIFO; + else + clp->out_type = dd_filetype(outf, clp->out_st_size); + + if ((FT_SG != clp->out_type) && clp->verify) { + pr2serr("%s --verify only supported by sg OFILEs\n", my_name); + return SG_LIB_FILE_ERROR; + } + if (FT_FIFO == clp->out_type) + ; + else if (FT_ST == clp->out_type) { + pr2serr("%sunable to use scsi tape device %s\n", my_name, outf); + return SG_LIB_FILE_ERROR; + } else if (FT_SG == clp->out_type) { + clp->outfd = sg_out_open(clp, outf, NULL, NULL); + if (clp->outfd < 0) + return -clp->outfd; + } else if (FT_DEV_NULL == clp->out_type) + clp->outfd = -1; /* don't bother opening */ + else { + if (FT_RAW != clp->out_type) { + flags = O_WRONLY | O_CREAT; + if (clp->out_flags.direct) + flags |= O_DIRECT; + if (clp->out_flags.excl) + flags |= O_EXCL; + if (clp->out_flags.dsync) + flags |= O_SYNC; + if (clp->out_flags.append) + flags |= O_APPEND; + + if ((clp->outfd = open(outf, flags, 0666)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for " + "writing", my_name, outf); + perror(ebuff); + return sg_convert_errno(err); + } + } + else { /* raw output file */ + if ((clp->outfd = open(outf, O_WRONLY)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for raw " + "writing", my_name, outf); + perror(ebuff); + return sg_convert_errno(err); + } + } + } + clp->outfp = outf; + } + + if (out2f[0]) + clp->ofile2_given = true; + if (out2f[0] && ('-' != out2f[0])) { + clp->out2_type = dd_filetype(out2f, clp->out2_st_size); + + if (FT_ST == clp->out2_type) { + pr2serr("%sunable to use scsi tape device %s\n", my_name, out2f); + return SG_LIB_FILE_ERROR; + } + else if (FT_SG == clp->out2_type) { + clp->out2fd = sg_out_open(clp, out2f, NULL, NULL); + if (clp->out2fd < 0) + return -clp->out2fd; + } + else if (FT_DEV_NULL == clp->out2_type) + clp->out2fd = -1; /* don't bother opening */ + else { + if (FT_RAW != clp->out2_type) { + flags = O_WRONLY | O_CREAT; + if (clp->out_flags.direct) + flags |= O_DIRECT; + if (clp->out_flags.excl) + flags |= O_EXCL; + if (clp->out_flags.dsync) + flags |= O_SYNC; + if (clp->out_flags.append) + flags |= O_APPEND; + + if ((clp->out2fd = open(out2f, flags, 0666)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for " + "writing", my_name, out2f); + perror(ebuff); + return sg_convert_errno(err); + } + } + else { /* raw output file */ + if ((clp->out2fd = open(out2f, O_WRONLY)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scould not open %s for raw " + "writing", my_name, out2f); + perror(ebuff); + return sg_convert_errno(err); + } + } + if (clp->o_sgl.lowest_lba > 0) { + off64_t offset = clp->o_sgl.lowest_lba; + + offset *= clp->bs; /* could exceed 32 bits here! */ + if (lseek64(clp->out2fd, offset, SEEK_SET) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "%scouldn't seek to required " + "position on %s", my_name, out2f); + perror(ebuff); + return sg_convert_errno(err); + } + } + } + clp->out2fp = out2f; + } + if ((FT_SG == clp->in_type ) && (FT_SG == clp->out_type)) { + ; + } else if (clp->in_flags.order) + pr2serr("Warning: oflag=order only active on sg->sg copies\n"); + + if (outregf[0]) { + int ftyp = dd_filetype(outregf, clp->outreg_st_size); + + clp->outreg_type = ftyp; + if (! ((FT_OTHER == ftyp) || (FT_ERROR == ftyp) || + (FT_DEV_NULL == ftyp))) { + pr2serr("File: %s can only be regular file or pipe (or " + "/dev/null)\n", outregf); + return SG_LIB_SYNTAX_ERROR; + } + if ((clp->outregfd = open(outregf, O_WRONLY | O_CREAT, 0666)) < 0) { + err = errno; + snprintf(ebuff, EBUFF_SZ, "could not open %s for writing", + outregf); + perror(ebuff); + return sg_convert_errno(err); + } + if (clp->verbose > 1) + pr2serr("ofreg=%s opened okay, fd=%d\n", outregf, clp->outregfd); + if (FT_ERROR == ftyp) + clp->outreg_type = FT_OTHER; /* regular file created */ + } else + clp->outregfd = -1; + + if ((STDIN_FILENO == clp->infd) && (STDOUT_FILENO == clp->outfd)) { + pr2serr("Won't default both IFILE to stdin _and_ OFILE to " + "/dev/null\n"); + pr2serr("For more information use '--help'\n"); + return SG_LIB_SYNTAX_ERROR; + } + if ((clp->in_type == FT_FIFO) && (! clp->i_sgl.is_pipe_suitable())) { + pr2serr("The skip= argument is not suitable for a pipe\n"); + return SG_LIB_SYNTAX_ERROR; + } + if ((clp->out_type == FT_FIFO) && (! clp->o_sgl.is_pipe_suitable())) { + pr2serr("The seek= argument is not suitable for a pipe\n"); + return SG_LIB_SYNTAX_ERROR; + } + res = do_count_work(clp, inf, in_num_sect, outf, out_num_sect); + if (res) + return res; + + if (clp->verbose > 2) + pr2serr("Start of loop, count=%" PRId64 ", in_num_sect=%" PRId64 + ", out_num_sect=%" PRId64 "\n", clp->dd_count, in_num_sect, + out_num_sect); + if (clp->dd_count < 0) { + pr2serr("Couldn't calculate count, please give one\n"); + return SG_LIB_CAT_OTHER; + } + if (! clp->cdbsz_given) { + if ((FT_SG == clp->in_type) && (MAX_SCSI_CDB_SZ != clp->cdbsz_in) && + ((clp->i_sgl.high_lba_p1 > UINT_MAX) || (clp->bpt > USHRT_MAX))) { + pr2serr("Note: SCSI command size increased to 16 bytes (for " + "'if')\n"); + clp->cdbsz_in = MAX_SCSI_CDB_SZ; + } + if ((FT_SG == clp->out_type) && (MAX_SCSI_CDB_SZ != clp->cdbsz_out) && + ((clp->o_sgl.high_lba_p1 > UINT_MAX) || (clp->bpt > USHRT_MAX))) { + pr2serr("Note: SCSI command size increased to 16 bytes (for " + "'of')\n"); + clp->cdbsz_out = MAX_SCSI_CDB_SZ; + } + } + + clp->in_rem_count = clp->dd_count; + clp->out_rem_count = clp->dd_count; +#if 0 + status = pthread_mutex_init(&clp->out2_mutex, NULL); + if (0 != status) err_exit(status, "init out2_mutex"); +#endif + + if (clp->dry_run > 0) { + pr2serr("Due to --dry-run option, bypass copy/read\n"); + goto fini; + } + if (! clp->ofile_given) + pr2serr("of=OFILE not given so only read from IFILE, to output to " + "stdout use 'of=-'\n"); + + sigemptyset(&signal_set); + sigaddset(&signal_set, SIGINT); +#if 0 + status = pthread_sigmask(SIG_BLOCK, &signal_set, NULL); + if (0 != status) err_exit(status, "pthread_sigmask"); +#endif + + res = sigprocmask(SIG_BLOCK, &signal_set, NULL); + if (res < 0) { + pr2serr("sigprocmask failed: %s\n", safe_strerror(errno)); + goto fini; + } + +#if 0 + status = pthread_create(&sig_listen_thread_id, NULL, + sig_listen_thread, (void *)clp); + if (0 != status) err_exit(status, "pthread_create, sig..."); +#endif + + listen_thr.emplace_back(sig_listen_thread, clp); + + if (do_time) { + start_tm.tv_sec = 0; + start_tm.tv_usec = 0; + gettimeofday(&start_tm, NULL); + } + +/* vvvvvvvvvvv Start worker threads vvvvvvvvvvvvvvvvvvvvvvvv */ + if (num_threads > 0) { + /* launch "infant" thread to catch early mortality, if any */ + work_thr.emplace_back(read_write_thread, clp, 0, true); + { + unique_lock<mutex> lk(clp->infant_mut); + clp->infant_cv.wait(lk, []{ return gcoll.processed; }); + } + if (clp->next_count_pos.load() < 0) { + /* infant thread error-ed out, join with it */ + for (auto & t : work_thr) { + if (t.joinable()) + t.join(); + } + goto jump; + } + + /* now start the rest of the threads */ + for (k = 1; k < num_threads; ++k) + work_thr.emplace_back(read_write_thread, clp, k, false); + + /* now wait for worker threads to finish */ + for (auto & t : work_thr) { + if (t.joinable()) + t.join(); + } + } /* started worker threads and hereafter they have all exited */ +jump: + if (do_time && (start_tm.tv_sec || start_tm.tv_usec)) + calc_duration_throughput(0); + + if (do_sync) { + if (FT_SG == clp->out_type) { + pr2serr_lk(">> Synchronizing cache on %s\n", outf); + res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false, 0); + if (SG_LIB_CAT_UNIT_ATTENTION == res) { + pr2serr_lk("Unit attention(out), continuing\n"); + res = sg_ll_sync_cache_10(clp->outfd, 0, 0, 0, 0, 0, false, + 0); + } + if (0 != res) + pr2serr_lk("Unable to synchronize cache\n"); + } + if (FT_SG == clp->out2_type) { + pr2serr_lk(">> Synchronizing cache on %s\n", out2f); + res = sg_ll_sync_cache_10(clp->out2fd, 0, 0, 0, 0, 0, false, 0); + if (SG_LIB_CAT_UNIT_ATTENTION == res) { + pr2serr_lk("Unit attention(out2), continuing\n"); + res = sg_ll_sync_cache_10(clp->out2fd, 0, 0, 0, 0, 0, false, + 0); + } + if (0 != res) + pr2serr_lk("Unable to synchronize cache (of2)\n"); + } + } + + shutting_down = true; + for (auto & t : listen_thr) { + if (t.joinable()) { + t.detach(); + t.~thread(); /* kill listening thread */ + } + } + +fini: + + if ((STDIN_FILENO != clp->infd) && (clp->infd >= 0)) + close(clp->infd); + if ((STDOUT_FILENO != clp->outfd) && (FT_DEV_NULL != clp->out_type) && + (clp->outfd >= 0)) + close(clp->outfd); + if ((clp->out2fd >= 0) && (STDOUT_FILENO != clp->out2fd) && + (FT_DEV_NULL != clp->out2_type)) + close(clp->out2fd); + if ((clp->outregfd >= 0) && (STDOUT_FILENO != clp->outregfd) && + (FT_DEV_NULL != clp->outreg_type)) + close(clp->outregfd); + res = exit_status; +#if 0 + if ((0 != clp->out_count.load()) && (0 == clp->dry_run)) { + pr2serr(">>>> Some error occurred, remaining blocks=%" PRId64 "\n", + clp->out_count.load()); + if (0 == res) + res = SG_LIB_CAT_OTHER; + } +#endif + print_stats(""); + if (clp->dio_incomplete_count.load()) { + int fd; + char c; + + pr2serr(">> Direct IO requested but incomplete %d times\n", + clp->dio_incomplete_count.load()); + if ((fd = open(proc_allow_dio, O_RDONLY)) >= 0) { + if (1 == read(fd, &c, 1)) { + if ('0' == c) + pr2serr(">>> %s set to '0' but should be set to '1' for " + "direct IO\n", proc_allow_dio); + } + close(fd); + } + } + if (clp->sum_of_resids.load()) + pr2serr(">> Non-zero sum of residual counts=%d\n", + clp->sum_of_resids.load()); + if (clp->verbose && (num_start_eagain > 0)) + pr2serr("Number of start EAGAINs: %d\n", num_start_eagain.load()); + if (clp->verbose && (num_fin_eagain > 0)) + pr2serr("Number of finish EAGAINs: %d\n", num_fin_eagain.load()); + if (clp->verbose && (num_ebusy > 0)) + pr2serr("Number of EBUSYs: %d\n", num_ebusy.load()); + if (clp->verbose > 1) { + pr2serr("Number of SG_GET_NUM_WAITING calls=%ld\n", + num_waiting_calls.load()); + } + if (clp->verify && (SG_LIB_CAT_MISCOMPARE == res)) + pr2serr("Verify/compare failed due to miscompare\n"); + return (res >= 0) ? res : SG_LIB_CAT_OTHER; +} |