sg_xcopy+sg_copy_results first commit; sg_persist manpage

git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@448 6180dd3e-e324-4e3e-922d-17de1ae2f315
diff --git a/COVERAGE b/COVERAGE
index 88a8735..c5e75bd 100644
--- a/COVERAGE
+++ b/COVERAGE
@@ -12,6 +12,7 @@
 		     sg__sat_set_features, sg_sat_smart_rd_data
 		     (previous four in the examples directory)]
 ATA COMMAND PASS-THROUGH(12)  sg_sat_identify, ++
+EXTENDED COPY       sg_xcopy, ++
 GET CONFIGURATION   sg_get_config, ++
 GET LBA STATUS      sg_get_lba_status, ++
 INQUIRY             sg_dd, sg_format, sg_inq, sginfo,
@@ -45,6 +46,7 @@
 READ LONG (16)      sg_read_long, ++
 READ MEDIA SERIAL NUMBER     sg_rmsn, ++
 REASSIGN BLOCKS     sg_reassign, ++
+RECEIVE COPY RESULTS   sg_copy_results, ++
 RECEIVE DIAGNOSTIC  sg_senddiag, sg_ses, ++
 REPORT IDENTIFYING INFORMATION  sg_ident, ++ (2)
 REPORT LUNS         sg_luns, ++
@@ -97,4 +99,4 @@
 
 
 Douglas Gilbert
-24th February 2012
+23rd March 2012
diff --git a/CREDITS b/CREDITS
index 4441f0b..cd27d11 100644
--- a/CREDITS
+++ b/CREDITS
@@ -39,6 +39,7 @@
         sg_vpd_vendor), sg_stpg and sg_safte [20071013]
         sg_referrals [20100906]
         sg_inq --export option [20120220]
+        sg_xcopy+sg_copy_results [20120322]
 
 Hayashi Naoyuki <titan at culzean dot org>
         port to Tru64 [20060127]
diff --git a/ChangeLog b/ChangeLog
index 18c5d59..b9dddbc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,7 +2,9 @@
 some description at the top of its ".c" file. All utilities in the main
 directory have their own "man" pages. There is also a sg3_utils man page.
 
-Changelog for sg3_utils-1.34 [20120320] [svn: r447]
+Changelog for sg3_utils-1.34 [20120323] [svn: r448]
+  - sg_xcopy: new utility for extended copy command
+  - sg_copy_results: new utility for receive copy results
   - sg_verify: add 16 byte cdb, bytchk (data-out buffer)
     and group number support
   - sync to spc4r35 and sbc3r30
diff --git a/doc/sg_persist.8 b/doc/sg_persist.8
index 68dc87f..1ea2775 100644
--- a/doc/sg_persist.8
+++ b/doc/sg_persist.8
@@ -126,16 +126,18 @@
 Preempt is a sub\-command of the PROUT command. Preempts the existing
 persistent reservation (identified by \fI\-\-param\-sark=SARK\fR) with
 the registration key that is registered for this I_T_L nexus (identified
-by \fI\-\-param\-rk=RK\fR). The associated \fI\-\-prout\-type=TYPE\fR option
-needs to match the type of the reservation.
+by \fI\-\-param\-rk=RK\fR). If a new reservation is establised as
+a result of the preemption then the supplied \fI\-\-prout\-type=TYPE\fR
+is used as the type for this new reservation.
 .TP
 \fB\-A\fR, \fB\-\-preempt\-abort\fR
 Preempt and Abort is a sub\-command of the PROUT command. Preempts
 the existing persistent reservation (identified by \fI\-\-param\-sark=SARK\fR)
 with the registration key that is registered for this I_T_L nexus (identified
-by \fI\-\-param\-rk=RK\fR). The associated \fI\-\-prout\-type=TYPE\fR option
-needs to match the type of the reservation. ACA and other pending tasks are
-aborted.
+by \fI\-\-param\-rk=RK\fR). If a new reservation is establised as
+a result of the preemption then the supplied \fI\-\-prout\-type=TYPE\fR
+is used as the type for this new reservation. ACA and other pending
+tasks are aborted.
 .TP
 \fB\-T\fR, \fB\-\-prout\-type\fR=\fITYPE\fR
 specify the PROUT command's 'type' argument. Required by
diff --git a/doc/sg_rtpg.8 b/doc/sg_rtpg.8
index 786dd12..0fe3de7 100644
--- a/doc/sg_rtpg.8
+++ b/doc/sg_rtpg.8
@@ -22,6 +22,9 @@
 target port group descriptor returned. The default action is not
 to decode these values.
 .TP
+\fB\-e\fR, \fB\-\-extended\fR
+use extended header format for parameter data.
+.TP
 \fB\-h\fR, \fB\-\-help\fR
 output the usage message then exit.
 .TP
diff --git a/include/sg_cmds_extra.h b/include/sg_cmds_extra.h
index 2c26a0b..c11198b 100644
--- a/include/sg_cmds_extra.h
+++ b/include/sg_cmds_extra.h
@@ -160,6 +160,8 @@
  * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
 extern int sg_ll_report_tgt_prt_grp(int sg_fd, void * resp,
                                     int mx_resp_len, int noisy, int verbose);
+extern int sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+                                     int extended, int noisy, int verbose);
 
 /* Invokes a SCSI SET TARGET PORT GROUPS command. Return of 0 -> success,
  * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported,
@@ -272,6 +274,23 @@
                               uint64_t llba, void * data_out, int xfer_len,
                               int * offsetp, int noisy, int verbose);
 
+/* Invokes a SCSI RECEIVE COPY RESULTS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+extern int sg_ll_receive_copy_results(int sg_fd, int sa, int list_id,
+                                      void * resp, int mx_resp_len,
+                                      int noisy, int verbose);
+
+/* Invokes a SCSI EXTENDEd COPY command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Extended copy not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+extern int sg_ll_extended_copy(int sg_fd, void * resp, int mx_resp_len,
+                               int noisy, int verbose);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/sg_cmds_extra.c b/lib/sg_cmds_extra.c
index a69f7dc..791da15 100644
--- a/lib/sg_cmds_extra.c
+++ b/lib/sg_cmds_extra.c
@@ -56,6 +56,10 @@
 #define REASSIGN_BLKS_CMDLEN  6
 #define RECEIVE_DIAGNOSTICS_CMD   0x1c
 #define RECEIVE_DIAGNOSTICS_CMDLEN  6
+#define EXTENDED_COPY_CMD   0x83
+#define EXTENDED_COPY_CMDLEN 16
+#define RECEIVE_COPY_RESULTS_CMD   0x84
+#define RECEIVE_COPY_RESULTS_CMDLEN 16
 #define SEND_DIAGNOSTIC_CMD   0x1d
 #define SEND_DIAGNOSTIC_CMDLEN  6
 #define SERVICE_ACTION_IN_12_CMD 0xab
@@ -78,6 +82,10 @@
 #define READ_MEDIA_SERIAL_NUM_SA 0x1
 #define REPORT_IDENTIFYING_INFORMATION_SA 0x5
 #define REPORT_TGT_PRT_GRP_SA 0xa
+#define RECEIVE_COPY_RES_COPY_STATUS_SA 0x00
+#define RECEIVE_COPY_RES_RECEIVE_DATA_SA 0x01
+#define RECEIVE_COPY_RES_OPERATING_PARMS_SA 0x03
+#define RECEIVE_COPY_RES_FAILED_SEGMENT_DETAILS_SA 0x04
 #define SET_IDENTIFYING_INFORMATION_SA 0x6
 #define SET_TGT_PRT_GRP_SA 0xa
 #define WRITE_LONG_16_SA 0x11
@@ -164,13 +172,21 @@
     return ret;
 }
 
+int
+sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len,
+                         int noisy, int verbose)
+{
+    return sg_ll_report_tgt_prt_grp2(sg_fd, resp, mx_resp_len, 0, noisy,
+                                     verbose);
+}
+
 /* Invokes a SCSI REPORT TARGET PORT GROUPS command. Return of 0 -> success,
  * SG_LIB_CAT_INVALID_OP -> Report Target Port Groups not supported,
  * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_ABORTED_COMMAND,
  * SG_LIB_CAT_UNIT_ATTENTION, -1 -> other failure */
 int
-sg_ll_report_tgt_prt_grp(int sg_fd, void * resp, int mx_resp_len, int noisy,
-                         int verbose)
+sg_ll_report_tgt_prt_grp2(int sg_fd, void * resp, int mx_resp_len,
+                          int extended, int noisy, int verbose)
 {
     int k, res, ret, sense_cat;
     unsigned char rtpgCmdBlk[MAINTENANCE_IN_CMDLEN] =
@@ -179,6 +195,9 @@
     unsigned char sense_b[SENSE_BUFF_LEN];
     struct sg_pt_base * ptvp;
 
+    if (extended) {
+        rtpgCmdBlk[1] |= 0x20;
+    }
     rtpgCmdBlk[6] = (mx_resp_len >> 24) & 0xff;
     rtpgCmdBlk[7] = (mx_resp_len >> 16) & 0xff;
     rtpgCmdBlk[8] = (mx_resp_len >> 8) & 0xff;
@@ -2185,3 +2204,140 @@
     destruct_scsi_pt_obj(ptvp);
     return ret;
 }
+
+/* Invokes a SCSI RECEIVE COPY RESULTS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int
+sg_ll_receive_copy_results(int sg_fd, int sa, int list_id, void * resp,
+                           int mx_resp_len, int noisy, int verbose)
+{
+    int k, res, ret, sense_cat;
+    unsigned char rcvcopyresCmdBlk[RECEIVE_COPY_RESULTS_CMDLEN] =
+      {RECEIVE_COPY_RESULTS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    rcvcopyresCmdBlk[1] = (unsigned char)(sa & 0x1f);
+    rcvcopyresCmdBlk[2] = (unsigned char)(list_id);
+    rcvcopyresCmdBlk[10] = (unsigned char)((mx_resp_len >> 24) & 0xff);
+    rcvcopyresCmdBlk[11] = (unsigned char)((mx_resp_len >> 16) & 0xff);
+    rcvcopyresCmdBlk[12] = (unsigned char)((mx_resp_len >> 8) & 0xff);
+    rcvcopyresCmdBlk[13] = (unsigned char)(mx_resp_len & 0xff);
+
+    if (NULL == sg_warnings_strm)
+        sg_warnings_strm = stderr;
+    if (verbose) {
+        fprintf(sg_warnings_strm, "    Receive copy results cmd: ");
+        for (k = 0; k < RECEIVE_COPY_RESULTS_CMDLEN; ++k)
+            fprintf(sg_warnings_strm, "%02x ", rcvcopyresCmdBlk[k]);
+        fprintf(sg_warnings_strm, "\n");
+    }
+
+    ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp) {
+        fprintf(sg_warnings_strm, "receive copy results: out of "
+                "memory\n");
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, rcvcopyresCmdBlk, sizeof(rcvcopyresCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, "receive copy results", res,
+                               mx_resp_len, sense_b, noisy, verbose,
+                               &sense_cat);
+    if (-1 == ret)
+        ;
+    else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_NOT_READY:
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+/* Invokes a SCSI RECEIVE COPY RESULTS command. Return of 0 -> success,
+ * SG_LIB_CAT_INVALID_OP -> Receive copy results not supported,
+ * SG_LIB_CAT_ILLEGAL_REQ -> bad field in cdb, SG_LIB_CAT_UNIT_ATTENTION,
+ * SG_LIB_CAT_NOT_READY -> device not ready, SG_LIB_CAT_ABORTED_COMMAND,
+ * -1 -> other failure */
+int
+sg_ll_extended_copy(int sg_fd, void * resp,
+                    int mx_resp_len, int noisy, int verbose)
+{
+    int k, res, ret, sense_cat;
+    unsigned char xcopyCmdBlk[EXTENDED_COPY_CMDLEN] =
+      {EXTENDED_COPY_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+    unsigned char sense_b[SENSE_BUFF_LEN];
+    struct sg_pt_base * ptvp;
+
+    xcopyCmdBlk[10] = (unsigned char)((mx_resp_len >> 24) & 0xff);
+    xcopyCmdBlk[11] = (unsigned char)((mx_resp_len >> 16) & 0xff);
+    xcopyCmdBlk[12] = (unsigned char)((mx_resp_len >> 8) & 0xff);
+    xcopyCmdBlk[13] = (unsigned char)(mx_resp_len & 0xff);
+
+    if (NULL == sg_warnings_strm)
+        sg_warnings_strm = stderr;
+    if (verbose) {
+        fprintf(sg_warnings_strm, "    Extended copy cmd: ");
+        for (k = 0; k < EXTENDED_COPY_CMDLEN; ++k)
+            fprintf(sg_warnings_strm, "%02x ", xcopyCmdBlk[k]);
+        fprintf(sg_warnings_strm, "\n");
+    }
+
+    ptvp = construct_scsi_pt_obj();
+    if (NULL == ptvp) {
+        fprintf(sg_warnings_strm, "extended copy: out of memory\n");
+        return -1;
+    }
+    set_scsi_pt_cdb(ptvp, xcopyCmdBlk, sizeof(xcopyCmdBlk));
+    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    set_scsi_pt_data_out(ptvp, (unsigned char *)resp, mx_resp_len);
+    res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
+    ret = sg_cmds_process_resp(ptvp, "extended copy", res,
+                               mx_resp_len, sense_b, noisy, verbose,
+                               &sense_cat);
+    if (-1 == ret)
+        ;
+    else if (-2 == ret) {
+        switch (sense_cat) {
+        case SG_LIB_CAT_NOT_READY:
+        case SG_LIB_CAT_INVALID_OP:
+        case SG_LIB_CAT_ILLEGAL_REQ:
+        case SG_LIB_CAT_UNIT_ATTENTION:
+        case SG_LIB_CAT_ABORTED_COMMAND:
+            ret = sense_cat;
+            break;
+        case SG_LIB_CAT_RECOVERED:
+        case SG_LIB_CAT_NO_SENSE:
+            ret = 0;
+            break;
+        default:
+            ret = -1;
+            break;
+        }
+    } else
+        ret = 0;
+    destruct_scsi_pt_obj(ptvp);
+    return ret;
+}
+
+
diff --git a/src/Makefile.am b/src/Makefile.am
index bf4425c..22ab714 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,7 +17,7 @@
 	sg_sat_identify sg_sat_phy_event sg_sat_set_features sg_scan \
 	sg_senddiag sg_ses sg_start sg_stpg sg_sync sg_test_rwbuf sg_turs \
 	sg_unmap sg_verify sg_vpd sg_write_buffer sg_write_long \
-	sg_write_same sg_wr_mode
+	sg_write_same sg_wr_mode sg_xcopy sg_copy_results
 
 distclean-local:
 	rm -f sg_scan.c
@@ -277,3 +277,8 @@
 sg_wr_mode_SOURCES = sg_wr_mode.c
 sg_wr_mode_LDADD = ../lib/libsgutils2.la @os_libs@
 
+sg_xcopy_SOURCES = sg_xcopy.c
+sg_xcopy_LDADD = ../lib/libsgutils2.la @os_libs@
+
+sg_copy_results_SOURCES = sg_copy_results.c
+sg_copy_results_LDADD = ../lib/libsgutils2.la @os_libs@
diff --git a/src/Makefile.in b/src/Makefile.in
index 7ef8b72..8c57843 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -252,7 +252,9 @@
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_write_buffer$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_write_long$(EXEEXT) \
 @OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_write_same$(EXEEXT) \
-@OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_wr_mode$(EXEEXT)
+@OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_wr_mode$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_xcopy$(EXEEXT) \
+@OS_FREEBSD_FALSE@@OS_LINUX_TRUE@	sg_copy_results$(EXEEXT)
 @OS_FREEBSD_TRUE@bin_PROGRAMS = sg_decode_sense$(EXEEXT) \
 @OS_FREEBSD_TRUE@	sg_format$(EXEEXT) sg_get_config$(EXEEXT) \
 @OS_FREEBSD_TRUE@	sg_get_lba_status$(EXEEXT) sg_ident$(EXEEXT) \
@@ -288,6 +290,9 @@
 CONFIG_CLEAN_VPATH_FILES =
 am__installdirs = "$(DESTDIR)$(bindir)"
 PROGRAMS = $(bin_PROGRAMS)
+am_sg_copy_results_OBJECTS = sg_copy_results.$(OBJEXT)
+sg_copy_results_OBJECTS = $(am_sg_copy_results_OBJECTS)
+sg_copy_results_DEPENDENCIES = ../lib/libsgutils2.la
 am_sg_dd_OBJECTS = sg_dd.$(OBJEXT)
 sg_dd_OBJECTS = $(am_sg_dd_OBJECTS)
 sg_dd_DEPENDENCIES = ../lib/libsgutils2.la
@@ -438,6 +443,9 @@
 am_sg_write_same_OBJECTS = sg_write_same.$(OBJEXT)
 sg_write_same_OBJECTS = $(am_sg_write_same_OBJECTS)
 sg_write_same_DEPENDENCIES = ../lib/libsgutils2.la
+am_sg_xcopy_OBJECTS = sg_xcopy.$(OBJEXT)
+sg_xcopy_OBJECTS = $(am_sg_xcopy_OBJECTS)
+sg_xcopy_DEPENDENCIES = ../lib/libsgutils2.la
 am_sginfo_OBJECTS = sginfo.$(OBJEXT)
 sginfo_OBJECTS = $(am_sginfo_OBJECTS)
 sginfo_DEPENDENCIES = ../lib/libsgutils2.la
@@ -460,14 +468,15 @@
 LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \
 	--mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \
 	$(LDFLAGS) -o $@
-SOURCES = $(sg_dd_SOURCES) $(sg_decode_sense_SOURCES) \
-	$(sg_emc_trespass_SOURCES) $(sg_format_SOURCES) \
-	$(sg_get_config_SOURCES) $(sg_get_lba_status_SOURCES) \
-	$(sg_ident_SOURCES) $(sg_inq_SOURCES) $(sg_logs_SOURCES) \
-	$(sg_luns_SOURCES) $(sg_map_SOURCES) $(sg_map26_SOURCES) \
-	$(sg_modes_SOURCES) $(sg_opcodes_SOURCES) \
-	$(sg_persist_SOURCES) $(sg_prevent_SOURCES) $(sg_raw_SOURCES) \
-	$(sg_rbuf_SOURCES) $(sg_rdac_SOURCES) $(sg_read_SOURCES) \
+SOURCES = $(sg_copy_results_SOURCES) $(sg_dd_SOURCES) \
+	$(sg_decode_sense_SOURCES) $(sg_emc_trespass_SOURCES) \
+	$(sg_format_SOURCES) $(sg_get_config_SOURCES) \
+	$(sg_get_lba_status_SOURCES) $(sg_ident_SOURCES) \
+	$(sg_inq_SOURCES) $(sg_logs_SOURCES) $(sg_luns_SOURCES) \
+	$(sg_map_SOURCES) $(sg_map26_SOURCES) $(sg_modes_SOURCES) \
+	$(sg_opcodes_SOURCES) $(sg_persist_SOURCES) \
+	$(sg_prevent_SOURCES) $(sg_raw_SOURCES) $(sg_rbuf_SOURCES) \
+	$(sg_rdac_SOURCES) $(sg_read_SOURCES) \
 	$(sg_read_block_limits_SOURCES) $(sg_read_buffer_SOURCES) \
 	$(sg_read_long_SOURCES) $(sg_readcap_SOURCES) \
 	$(sg_reassign_SOURCES) $(sg_referrals_SOURCES) \
@@ -480,16 +489,17 @@
 	$(sg_turs_SOURCES) $(sg_unmap_SOURCES) $(sg_verify_SOURCES) \
 	$(sg_vpd_SOURCES) $(sg_wr_mode_SOURCES) \
 	$(sg_write_buffer_SOURCES) $(sg_write_long_SOURCES) \
-	$(sg_write_same_SOURCES) $(sginfo_SOURCES) $(sgm_dd_SOURCES) \
-	$(sgp_dd_SOURCES)
-DIST_SOURCES = $(sg_dd_SOURCES) $(sg_decode_sense_SOURCES) \
-	$(sg_emc_trespass_SOURCES) $(sg_format_SOURCES) \
-	$(sg_get_config_SOURCES) $(sg_get_lba_status_SOURCES) \
-	$(sg_ident_SOURCES) $(sg_inq_SOURCES) $(sg_logs_SOURCES) \
-	$(sg_luns_SOURCES) $(sg_map_SOURCES) $(sg_map26_SOURCES) \
-	$(sg_modes_SOURCES) $(sg_opcodes_SOURCES) \
-	$(sg_persist_SOURCES) $(sg_prevent_SOURCES) $(sg_raw_SOURCES) \
-	$(sg_rbuf_SOURCES) $(sg_rdac_SOURCES) $(sg_read_SOURCES) \
+	$(sg_write_same_SOURCES) $(sg_xcopy_SOURCES) $(sginfo_SOURCES) \
+	$(sgm_dd_SOURCES) $(sgp_dd_SOURCES)
+DIST_SOURCES = $(sg_copy_results_SOURCES) $(sg_dd_SOURCES) \
+	$(sg_decode_sense_SOURCES) $(sg_emc_trespass_SOURCES) \
+	$(sg_format_SOURCES) $(sg_get_config_SOURCES) \
+	$(sg_get_lba_status_SOURCES) $(sg_ident_SOURCES) \
+	$(sg_inq_SOURCES) $(sg_logs_SOURCES) $(sg_luns_SOURCES) \
+	$(sg_map_SOURCES) $(sg_map26_SOURCES) $(sg_modes_SOURCES) \
+	$(sg_opcodes_SOURCES) $(sg_persist_SOURCES) \
+	$(sg_prevent_SOURCES) $(sg_raw_SOURCES) $(sg_rbuf_SOURCES) \
+	$(sg_rdac_SOURCES) $(sg_read_SOURCES) \
 	$(sg_read_block_limits_SOURCES) $(sg_read_buffer_SOURCES) \
 	$(sg_read_long_SOURCES) $(sg_readcap_SOURCES) \
 	$(sg_reassign_SOURCES) $(sg_referrals_SOURCES) \
@@ -502,8 +512,8 @@
 	$(sg_turs_SOURCES) $(sg_unmap_SOURCES) $(sg_verify_SOURCES) \
 	$(sg_vpd_SOURCES) $(sg_wr_mode_SOURCES) \
 	$(sg_write_buffer_SOURCES) $(sg_write_long_SOURCES) \
-	$(sg_write_same_SOURCES) $(sginfo_SOURCES) $(sgm_dd_SOURCES) \
-	$(sgp_dd_SOURCES)
+	$(sg_write_same_SOURCES) $(sg_xcopy_SOURCES) $(sginfo_SOURCES) \
+	$(sgm_dd_SOURCES) $(sgp_dd_SOURCES)
 ETAGS = etags
 CTAGS = ctags
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
@@ -731,6 +741,10 @@
 sg_write_same_LDADD = ../lib/libsgutils2.la @os_libs@
 sg_wr_mode_SOURCES = sg_wr_mode.c
 sg_wr_mode_LDADD = ../lib/libsgutils2.la @os_libs@
+sg_xcopy_SOURCES = sg_xcopy.c
+sg_xcopy_LDADD = ../lib/libsgutils2.la @os_libs@
+sg_copy_results_SOURCES = sg_copy_results.c
+sg_copy_results_LDADD = ../lib/libsgutils2.la @os_libs@
 all: all-am
 
 .SUFFIXES:
@@ -808,6 +822,9 @@
 	list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
 	echo " rm -f" $$list; \
 	rm -f $$list
+sg_copy_results$(EXEEXT): $(sg_copy_results_OBJECTS) $(sg_copy_results_DEPENDENCIES) 
+	@rm -f sg_copy_results$(EXEEXT)
+	$(LINK) $(sg_copy_results_OBJECTS) $(sg_copy_results_LDADD) $(LIBS)
 sg_dd$(EXEEXT): $(sg_dd_OBJECTS) $(sg_dd_DEPENDENCIES) 
 	@rm -f sg_dd$(EXEEXT)
 	$(LINK) $(sg_dd_OBJECTS) $(sg_dd_LDADD) $(LIBS)
@@ -958,6 +975,9 @@
 sg_write_same$(EXEEXT): $(sg_write_same_OBJECTS) $(sg_write_same_DEPENDENCIES) 
 	@rm -f sg_write_same$(EXEEXT)
 	$(LINK) $(sg_write_same_OBJECTS) $(sg_write_same_LDADD) $(LIBS)
+sg_xcopy$(EXEEXT): $(sg_xcopy_OBJECTS) $(sg_xcopy_DEPENDENCIES) 
+	@rm -f sg_xcopy$(EXEEXT)
+	$(LINK) $(sg_xcopy_OBJECTS) $(sg_xcopy_LDADD) $(LIBS)
 sginfo$(EXEEXT): $(sginfo_OBJECTS) $(sginfo_DEPENDENCIES) 
 	@rm -f sginfo$(EXEEXT)
 	$(LINK) $(sginfo_OBJECTS) $(sginfo_LDADD) $(LIBS)
@@ -974,6 +994,7 @@
 distclean-compile:
 	-rm -f *.tab.c
 
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_copy_results.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_dd.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_decode_sense.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_emc_trespass.Po@am__quote@
@@ -1026,6 +1047,7 @@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_buffer.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_long.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_write_same.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_xcopy.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sginfo.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgm_dd.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sgp_dd.Po@am__quote@
diff --git a/src/sg_copy_results.c b/src/sg_copy_results.c
new file mode 100644
index 0000000..02e205f
--- /dev/null
+++ b/src/sg_copy_results.c
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2011 Hannes Reinecke, SUSE Labs
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ */
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+
+/* A utility program for the Linux OS SCSI subsystem.
+   *  Copyright (C) 2004-2010 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.
+
+   This program issues the SCSI command RECEIVE COPY RESULTS to a given
+   SCSI device.
+   It sends the command with the service action passed as the sa argument,
+   and the optional list identifier passed as the list_id argument.
+*/
+
+static char * version_str = "1.0 20110608";
+
+
+#define MAX_XFER_LEN 10000
+
+/* #define SG_DEBUG */
+
+#define ME "sg_copy_results: "
+
+#define EBUFF_SZ 256
+
+struct descriptor_type {
+    int code;
+    char desc[];
+};
+
+struct descriptor_type target_descriptor_codes [] = {
+    { 0xe0, "Fibre Channel N_Port_Name"},
+    { 0xe1, "Fibre Channel N_port_ID"},
+    { 0xe2, "Fibre Channesl N_port_ID with N_Port_Name checking"},
+    { 0xe3, "Parallel Interface T_L" },
+    { 0xe4, "Identification descriptor" },
+    { 0xe5, "IPv4" },
+    { 0xe6, "Alias" },
+    { 0xe7, "RDMA" },
+    { 0xe8, "IEEE 1395 EUI-64" },
+    { 0xe9, "SAS Serial SCSI Protocol" },
+    { 0xea, "IPv6" },
+    { 0xeb, "IP Copy Service" }
+};
+
+struct descriptor_type segment_descriptor_codes [] = {
+    { 0x00, "Copy from block device to stream device" },
+    { 0x01, "Copy from stream device to block device" },
+    { 0x02, "Copy from block device to block device" },
+    { 0x03, "Copy from stream device to stream device" },
+    { 0x04, "Copy inline data to stream device" },
+    { 0x05, "Copy embedded data to stream device" },
+    { 0x06, "Read from stream device and discard" },
+    { 0x07, "Verify block or stream device operation" },
+    { 0x08, "Copy block device with offset to stream device" },
+    { 0x09, "Copy stream device to block device with offset" },
+    { 0x0A, "Copy block device with offset to block device with offset" },
+    { 0x0B, "Copy from block device to stream device "
+      "and hold a copy of processed data for the application client" },
+    { 0x0C, "Copy from stream device to block device "
+      "and hold a copy of processed data for the application client" },
+    { 0x0D, "Copy from block device to block device "
+      "and hold a copy of processed data for the application client" },
+    { 0x0E, "Copy from stream device to stream device "
+      "and hold a copy of processed data for the application client" },
+    { 0x0F, "Read from stream device "
+      "and hold a copy of processed data for the application client" },
+    { 0x10, "Write filemarks to sequential-access device" },
+    { 0x11, "Space records or filemarks on sequential-access device" },
+    { 0x12, "Locate on sequential-access device" },
+    { 0x13, "Image copy from sequential-access device to sequential-access device" },
+    { 0x14, "Register persistent reservation key" },
+    { 0x15, "Third party persistent reservations source I_T nexus" }
+};
+
+static void
+scsi_copy_status(unsigned char *rcBuff, unsigned int rcBuffLen)
+{
+    unsigned int len;
+
+    if (rcBuffLen < 4) {
+        fprintf(stderr, "  <<not enough data to procedd report>>\n");
+        return;
+    }
+    len = (rcBuff[0] << 24) | (rcBuff[1] << 16) | (rcBuff[2] << 8) | rcBuff[3];
+    if (len > rcBuffLen) {
+        fprintf(stderr, "  <<report too long for internal buffer,"
+                " output truncated\n");
+    }
+    printf("Receive copy results (copy status):\n");
+    printf("    Held data discarded: %s\n", rcBuff[4] & 0x80 ? "Yes":"No");
+    printf("    Copy manager status: ");
+    switch (rcBuff[4] & 0x7f) {
+    case 0:
+        printf("Operation in progress\n");
+        break;
+    case 1:
+        printf("Operation completed without errors\n");
+        break;
+    case 2:
+        printf("Operation completed with errors\n");
+        break;
+    default:
+        printf("Unknown/Reserved\n");
+        break;
+    }
+    printf("    Segments processed: %u\n", (rcBuff[5] << 8) | rcBuff[6]);
+    printf("    Transfer count units: %u\n", rcBuff[7]);
+    printf("    Transfer count: %u\n",
+           rcBuff[8] << 24 | rcBuff[9] << 16 | rcBuff[10] << 8 | rcBuff[11]);
+}
+
+static void
+scsi_operating_parameters(unsigned char *rcBuff, unsigned int rcBuffLen)
+{
+    unsigned int len, n;
+
+    len = (rcBuff[0] << 24) | (rcBuff[1] << 16) | (rcBuff[2] << 8) | rcBuff[3];
+    if (len > rcBuffLen) {
+        fprintf(stderr, "  <<report too long for internal buffer,"
+                " output truncated\n");
+    }
+    printf("Receive copy results (report operating parameters):\n");
+    printf("    Supports no list identifier: %s\n",
+           rcBuff[4] & 1 ? "yes" : "no");
+    n = (rcBuff[8] << 8) | rcBuff[9];
+    printf("    Maximum target descriptor count: %u\n", n);
+    n = (rcBuff[10] << 8) | rcBuff[11];
+    printf("    Maximum segment descriptor count: %u\n", n);
+    n = (rcBuff[12] << 24) | (rcBuff[13] << 16) |
+        (rcBuff[14] << 8) | rcBuff[15];
+    printf("    Maximum descriptor list length: %u bytes\n", n);
+    n = (rcBuff[16] << 24) | (rcBuff[17] << 16) |
+        (rcBuff[18] << 8) | rcBuff[19];
+    printf("    Maximum segment length: %u bytes\n", n);
+    n = (rcBuff[20] << 24) | (rcBuff[21] << 16) |
+        (rcBuff[22] << 8) | rcBuff[23];
+    if (n == 0) {
+        printf("    Inline data not supported\n");
+    } else {
+        printf("    Maximum inline data length: %u bytes\n", n);
+    }
+    n = (rcBuff[24] << 24) | (rcBuff[25] << 16) |
+        (rcBuff[26] << 8) | rcBuff[27];
+    printf("    Held data limit: %u bytes\n", n);
+    n = (rcBuff[28] << 24) | (rcBuff[29] << 16) |
+        (rcBuff[30] << 8) | rcBuff[31];
+    printf("    Maximum stream device transfer size: %u bytes\n", n);
+    n = (rcBuff[34] << 8) | rcBuff[35];
+    printf("    Total concurrent copies: %u\n", n);
+    printf("    Maximum concurrent copies: %u\n", rcBuff[36]);
+    printf("    Data segment granularity: %lu bytes\n",
+           (unsigned long)(1 << rcBuff[37]));
+    printf("    Inline data granularity: %lu bytes\n",
+           (unsigned long)(1 << rcBuff[38]));
+    printf("    Held data granularity: %lu bytes\n",
+           (unsigned long)(1 << rcBuff[39]));
+
+    printf("    Implemented descriptor list:\n        ");
+    for (n = 0; n < rcBuff[43]; n++) {
+        int code = rcBuff[44 + n];
+
+        if (code < 0x16) {
+            printf("Segment descriptor 0x%02x: %s\n",
+                   code, segment_descriptor_codes[code].desc);
+        } else if (code < 0xc0) {
+            printf("Segment descriptor 0x%02x: Reserved\n", code);
+        } else if (code < 0xe0) {
+            printf("Vendor specific descriptor 0x%02x\n", code);
+        } else if (code < 0xec) {
+            printf("Target descriptor 0x%02x: %s\n",
+                   code, target_descriptor_codes[code - 0xe0].desc);
+        } else {
+            printf("Target descriptor 0x%02x: Reserved\n", code);
+        }
+    }
+    printf("\n");
+}
+
+static struct option long_options[] = {
+        {"failed", 0, 0, 'f'},
+        {"help", 0, 0, 'h'},
+        {"hex", 0, 0, 'H'},
+        {"list_id", 1, 0, 'l'},
+        {"params", 0, 0, 'p'},
+        {"receive", 0, 0, 'r'},
+        {"status", 0, 0, 's'},
+        {"verbose", 0, 0, 'v'},
+        {"version", 0, 0, 'V'},
+        {"xfer_len", 1, 0, 'x'},
+        {0, 0, 0, 0},
+};
+
+static void
+usage()
+{
+  fprintf(stderr, "Usage: "
+          "sg_copy_results [--status|--receive|--params|--failed] [--help] "
+          "[--list_id=ID]\n"
+          "                     [--verbose] [--version] [--hex] DEVICE\n"
+          "  where:\n"
+          "    --status|-s          use COPY STATUS service action\n"
+          "    --receive|-r         use RECEIVE DATA service action\n"
+          "    --params|-p          use OPERATING PARAMETERS service action\n"
+          "    --failed|-f          use FAILD SEGMENT DETAILS service action\n"
+          "    --help|-h            print out usage message\n"
+          "    --list_id=ID|-l ID   list identifier "
+          "(default: 0)\n"
+          "    --verbose|-v         increase verbosity\n"
+          "    --version|-V         print version string then exit\n"
+          "    --hex|-H             print out response buffer in hex\n"
+          "    --xfer_len=BTL|-x BTL    byte transfer length (< 10000) "
+          "(default:\n"
+          "                             520 bytes)\n\n"
+          "Performs a SCSI RECEIVE COPY RESULTS command. Returns the response "
+          "as specified by the service action parameters.\n"
+          );
+}
+
+int
+main(int argc, char * argv[])
+{
+    int sg_fd, res, c;
+    unsigned char * cpResultBuff = NULL;
+    int xfer_len = 520;
+    int sa = 3;
+    int list_id = 0;
+    int do_hex = 0;
+    int verbose = 0;
+    const char * device_name = NULL;
+    char file_name[256];
+    int ret = 1;
+
+    memset(file_name, 0, sizeof file_name);
+    while (1) {
+        int option_index = 0;
+
+        c = getopt_long(argc, argv, "fhHl:prsvVx:", long_options,
+                        &option_index);
+        if (c == -1)
+            break;
+
+        switch (c) {
+        case 'f':
+            sa = 4;
+            break;
+        case 'H':
+            do_hex = 1;
+            break;
+        case 'h':
+        case '?':
+            usage();
+            return 0;
+        case 'l':
+            list_id = sg_get_num(optarg);
+            if (-1 == list_id) {
+                fprintf(stderr, "bad argument to '--list_id'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        case 'p':
+            sa = 3;
+            break;
+        case 'r':
+            sa = 1;
+            break;
+        case 's':
+            sa = 0;
+            break;
+        case 'v':
+            ++verbose;
+            break;
+        case 'V':
+            fprintf(stderr, ME "version: %s\n", version_str);
+            return 0;
+        case 'x':
+            xfer_len = sg_get_num(optarg);
+            if (-1 == xfer_len) {
+                fprintf(stderr, "bad argument to '--xfer_len'\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            break;
+        default:
+            fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
+            usage();
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+    if (optind < argc) {
+        if (NULL == device_name) {
+            device_name = argv[optind];
+            ++optind;
+        }
+        if (optind < argc) {
+            for (; optind < argc; ++optind)
+                fprintf(stderr, "Unexpected extra argument: %s\n",
+                        argv[optind]);
+            usage();
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+
+    if (NULL == device_name) {
+        fprintf(stderr, "missing device name!\n");
+        usage();
+        return SG_LIB_SYNTAX_ERROR;
+    }
+    if (xfer_len >= MAX_XFER_LEN) {
+        fprintf(stderr, "xfer_len (%d) is out of range ( < %d)\n",
+                xfer_len, MAX_XFER_LEN);
+        usage();
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+    if (NULL == (cpResultBuff = malloc(xfer_len))) {
+            fprintf(stderr, ME "out of memory\n");
+            return SG_LIB_FILE_ERROR;
+    }
+    memset(cpResultBuff, 0x00, xfer_len);
+
+    sg_fd = sg_cmds_open_device(device_name, 0 /* rw */, verbose);
+    if (sg_fd < 0) {
+        fprintf(stderr, ME "open error: %s: %s\n", device_name,
+                safe_strerror(-sg_fd));
+        return SG_LIB_FILE_ERROR;
+    }
+
+    if (verbose)
+        fprintf(stderr, ME "issue receive copy results to device %s\n"
+                "\t\txfer_len= %d (0x%x), sa=%d, list_id=%d\n",
+                device_name, xfer_len, xfer_len, sa, list_id);
+
+    res = sg_ll_receive_copy_results(sg_fd, sa, list_id, cpResultBuff,
+                                     xfer_len, 0, verbose);
+    ret = res;
+    switch (res) {
+    case 0:
+        break;
+    case SG_LIB_CAT_NOT_READY:
+        fprintf(stderr, "  SCSI RECEIVE COPY RESULTS failed, "
+                "device not ready\n");
+        break;
+    case SG_LIB_CAT_UNIT_ATTENTION:
+        fprintf(stderr, "  SCSI RECEIVE COPY RESULTS failed, "
+                "unit attention\n");
+        break;
+    case SG_LIB_CAT_ABORTED_COMMAND:
+        fprintf(stderr, "  SCSI RECEIVE COPY RESULTS failed, "
+                "aborted command\n");
+        break;
+    case SG_LIB_CAT_INVALID_OP:
+        fprintf(stderr, "  SCSI RECEIVE COPY RESULTS command not supported\n");
+        break;
+    case SG_LIB_CAT_ILLEGAL_REQ:
+        fprintf(stderr, "  SCSI RECEIVE COPY RESULTS failed, "
+                "bad field in cdb\n");
+        break;
+    default:
+        fprintf(stderr, "  SCSI RECEIVE COPY RESULTS command error %d\n", res);
+        break;
+    }
+    if (res != 0)
+        goto finish;
+    if (1 == do_hex) {
+        dStrHex((const char *)cpResultBuff, xfer_len, 1);
+        res = 0;
+        goto finish;
+    }
+    switch (sa) {
+    case 3: /* Operating parameters */
+        scsi_operating_parameters(cpResultBuff, xfer_len);
+        res = 0;
+        break;
+    case 0: /* Copy status */
+        scsi_copy_status(cpResultBuff, xfer_len);
+        res = 0;
+        break;
+    default:
+        dStrHex((const char *)cpResultBuff, xfer_len, 1);
+        res = 0;
+        break;
+    }
+
+finish:
+    free(cpResultBuff);
+    res = sg_cmds_close_device(sg_fd);
+    if (res < 0) {
+        fprintf(stderr, ME "close error: %s\n", safe_strerror(-res));
+        if (0 == ret)
+            return SG_LIB_FILE_ERROR;
+    }
+    return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
+}
diff --git a/src/sg_rtpg.c b/src/sg_rtpg.c
index d0d1403..f2aaca0 100644
--- a/src/sg_rtpg.c
+++ b/src/sg_rtpg.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2011 Christophe Varoqui and Douglas Gilbert.
+ * Copyright (c) 2004-2012 Christophe Varoqui and Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -26,7 +26,7 @@
  * to the given SCSI device.
  */
 
-static char * version_str = "1.15 20111014";
+static char * version_str = "1.16 20120322";
 
 #define REPORT_TGT_GRP_BUFF_LEN 1024
 
@@ -42,29 +42,9 @@
 #define STATUS_CODE_CHANGED_BY_SET 0x1
 #define STATUS_CODE_CHANGED_BY_IMPLICIT 0x2
 
-/* <<<<<<<<<<<<<<< start of test code */
-/* #define TEST_CODE */
-
-#ifdef TEST_CODE
-
-#warning "<<<< TEST_CODE response compiled in >>>>"
-
-unsigned char dummy_resp[32] = {
-        0, 0, 0, 28,
-
-        0x80, 0x3, 0, 1, 0, 2, 0, 2,
-        0, 0, 0, 1,
-        0, 0, 0, 2,
-
-        0x1, 0x3, 0, 2, 0, 0, 0, 1,
-        0, 0, 0, 3,
-};
-
-#endif
-/* <<<<<<<<<<<<<<< end of test code */
-
 static struct option long_options[] = {
         {"decode", 0, 0, 'd'},
+        {"extended", 0, 0, 'e'},
         {"help", 0, 0, 'h'},
         {"hex", 0, 0, 'H'},
         {"raw", 0, 0, 'r'},
@@ -76,11 +56,12 @@
 static void usage()
 {
     fprintf(stderr, "Usage: "
-          "sg_rtpg   [--decode] [--help] [--hex] [--raw] [--verbose] "
-          "[--version]\n"
+          "sg_rtpg   [--decode] [--extended] [--help] [--hex] [--raw] "
+          "[--verbose] [--version]\n"
           "                 DEVICE\n"
           "  where:\n"
           "    --decode|-d        decode status and asym. access state\n"
+          "    --extended|-e      use extended header parameter data format\n"
           "    --help|-h          print out usage message\n"
           "    --hex|-H           print out response in hex\n"
           "    --raw|-r           output response in binary to stdout\n"
@@ -158,13 +139,14 @@
     int hex = 0;
     int raw = 0;
     int verbose = 0;
+    int extended = 0;
     const char * device_name = NULL;
     int ret = 0;
 
     while (1) {
         int option_index = 0;
 
-        c = getopt_long(argc, argv, "dhHrvV", long_options,
+        c = getopt_long(argc, argv, "dehHrvV", long_options,
                         &option_index);
         if (c == -1)
             break;
@@ -173,6 +155,9 @@
         case 'd':
             decode = 1;
             break;
+        case 'e':
+             extended = 1;
+             break;
         case 'h':
         case '?':
             usage();
@@ -231,13 +216,9 @@
     memset(reportTgtGrpBuff, 0x0, sizeof(reportTgtGrpBuff));
     /* trunc = 0; */
 
-#ifndef TEST_CODE
-    res = sg_ll_report_tgt_prt_grp(sg_fd, reportTgtGrpBuff,
-                            sizeof(reportTgtGrpBuff), 1, verbose);
-#else
-    memcpy(reportTgtGrpBuff, dummy_resp, sizeof(dummy_resp));
-    res = 0;
-#endif
+    res = sg_ll_report_tgt_prt_grp2(sg_fd, reportTgtGrpBuff,
+                                    sizeof(reportTgtGrpBuff),
+                                    extended, 1, verbose);
     ret = res;
     if (0 == res) {
         report_len = (reportTgtGrpBuff[0] << 24) +
@@ -263,8 +244,16 @@
             goto err_out;
         }
         printf("Report target port groups:\n");
-        for (k = 4, ucp = reportTgtGrpBuff + 4; k < report_len;
-             k += off, ucp += off) {
+        ucp = reportTgtGrpBuff + 4;
+        if (extended) {
+             if (!(ucp[0] & 0x10)) {
+                  fprintf(stderr, "   <<invalid extended header format\n");
+                  goto err_out;
+             }
+             printf("  Implicit transition time: %d\n", ucp[1]);
+             ucp += 4;;
+        }
+        for (k = 4; k < report_len; k += off, ucp += off) {
 
             printf("  target port group id : 0x%x , Pref=%d\n",
                    (ucp[2] << 8) + ucp[3], !!(ucp[0] & 0x80));
diff --git a/src/sg_stpg.c b/src/sg_stpg.c
index 984bb0e..6dcd194 100644
--- a/src/sg_stpg.c
+++ b/src/sg_stpg.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2011 Hannes Reinecke, Christophe Varoqui and Douglas Gilbert.
+ * Copyright (c) 2004-2012 Hannes Reinecke, Christophe Varoqui and Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -24,7 +24,7 @@
  * to the given SCSI device.
  */
 
-static char * version_str = "1.3 20111014";
+static char * version_str = "1.4 20120322";
 
 #define TGT_GRP_BUFF_LEN 1024
 #define MX_ALLOC_LEN (0xc000 + 0x80)
@@ -571,8 +571,9 @@
         memset(reportTgtGrpBuff, 0x0, sizeof(reportTgtGrpBuff));
         /* trunc = 0; */
 
-        res = sg_ll_report_tgt_prt_grp(sg_fd, reportTgtGrpBuff,
-                                sizeof(reportTgtGrpBuff), 1, verbose);
+        res = sg_ll_report_tgt_prt_grp2(sg_fd, reportTgtGrpBuff,
+                                        sizeof(reportTgtGrpBuff), 0, 1,
+                                        verbose);
         ret = res;
         if (0 == res) {
             report_len = (reportTgtGrpBuff[0] << 24) +
diff --git a/src/sg_xcopy.c b/src/sg_xcopy.c
new file mode 100644
index 0000000..2c154ab
--- /dev/null
+++ b/src/sg_xcopy.c
@@ -0,0 +1,1536 @@
+#define _XOPEN_SOURCE 600
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE     /* resolves u_char typedef in scsi/scsi.h [lk 2.4] */
+#endif
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <linux/major.h>
+#include <linux/fs.h>   /* <sys/mount.h> */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "sg_lib.h"
+#include "sg_cmds_basic.h"
+#include "sg_cmds_extra.h"
+#include "sg_io_linux.h"
+
+/* A utility program for copying files. Similar to 'dd' but using
+ * the 'Extended Copy' command.
+ *
+ *  Copyright (c) 2011-2012 Hannes Reinecke, SUSE Labs
+ *
+ *  Largerly taken from 'sg_dd', which has the
+ *
+ *  Copyright (C) 1999 - 2010 D. Gilbert and P. Allworth
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+
+   This program is a specialisation of the Unix "dd" command in which
+   either the input or the output file is a scsi generic device, raw
+   device, a block device or a normal file. The block size ('bs') is
+   assumed to be 512 if not given. This program complains if 'ibs' or
+   'obs' are given with a value that differs from 'bs' (or the default 512).
+   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) is transferred to or from the sg device in a single SCSI
+   command.
+
+   This version is designed for the linux kernel 2.4 and 2.6 series.
+*/
+
+static char * version_str = "0.2 20120322";
+
+#define ME "sg_xcp: "
+
+#define SG_DEBUG
+
+#define STR_SZ 1024
+#define INOUTF_SZ 512
+#define EBUFF_SZ 512
+
+#define DEF_BLOCK_SIZE 512
+#define DEF_BLOCKS_PER_TRANSFER 128
+#define DEF_BLOCKS_PER_2048TRANSFER 32
+
+#define DEF_MODE_RESP_LEN 252
+#define RW_ERR_RECOVERY_MP 1
+#define CACHING_MP 8
+#define CONTROL_MP 0xa
+
+#define SENSE_BUFF_LEN 32       /* Arbitrary, could be larger */
+#define READ_CAP_REPLY_LEN 8
+#define RCAP16_REPLY_LEN 32
+#define EXTENDED_COPY_OPCODE 0x83
+#define READ_LONG_OPCODE 0x3E
+#define READ_LONG_CMD_LEN 10
+#define READ_LONG_DEF_BLK_INC 8
+
+#define DEF_TIMEOUT 60000       /* 60,000 millisecs == 60 seconds */
+
+#ifndef RAW_MAJOR
+#define RAW_MAJOR 255   /*unlikey value */
+#endif
+
+#define SG_LIB_FLOCK_ERR 90
+
+#define FT_OTHER 1              /* filetype is probably normal */
+#define FT_SG 2                 /* filetype is sg char device or supports
+                                   SG_IO ioctl */
+#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 block device */
+#define FT_FIFO 64              /* filetype is a fifo (name pipe) */
+#define FT_ERROR 128            /* couldn't "stat" file */
+
+#define TD_FC_WWPN 1
+#define TD_FC_PORT 2
+#define TD_FC_WWPN_AND_PORT 4
+#define TD_SPI 8
+#define TD_VPD 16
+#define TD_IPV4 32
+#define TD_ALIAS 64
+#define TD_RDMA 128
+#define TD_FW 256
+#define TD_SAS 512
+
+#define DEV_NULL_MINOR_NUM 3
+
+#define MIN_RESERVED_SIZE 8192
+
+#define MAX_UNIT_ATTENTIONS 10
+#define MAX_ABORTED_CMDS 256
+
+static int64_t dd_count = -1;
+static int64_t in_full = 0;
+static int in_partial = 0;
+static int64_t out_full = 0;
+static int out_partial = 0;
+static int recovered_errs = 0;
+static int unrecovered_errs = 0;
+static int num_retries = 0;
+
+static int do_time = 0;
+static int verbose = 0;
+static int start_tm_valid = 0;
+static struct timeval start_tm;
+static int blk_sz = 0;
+static int priority = 1;
+static int list_id_usage = -1;
+
+struct xcopy_fp_t {
+    char fname[INOUTF_SZ];
+    dev_t devno;
+    int sg_type;
+    int sg_fd;
+    int append;
+    int excl;
+    int flock;
+    int cat;     /* Destination count    */
+    int dc;      /* Descriptor type code */
+    int pdt;     /* Peripheral device type */
+    int retries;
+};
+
+static struct xcopy_fp_t ifp;
+static struct xcopy_fp_t ofp;
+
+static void calc_duration_throughput(int contin);
+
+
+static void
+install_handler(int sig_num, void (*sig_handler) (int sig))
+{
+    struct sigaction sigact;
+    sigaction (sig_num, NULL, &sigact);
+    if (sigact.sa_handler != SIG_IGN)
+    {
+        sigact.sa_handler = sig_handler;
+        sigemptyset (&sigact.sa_mask);
+        sigact.sa_flags = 0;
+        sigaction (sig_num, &sigact, NULL);
+    }
+}
+
+
+static void
+print_stats(const char * str)
+{
+    if (0 != dd_count)
+        fprintf(stderr, "  remaining block count=%"PRId64"\n", dd_count);
+    fprintf(stderr, "%s%"PRId64"+%d records in\n", str, in_full - in_partial,
+            in_partial);
+    fprintf(stderr, "%s%"PRId64"+%d records out\n", str, out_full - out_partial,
+            out_partial);
+    if (recovered_errs > 0)
+        fprintf(stderr, "%s%d recovered errors\n", str, recovered_errs);
+    if (num_retries > 0)
+        fprintf(stderr, "%s%d retries attempted\n", str, num_retries);
+    else if (unrecovered_errs)
+        fprintf(stderr, "%s%d unrecovered error(s)\n", str,
+                unrecovered_errs);
+}
+
+
+static void
+interrupt_handler(int sig)
+{
+    struct sigaction sigact;
+
+    sigact.sa_handler = SIG_DFL;
+    sigemptyset(&sigact.sa_mask);
+    sigact.sa_flags = 0;
+    sigaction(sig, &sigact, NULL);
+    fprintf(stderr, "Interrupted by signal,");
+    if (do_time)
+        calc_duration_throughput(0);
+    print_stats("");
+    kill(getpid (), sig);
+}
+
+
+static void
+siginfo_handler(int sig)
+{
+    sig = sig;  /* dummy to stop -W warning messages */
+    fprintf(stderr, "Progress report, continuing ...\n");
+    if (do_time)
+        calc_duration_throughput(1);
+    print_stats("  ");
+}
+
+static int bsg_major_checked = 0;
+static int bsg_major = 0;
+
+static void
+find_bsg_major(void)
+{
+    const char * proc_devices = "/proc/devices";
+    FILE *fp;
+    char a[128];
+    char b[128];
+    char * cp;
+    int n;
+
+    if (NULL == (fp = fopen(proc_devices, "r"))) {
+        if (verbose)
+            fprintf(stderr, "fopen %s failed: %s\n", proc_devices,
+                    strerror(errno));
+        return;
+    }
+    while ((cp = fgets(b, sizeof(b), fp))) {
+        if ((1 == sscanf(b, "%s", a)) &&
+            (0 == memcmp(a, "Character", 9)))
+            break;
+    }
+    while (cp && (cp = fgets(b, sizeof(b), fp))) {
+        if (2 == sscanf(b, "%d %s", &n, a)) {
+            if (0 == strcmp("bsg", a)) {
+                bsg_major = n;
+                break;
+            }
+        } else
+            break;
+    }
+    if (verbose > 5) {
+        if (cp)
+            fprintf(stderr, "found bsg_major=%d\n", bsg_major);
+        else
+            fprintf(stderr, "found no bsg char device in %s\n", proc_devices);
+    }
+    fclose(fp);
+}
+
+static int
+open_sg(struct xcopy_fp_t * fp, int verbose)
+{
+    int devmajor, devminor, offset;
+    struct sg_simple_inquiry_resp sir;
+    char ebuff[EBUFF_SZ];
+    int len;
+
+    devmajor = major(fp->devno);
+    devminor = minor(fp->devno);
+
+    if (fp->sg_type & FT_SG) {
+        snprintf(ebuff, EBUFF_SZ, "%s", fp->fname);
+    } else if (fp->sg_type & FT_BLOCK) {
+        int fd;
+
+        snprintf(ebuff, EBUFF_SZ, "/sys/dev/block/%d:%d/partition",
+                 devmajor, devminor);
+        if ((fd = open(ebuff, O_RDONLY)) < 0) {
+            perror("opening partition");
+        } else {
+            len = read(fd, ebuff, EBUFF_SZ);
+            if (len < 0) {
+                perror("read partition");
+            } else {
+                offset = strtoul(ebuff, NULL, 10);
+                devminor -= offset;
+            }
+            close(fd);
+        }
+        snprintf(ebuff, EBUFF_SZ, "/dev/block/%d:%d", devmajor, devminor);
+    } else {
+        snprintf(ebuff, EBUFF_SZ, "/dev/char/%d:%d", devmajor, devminor);
+    }
+    fp->sg_fd = sg_cmds_open_device(ebuff, 0, verbose);
+    if (fp->sg_fd < 0) {
+        snprintf(ebuff, EBUFF_SZ,
+                 ME "could not open %s device %d:%d for sg",
+                 fp->sg_type & FT_BLOCK ? "block" : "char",
+                 devmajor, devminor);
+        perror(ebuff);
+        return -1;
+    }
+    if (sg_simple_inquiry(fp->sg_fd, &sir, 0, verbose)) {
+        fprintf(stderr, "INQUIRY failed on %s\n", ebuff);
+        sg_cmds_close_device(fp->sg_fd);
+        fp->sg_fd = -1;
+        return fp->sg_fd;
+    }
+
+    fp->pdt = sir.peripheral_type;
+    if (verbose)
+        fprintf(stderr, "    %s: %.8s  %.16s  %.4s  [pdt=%d]\n",
+                fp->fname, sir.vendor, sir.product, sir.revision, fp->pdt);
+
+    return fp->sg_fd;
+}
+
+static int
+dd_filetype(struct xcopy_fp_t * fp)
+{
+    struct stat st;
+    size_t len = strlen(fp->fname);
+
+    if ((1 == len) && ('.' == fp->fname[0]))
+        return FT_DEV_NULL;
+    if (stat(fp->fname, &st) < 0)
+        return FT_ERROR;
+    if (S_ISCHR(st.st_mode)) {
+        fp->devno = st.st_rdev;
+        /* major() and minor() defined in sys/sysmacros.h */
+        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;
+        if (! bsg_major_checked) {
+            bsg_major_checked = 1;
+            find_bsg_major();
+        }
+        if (bsg_major == (int)major(st.st_rdev))
+            return FT_SG;
+    } else if (S_ISBLK(st.st_mode)) {
+        fp->devno = st.st_rdev;
+        return FT_BLOCK;
+    } else if (S_ISFIFO(st.st_mode)) {
+        fp->devno = st.st_dev;
+        return FT_FIFO;
+    }
+    fp->devno = st.st_dev;
+    return FT_OTHER | FT_BLOCK;
+}
+
+
+static char *
+dd_filetype_str(int ft, char * buff)
+{
+    int off = 0;
+
+    if (FT_DEV_NULL & ft)
+        off += snprintf(buff + off, 32, "null device ");
+    if (FT_SG & ft)
+        off += snprintf(buff + off, 32, "SCSI generic (sg) device ");
+    if (FT_BLOCK & ft)
+        off += snprintf(buff + off, 32, "block device ");
+    if (FT_FIFO & ft)
+        off += snprintf(buff + off, 32, "fifo (named pipe) ");
+    if (FT_ST & ft)
+        off += snprintf(buff + off, 32, "SCSI tape device ");
+    if (FT_RAW & ft)
+        off += snprintf(buff + off, 32, "raw device ");
+    if (FT_OTHER & ft)
+        off += snprintf(buff + off, 32, "other (perhaps ordinary file) ");
+    if (FT_ERROR & ft)
+        off += snprintf(buff + off, 32, "unable to 'stat' file ");
+    return buff;
+}
+
+
+static void
+usage()
+{
+    fprintf(stderr, "Usage: "
+           "sg_xcopy  [bs=BS] [count=COUNT] [ibs=BS] [if=IFILE]"
+           " [iflag=FLAGS]\n"
+           "              [obs=BS] [of=OFILE] [oflag=FLAGS] "
+           "[seek=SEEK] [skip=SKIP]\n"
+           "              [--help] [--version]\n\n"
+           "              [list_id=ID] [id_usage=hold|discard] \n"
+           "              [bpt=BPT] [cat=0|1] [dc=0|1] [odir=0|1] "
+           "[of2=OFILE2] [prio=PRIO] [retries=RETR]\n"
+           "              [time=0|1] [verbose=VERB]\n"
+           "  where:\n"
+           "    bpt         is blocks_per_transfer (default is 128 or 32 "
+           "when BS>=2048)\n"
+           "    bs          block size (default is 512)\n");
+    fprintf(stderr,
+           "    count       number of blocks to copy (def: device size)\n"
+           "    ibs         input block size (if given must be same as "
+           "'bs=')\n"
+           "    if          file or device to read from (def: stdin)\n"
+           "    iflag       comma separated list from: [cat,dc,excl,\n"
+           "                flock,null]\n"
+           "    obs         output block size (if given must be same as "
+           "'bs=')\n"
+           "    of          file or device to write to (def: stdout), "
+           "OFILE of '.'\n");
+    fprintf(stderr,
+           "                treated as /dev/null\n"
+           "    of2         additional output file (def: /dev/null), "
+           "OFILE2 should be\n"
+           "                normal file or pipe\n"
+           "    oflag       comma separated list from: [append,cat,dc,\n"
+           "                excl,flock,null]\n"
+           "    prio        Use priority PRIO (def: 1)\n"
+           "    retries     retry sgio errors RETR times (def: 0)\n"
+           "    seek        block position to start writing to OFILE\n"
+           "    skip        block position to start reading from IFILE\n"
+           "    time        0->no timing(def), 1->time plus calculate "
+           "throughput\n"
+           "    verbose     0->quiet(def), 1->some noise, 2->more noise, "
+           "etc\n"
+           "    --help      print out this usage message then exit\n"
+           "    --version   print version information then exit\n\n"
+           "copy from IFILE to OFILE, similar to dd command; "
+           "but using the EXTENDED COPY SCSI command\n");
+}
+
+static int
+scsi_extended_copy(int sg_fd, unsigned char list_id,
+                   unsigned char *src_desc, unsigned char *dst_desc,
+                   int64_t num_blk, uint64_t src_lba, uint64_t dst_lba)
+{
+    unsigned char xcopyBuff[256];
+    unsigned char *seg_desc;
+    int verb;
+
+    verb = (verbose ? verbose - 1: 0);
+
+    memset(xcopyBuff, 0, 256);
+    xcopyBuff[0] = list_id;
+    xcopyBuff[1] = (list_id_usage << 3) | priority;
+    xcopyBuff[2] = 0;
+    xcopyBuff[3] = 64; /* Two target descriptors */
+    xcopyBuff[11] = 28; /* One segment descriptor */
+    memcpy(xcopyBuff + 16, src_desc, 32);
+    memcpy(xcopyBuff + 48, dst_desc, 32);
+    seg_desc = xcopyBuff + 80;
+    seg_desc[0] = 0x02;
+    seg_desc[1] = ifp.cat | (ifp.dc << 1);
+    seg_desc[2] = 0;
+    seg_desc[3] = 0x18;
+    seg_desc[4] = 0;
+    seg_desc[5] = 0; /* Source target index */
+    seg_desc[7] = 1; /* Destination target index */
+    seg_desc[10] = (num_blk >> 8) & 0xff;
+    seg_desc[11] = num_blk & 0xff;
+    seg_desc[12] = (src_lba >> 56) & 0xff;
+    seg_desc[13] = (src_lba >> 48) & 0xff;
+    seg_desc[14] = (src_lba >> 40) & 0xff;
+    seg_desc[15] = (src_lba >> 32) & 0xff;
+    seg_desc[16] = (src_lba >> 24) & 0xff;
+    seg_desc[17] = (src_lba >> 16) & 0xff;
+    seg_desc[18] = (src_lba >> 8) & 0xff;
+    seg_desc[19] = src_lba & 0xff;
+    seg_desc[20] = (dst_lba >> 56) & 0xff;
+    seg_desc[21] = (dst_lba >> 48) & 0xff;
+    seg_desc[22] = (dst_lba >> 40) & 0xff;
+    seg_desc[23] = (dst_lba >> 32) & 0xff;
+    seg_desc[24] = (dst_lba >> 24) & 0xff;
+    seg_desc[25] = (dst_lba >> 16) & 0xff;
+    seg_desc[26] = (dst_lba >> 8) & 0xff;
+    seg_desc[27] = dst_lba & 0xff;
+
+    if (verbose > 3) {
+        fprintf(stderr, "\nParameter list in hex:\n");
+        dStrHex((const char *)xcopyBuff, 108, 1);
+    }
+    return sg_ll_extended_copy(sg_fd, xcopyBuff, 108, 0, verb);
+}
+
+/* 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 k, res;
+    unsigned int ui;
+    unsigned char rcBuff[RCAP16_REPLY_LEN];
+    int verb;
+
+    verb = (verbose ? verbose - 1: 0);
+    res = sg_ll_readcap_10(sg_fd, 0, 0, rcBuff, READ_CAP_REPLY_LEN, 0, verb);
+    if (0 != res)
+        return res;
+
+    if ((0xff == rcBuff[0]) && (0xff == rcBuff[1]) && (0xff == rcBuff[2]) &&
+        (0xff == rcBuff[3])) {
+        int64_t ls;
+
+        res = sg_ll_readcap_16(sg_fd, 0, 0, rcBuff, RCAP16_REPLY_LEN, 0,
+                               verb);
+        if (0 != res)
+            return res;
+        for (k = 0, ls = 0; k < 8; ++k) {
+            ls <<= 8;
+            ls |= rcBuff[k];
+        }
+        *num_sect = ls + 1;
+        *sect_sz = (rcBuff[8] << 24) | (rcBuff[9] << 16) |
+                   (rcBuff[10] << 8) | rcBuff[11];
+    } else {
+        ui = ((rcBuff[0] << 24) | (rcBuff[1] << 16) | (rcBuff[2] << 8) |
+              rcBuff[3]);
+        /* take care not to sign extend values > 0x7fffffff */
+        *num_sect = (int64_t)ui + 1;
+        *sect_sz = (rcBuff[4] << 24) | (rcBuff[5] << 16) |
+                   (rcBuff[6] << 8) | rcBuff[7];
+    }
+    if (verbose)
+        fprintf(stderr, "      number of blocks=%"PRId64" [0x%"PRIx64"], block "
+                "size=%d\n", *num_sect, *num_sect, *sect_sz);
+    return 0;
+}
+
+static int
+scsi_operating_parameter(int sg_fd, int type, int is_target,
+                         unsigned long *max_bytep)
+{
+    int res;
+    unsigned char rcBuff[256];
+    unsigned int rcBuffLen = 256, len, n, td_list = 0;
+    unsigned long max_segment_len, max_segment_num, held_data_limit, num;
+    int verb, valid = 0;
+
+    verb = (verbose ? verbose - 1: 0);
+    res = sg_ll_receive_copy_results(sg_fd, 0x03, 0, rcBuff, rcBuffLen, 0, verb);
+    if (0 != res)
+        return -res;
+
+    len = (rcBuff[0] << 24) | (rcBuff[1] << 16) | (rcBuff[2] << 8) | rcBuff[3];
+    if (len > rcBuffLen) {
+        fprintf(stderr, "  <<report too long for internal buffer,"
+                " output truncated\n");
+    }
+    if (verbose > 2) {
+        fprintf(stderr, "\nOutput response in hex:\n");
+        dStrHex((const char *)rcBuff, len, 1);
+    }
+    printf("Receive copy results (report operating parameters):\n");
+    num = rcBuff[8] << 8 | rcBuff[9];
+    printf("    Maximum target descriptor count: %lu\n", num);
+    max_segment_num = rcBuff[10] << 8 | rcBuff[11];
+    printf("    Maximum segment descriptor count: %lu\n", max_segment_num);
+    num = rcBuff[12] << 24 | rcBuff[13] << 16 | rcBuff[14] << 8 | rcBuff[15];
+    printf("    Maximum descriptor list length: %lu\n", num);
+    max_segment_len = rcBuff[16] << 24 | rcBuff[17] << 16 |
+        rcBuff[18] << 8 | rcBuff[19];
+    *max_bytep = max_segment_len;
+    printf("    Maximum segment length: %lu\n", max_segment_len);
+    num = rcBuff[20] << 24 | rcBuff[21] << 16 | rcBuff[22] << 8 | rcBuff[23];
+    printf("    Maximum inline data length: %lu\n", num);
+    held_data_limit = rcBuff[24] << 24 | rcBuff[25] << 16 |
+        rcBuff[26] << 8 | rcBuff[27];
+    if (list_id_usage < 0) {
+        if (!held_data_limit)
+            list_id_usage = 2;
+        else
+            list_id_usage = 0;
+    }
+    printf("    Held data limit: %lu (usage: %d)\n", held_data_limit, list_id_usage);
+    num = rcBuff[28] << 24 | rcBuff[29] << 16 | rcBuff[30] << 8 | rcBuff[31];
+    printf("    Maximum stream device transfer size: %lu\n", num);
+    printf("    Maximum concurrent copies: %u\n", rcBuff[36]);
+    printf("    Data segment granularity: %u\n", rcBuff[37]);
+    printf("    Inline data granularity: %u\n", rcBuff[38]);
+    printf("    Held data granularity: %u\n", rcBuff[39]);
+
+    printf("    Implemented descriptor list:\n");
+    for (n = 0; n < rcBuff[43]; n++) {
+        switch(rcBuff[44 + n]) {
+        case 0x00: /* copy block to stream device */
+            if (!is_target && (type & FT_BLOCK))
+                valid++;
+            if (is_target && (type & FT_ST))
+                valid++;
+            printf("        Copy Block to Stream device\n");
+            break;
+        case 0x01: /* copy stream to block device */
+            if (!is_target && (type & FT_ST))
+                valid++;
+            if (is_target && (type & FT_BLOCK))
+                valid++;
+            printf("        Copy Stream to Block device\n");
+            break;
+        case 0x02: /* copy block to block device */
+            if (!is_target && (type & FT_BLOCK))
+                valid++;
+            if (is_target && (type & FT_BLOCK))
+                valid++;
+            printf("        Copy Block to Block device\n");
+            break;
+        case 0x03: /* copy stream to stream device */
+            if (!is_target && (type & FT_ST))
+                valid++;
+            if (is_target && (type & FT_ST))
+                valid++;
+            printf("        Copy Stream to Stream device\n");
+            break;
+        case 0xe0: /* FC N_Port_Name */
+            printf("        FC N_Port_Name target descriptor\n");
+            td_list |= TD_FC_WWPN;
+            break;
+        case 0xe1: /* FC Port_ID */
+            printf("        FC Port_ID target descriptor\n");
+            td_list |= TD_FC_PORT;
+            break;
+        case 0xe2: /* FC N_Port_ID with N_Port_Name checking */
+            printf("        FC N_Port_ID with N_Port_Name target descriptor\n");
+            td_list |= TD_FC_WWPN_AND_PORT;
+            break;
+        case 0xe3: /* Parallel Interface T_L  */
+            printf("        SPI T_L target descriptor\n");
+            td_list |= TD_SPI;
+            break;
+        case 0xe4: /* identification descriptor */
+            printf("        Identification target descriptor\n");
+            td_list |= TD_VPD;
+            break;
+        case 0xe5: /* IPv4  */
+            printf("        IPv4 target descriptor\n");
+            td_list |= TD_IPV4;
+            break;
+        case 0xe6: /* Alias */
+            printf("        Alias target descriptor\n");
+            td_list |= TD_ALIAS;
+            break;
+        case 0xe7: /* RDMA */
+            printf("        RDMA target descriptor\n");
+            td_list |= TD_RDMA;
+            break;
+        case 0xe8: /* FireWire */
+            printf("        IEEE 1394 target descriptor\n");
+            td_list |= TD_FW;
+            break;
+        case 0xe9: /* SAS */
+            printf("        SAS target descriptor\n");
+            td_list |= TD_SAS;
+            break;
+        default:
+            printf("        Unhandled target descriptor 0x%02x\n",
+                   rcBuff[44 + n]);
+            break;
+        }
+    }
+    if (!valid) {
+        fprintf(stderr, ">> no matching target descriptor supported\n");
+        td_list = 0;
+    }
+    return td_list;
+}
+
+static void
+decode_designation_descriptor(const unsigned char * ucp, int i_len)
+{
+    int m, p_id, piv, c_set, assoc, desig_type, d_id, naa;
+    int k;
+    const unsigned char * ip;
+    uint64_t vsei;
+    char b[64];
+
+    ip = ucp + 4;
+    p_id = ((ucp[0] >> 4) & 0xf);
+    c_set = (ucp[0] & 0xf);
+    piv = ((ucp[1] & 0x80) ? 1 : 0);
+    assoc = ((ucp[1] >> 4) & 0x3);
+    desig_type = (ucp[1] & 0xf);
+    printf("    designator type: %d,  code set: %d\n", desig_type, c_set);
+    if (piv && ((1 == assoc) || (2 == assoc)))
+        printf("     transport: %s\n",
+               sg_get_trans_proto_str(p_id, sizeof(b), b));
+    /* printf("    associated with the %s\n", assoc_arr[assoc]); */
+    switch (desig_type) {
+    case 0: /* vendor specific */
+        k = 0;
+        if ((1 == c_set) || (2 == c_set)) { /* ASCII or UTF-8 */
+            for (k = 0; (k < i_len) && isprint(ip[k]); ++k)
+                ;
+            if (k >= i_len)
+                k = 1;
+        }
+        if (k)
+            printf("      vendor specific: %.*s\n", i_len, ip);
+        else
+            dStrHex((const char *)ip, i_len, 0);
+        break;
+    case 1: /* T10 vendor identification */
+        printf("      vendor id: %.8s\n", ip);
+        if (i_len > 8)
+            printf("      vendor specific: %.*s\n", i_len - 8, ip + 8);
+        break;
+    case 2: /* EUI-64 based */
+        if ((8 != i_len) && (12 != i_len) && (16 != i_len)) {
+            fprintf(stderr, "      << expect 8, 12 and 16 byte "
+                    "EUI, got %d>>\n", i_len);
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        printf("      0x");
+        for (m = 0; m < i_len; ++m)
+            printf("%02x", (unsigned int)ip[m]);
+        printf("\n");
+        break;
+    case 3: /* NAA */
+        if (1 != c_set) {
+            fprintf(stderr, "      << unexpected code set %d for "
+                    "NAA>>\n", c_set);
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        naa = (ip[0] >> 4) & 0xff;
+        if (! ((2 == naa) || (5 == naa) || (6 == naa))) {
+            fprintf(stderr, "      << unexpected NAA [0x%x]>>\n", naa);
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        if ((5 == naa) && (0x10 == i_len)) {
+            if (verbose > 2)
+                fprintf(stderr, "      << unexpected NAA 5 len 16, assuming "
+                        "NAA 6 >>\n");
+            naa = 6;
+        }
+        if (2 == naa) {
+            if (8 != i_len) {
+                fprintf(stderr, "      << unexpected NAA 2 identifier "
+                        "length: 0x%x>>\n", i_len);
+                dStrHex((const char *)ip, i_len, 0);
+                break;
+            }
+            d_id = (((ip[0] & 0xf) << 8) | ip[1]);
+            /* c_id = ((ip[2] << 16) | (ip[3] << 8) | ip[4]); */
+            /* vsi = ((ip[5] << 16) | (ip[6] << 8) | ip[7]); */
+            printf("      0x");
+            for (m = 0; m < 8; ++m)
+                printf("%02x", (unsigned int)ip[m]);
+            printf("\n");
+        } else if (5 == naa) {
+            if (8 != i_len) {
+                fprintf(stderr, "      << unexpected NAA 5 identifier "
+                        "length: 0x%x>>\n", i_len);
+                dStrHex((const char *)ip, i_len, 0);
+                break;
+            }
+            /* c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | */
+                    /* (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); */
+            vsei = ip[3] & 0xf;
+            for (m = 1; m < 5; ++m) {
+                vsei <<= 8;
+                vsei |= ip[3 + m];
+            }
+            printf("      0x");
+            for (m = 0; m < 8; ++m)
+                printf("%02x", (unsigned int)ip[m]);
+            printf("\n");
+        } else if (6 == naa) {
+            if (16 != i_len) {
+                fprintf(stderr, "      << unexpected NAA 6 identifier "
+                        "length: 0x%x>>\n", i_len);
+                dStrHex((const char *)ip, i_len, 0);
+                break;
+            }
+            /* c_id = (((ip[0] & 0xf) << 20) | (ip[1] << 12) | */
+                    /* (ip[2] << 4) | ((ip[3] & 0xf0) >> 4)); */
+            vsei = ip[3] & 0xf;
+            for (m = 1; m < 5; ++m) {
+                vsei <<= 8;
+                vsei |= ip[3 + m];
+            }
+            printf("      0x");
+            for (m = 0; m < 16; ++m)
+                printf("%02x", (unsigned int)ip[m]);
+            printf("\n");
+        }
+        break;
+    case 4: /* Relative target port */
+        if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+            fprintf(stderr, "      << expected binary code_set, target "
+                    "port association, length 4>>\n");
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        d_id = ((ip[2] << 8) | ip[3]);
+        printf("      Relative target port: 0x%x\n", d_id);
+        break;
+    case 5: /* (primary) Target port group */
+        if ((1 != c_set) || (1 != assoc) || (4 != i_len)) {
+            fprintf(stderr, "      << expected binary code_set, target "
+                    "port association, length 4>>\n");
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        d_id = ((ip[2] << 8) | ip[3]);
+        printf("      Target port group: 0x%x\n", d_id);
+        break;
+    case 6: /* Logical unit group */
+        if ((1 != c_set) || (0 != assoc) || (4 != i_len)) {
+            fprintf(stderr, "      << expected binary code_set, logical "
+                    "unit association, length 4>>\n");
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        d_id = ((ip[2] << 8) | ip[3]);
+        printf("      Logical unit group: 0x%x\n", d_id);
+        break;
+    case 7: /* MD5 logical unit identifier */
+        if ((1 != c_set) || (0 != assoc)) {
+            printf("      << expected binary code_set, logical "
+                   "unit association>>\n");
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        printf("      MD5 logical unit identifier:\n");
+        dStrHex((const char *)ip, i_len, 0);
+        break;
+    case 8: /* SCSI name string */
+        if (3 != c_set) {
+            fprintf(stderr, "      << expected UTF-8 code_set>>\n");
+            dStrHex((const char *)ip, i_len, 0);
+            break;
+        }
+        printf("      SCSI name string:\n");
+        /* does %s print out UTF-8 ok??
+         * Seems to depend on the locale. Looks ok here with my
+         * locale setting: en_AU.UTF-8
+         */
+        printf("      %s\n", (const char *)ip);
+        break;
+    default: /* reserved */
+        dStrHex((const char *)ip, i_len, 0);
+        break;
+    }
+}
+
+static int
+desc_from_vpd_id(int sg_fd, unsigned char *desc, int desc_len,
+                 unsigned int block_size)
+{
+    int res;
+    unsigned char rcBuff[256], *ucp, *best = NULL;
+    unsigned int len = 254;
+    int off = -1, u, i_len, best_len = 0, assoc, desig, f_desig = 0;
+
+    memset(rcBuff, 0xff, len);
+    res = sg_ll_inquiry(sg_fd, 0, 1, 0x83, rcBuff, 4, 1, verbose);
+    if (0 != res) {
+        fprintf(stderr, "VPD inquiry failed with %d\n", res);
+        return res;
+    } else if (rcBuff[1] != 0x83) {
+        fprintf(stderr, "invalid VPD response\n");
+        return SG_LIB_CAT_MALFORMED;
+    }
+    len = ((rcBuff[2] << 8) + rcBuff[3]) + 4;
+    res = sg_ll_inquiry(sg_fd, 0, 1, 0x83, rcBuff, len, 1, verbose);
+    if (0 != res) {
+        fprintf(stderr, "VPD inquiry failed with %d\n", res);
+        return res;
+    } else if (rcBuff[1] != 0x83) {
+        fprintf(stderr, "invalid VPD response\n");
+        return SG_LIB_CAT_MALFORMED;
+    }
+    if (verbose > 2) {
+        fprintf(stderr, "Output response in hex:\n");
+        dStrHex((const char *)rcBuff, len, 1);
+    }
+
+    while ((u = sg_vpd_dev_id_iter(rcBuff + 4, len - 4, &off, 0, -1, -1)) == 0) {
+        ucp = rcBuff + 4 + off;
+        i_len = ucp[3];
+        if (((unsigned int)off + i_len + 4) > len) {
+            fprintf(stderr, "    VPD page error: designator length %d longer "
+                    "than\n     remaining response length=%d\n", i_len, (len - off));
+            return SG_LIB_CAT_MALFORMED;
+        }
+        assoc = ((ucp[1] >> 4) & 0x3);
+        desig = (ucp[1] & 0xf);
+        fprintf(stderr, "    Desc %d: assoc %u desig %u len %d\n", off,
+                assoc, desig, i_len);
+        /* Descriptor must be less than 16 bytes */
+        if (i_len > 16)
+            continue;
+        if (desig == 3) {
+            best = ucp;
+            best_len = i_len;
+            break;
+        }
+        if (desig == 2) {
+            if (!best || f_desig < 2) {
+                best = ucp;
+                best_len = i_len;
+                f_desig = 2;
+            }
+        } else if (desig == 1) {
+            if (!best || f_desig == 0) {
+                best = ucp;
+                best_len = i_len;
+                f_desig = desig;
+            }
+        } else if (desig == 0) {
+            if (!best) {
+                best = ucp;
+                best_len = i_len;
+                f_desig = desig;
+            }
+        }
+    }
+    if (best) {
+        decode_designation_descriptor(best, best_len);
+        if (best_len + 4 < desc_len) {
+            memset(desc, 0, 32);
+            desc[0] = 0xe4;
+            memcpy(desc + 4, best, best_len + 4);
+            desc[4] &= 0x1f;
+            desc[29] = (block_size >> 16) & 0xff;
+            desc[30] = (block_size >> 8) & 0xff;
+            desc[31] = block_size & 0xff;
+            if (verbose > 3) {
+                fprintf(stderr, "Descriptor in hex (bs %d):\n", block_size);
+                dStrHex((const char *)desc, 32, 1);
+            }
+            return 32;
+        }
+        return  best_len + 8;
+    }
+    return 0;
+}
+
+static void
+calc_duration_throughput(int contin)
+{
+    struct timeval end_tm, res_tm;
+    double a, b;
+    int64_t blks;
+
+    if (start_tm_valid && (start_tm.tv_sec || start_tm.tv_usec)) {
+        blks = (in_full > out_full) ? in_full : out_full;
+        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)blk_sz * blks;
+        fprintf(stderr, "time to transfer data%s: %d.%06d secs",
+                (contin ? " so far" : ""), (int)res_tm.tv_sec,
+                (int)res_tm.tv_usec);
+        if ((a > 0.00001) && (b > 511))
+            fprintf(stderr, " at %.2f MB/sec\n", b / (a * 1000000.0));
+        else
+            fprintf(stderr, "\n");
+    }
+}
+
+static int
+process_flags(const char * arg, struct xcopy_fp_t * fp)
+{
+    char buff[256];
+    char * cp;
+    char * np;
+
+    strncpy(buff, arg, sizeof(buff));
+    buff[sizeof(buff) - 1] = '\0';
+    if ('\0' == buff[0]) {
+        fprintf(stderr, "no flag found\n");
+        return 1;
+    }
+    cp = buff;
+    do {
+        np = strchr(cp, ',');
+        if (np)
+            *np++ = '\0';
+        if (0 == strcmp(cp, "append"))
+            fp->append = 1;
+        else if (0 == strcmp(cp, "dc"))
+            ++fp->dc;
+        else if (0 == strcmp(cp, "excl"))
+            fp->excl = 1;
+        else if (0 == strcmp(cp, "null"))
+            ;
+        else if (0 == strcmp(cp, "cat"))
+            ++fp->cat;
+        else if (0 == strcmp(cp, "flock"))
+            ++fp->flock;
+        else {
+            fprintf(stderr, "unrecognised flag: %s\n", cp);
+            return 1;
+        }
+        cp = np;
+    } while (cp);
+    return 0;
+}
+
+/* Returns open input file descriptor (>= 0) or a negative value
+ * (-SG_LIB_FILE_ERROR or -SG_LIB_CAT_OTHER) if error.
+ */
+static int
+open_if(struct xcopy_fp_t * ifp, int verbose)
+{
+    int infd = -1, flags, fl, res;
+    char ebuff[EBUFF_SZ];
+
+    ifp->sg_type = dd_filetype(ifp);
+
+    if (verbose)
+        fprintf(stderr, " >> Input file type: %s, devno %d:%d\n",
+                dd_filetype_str(ifp->sg_type, ebuff),
+                major(ifp->devno), minor(ifp->devno));
+    if (FT_ERROR & ifp->sg_type) {
+        fprintf(stderr, ME "unable access %s\n", ifp->fname);
+        goto file_err;
+    }
+    flags = O_NONBLOCK;
+    if (ifp->excl)
+        flags |= O_EXCL;
+    fl = O_RDWR;
+    if ((infd = open(ifp->fname, fl | flags)) < 0) {
+        fl = O_RDONLY;
+        if ((infd = open(ifp->fname, fl | flags)) < 0) {
+            snprintf(ebuff, EBUFF_SZ,
+                     ME "could not open %s for sg reading", ifp->fname);
+            perror(ebuff);
+            goto file_err;
+        }
+    }
+    if (verbose)
+        fprintf(stderr, "        open input(sg_io), flags=0x%x\n",
+                fl | flags);
+
+    if (ifp->flock) {
+        res = flock(infd, LOCK_EX | LOCK_NB);
+        if (res < 0) {
+            close(infd);
+            snprintf(ebuff, EBUFF_SZ, ME "flock(LOCK_EX | LOCK_NB) on %s "
+                     "failed", ifp->fname);
+            perror(ebuff);
+            return -SG_LIB_FLOCK_ERR;
+        }
+    }
+    return infd;
+
+file_err:
+    if (infd >= 0)
+        close(infd);
+    return -SG_LIB_FILE_ERROR;
+}
+
+/* Returns open output file descriptor (>= 0), -1 for don't
+ * bother opening (e.g. /dev/null), or a more negative value
+ * (-SG_LIB_FILE_ERROR or -SG_LIB_CAT_OTHER) if error.
+ */
+static int
+open_of(struct xcopy_fp_t * ofp, int verbose)
+{
+    int outfd, flags, verb, res;
+    char ebuff[EBUFF_SZ];
+    struct sg_simple_inquiry_resp sir;
+
+    verb = (verbose ? verbose - 1: 0);
+    ofp->sg_type = dd_filetype(ofp);
+    if (verbose)
+        fprintf(stderr, " >> Output file type: %s, devno %d:%d\n",
+                dd_filetype_str(ofp->sg_type, ebuff),
+                major(ofp->devno), minor(ofp->devno));
+
+    ofp->sg_type |= FT_SG;
+
+    if (!(FT_DEV_NULL & ofp->sg_type)) {
+        flags = O_RDWR | O_NONBLOCK;
+        if (ofp->excl)
+            flags |= O_EXCL;
+        if ((outfd = open(ofp->fname, flags)) < 0) {
+            snprintf(ebuff, EBUFF_SZ,
+                     ME "could not open %s for sg writing", ofp->fname);
+            perror(ebuff);
+            goto file_err;
+        }
+        if (verbose)
+            fprintf(stderr, "        open output(sg_io), flags=0x%x\n",
+                    flags);
+        if (sg_simple_inquiry(outfd, &sir, 0, verb)) {
+            fprintf(stderr, "INQUIRY failed on %s\n", ofp->fname);
+            goto other_err;
+        }
+        ofp->pdt = sir.peripheral_type;
+        if (verbose)
+            fprintf(stderr, "    %s: %.8s  %.16s  %.4s  [pdt=%d]\n",
+                    ofp->fname, sir.vendor, sir.product, sir.revision, ofp->pdt);
+    } else {
+        outfd = -1; /* don't bother opening */
+    }
+    if (ofp->flock) {
+        res = flock(outfd, LOCK_EX | LOCK_NB);
+        if (res < 0) {
+            close(outfd);
+            snprintf(ebuff, EBUFF_SZ, ME "flock(LOCK_EX | LOCK_NB) on %s "
+                     "failed", ofp->fname);
+            perror(ebuff);
+            return -SG_LIB_FLOCK_ERR;
+        }
+    }
+    return outfd;
+
+file_err:
+    return -SG_LIB_FILE_ERROR;
+other_err:
+    return -SG_LIB_CAT_OTHER;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+    int64_t skip = 0;
+    int64_t seek = 0;
+    int ibs = 0;
+    int obs = 0;
+    int bpt = DEF_BLOCKS_PER_TRANSFER;
+    int bpt_given = 0;
+    char str[STR_SZ];
+    char * key;
+    char * buf;
+    int blocks = 0;
+    int res, k;
+    int infd, outfd;
+    int64_t in_num_sect = -1;
+    int64_t out_num_sect = -1;
+    int in_sect_sz, out_sect_sz;
+    int ret = 0;
+    unsigned long max_bytes_in, max_bytes_out;
+    unsigned char list_id = 1;
+    unsigned char src_desc[256];
+    unsigned char dst_desc[256];
+    /* int src_desc_len = 256; */
+    /* int dst_desc_len = 256; */
+
+    ifp.fname[0] = '\0';
+    ofp.fname[0] = '\0';
+
+    if (argc < 2) {
+        fprintf(stderr,
+                "Won't default both IFILE to stdin _and_ OFILE to stdout\n");
+        fprintf(stderr, "For more information use '--help'\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+    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';
+        if (0 == strncmp(key, "app", 3)) {
+            ifp.append = sg_get_num(buf);
+            ofp.append = ifp.append;
+        } else if (0 == strcmp(key, "bpt")) {
+            bpt = sg_get_num(buf);
+            if (-1 == bpt) {
+                fprintf(stderr, ME "bad argument to 'bpt='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            bpt_given = 1;
+        } else if (0 == strcmp(key, "bs")) {
+            blk_sz = sg_get_num(buf);
+            if (-1 == blk_sz) {
+                fprintf(stderr, ME "bad argument to 'bs='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "list_id")) {
+            ret = sg_get_num(buf);
+            if (-1 == ret || ret > 0xff) {
+                fprintf(stderr, ME "bad argument to 'list_id='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            list_id = (ret & 0xff);
+        } else if (0 == strcmp(key, "id_usage")) {
+            if (!strncmp(buf, "hold", 4))
+                list_id_usage = 0;
+            else if (!strncmp(buf, "discard", 7))
+                list_id_usage = 2;
+            else {
+                fprintf(stderr, ME "bad argument to 'list_id_usage='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "conv")) {
+            if (process_flags(buf, &ofp)) {
+                fprintf(stderr, ME "bad argument to 'conv='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "count")) {
+            if (0 != strcmp("-1", buf)) {
+                dd_count = sg_get_llnum(buf);
+                if (-1LL == dd_count) {
+                    fprintf(stderr, ME "bad argument to 'count='\n");
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+            }   /* treat 'count=-1' as calculate count (same as not given) */
+        } else if (0 == strcmp(key, "prio")) {
+            priority = sg_get_num(buf);
+        } else if (0 == strcmp(key, "cat")) {
+            ofp.cat = sg_get_num(buf);
+            ifp.cat = ofp.cat;
+        } else if (0 == strcmp(key, "dc")) {
+            /* t = sg_get_num(buf); */
+            ofp.dc = sg_get_num(buf);
+            ifp.dc = ofp.dc;
+        } else if (0 == strcmp(key, "ibs")) {
+            ibs = sg_get_num(buf);
+        } else if (strcmp(key, "if") == 0) {
+            if ('\0' != ifp.fname[0]) {
+                fprintf(stderr, "Second IFILE argument??\n");
+                return SG_LIB_SYNTAX_ERROR;
+            } else
+                strncpy(ifp.fname, buf, INOUTF_SZ);
+        } else if (0 == strcmp(key, "iflag")) {
+            if (process_flags(buf, &ifp)) {
+                fprintf(stderr, ME "bad argument to 'iflag='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "obs")) {
+            obs = sg_get_num(buf);
+        } else if (strcmp(key, "of") == 0) {
+            if ('\0' != ofp.fname[0]) {
+                fprintf(stderr, "Second OFILE argument??\n");
+                return SG_LIB_SYNTAX_ERROR;
+            } else
+                strncpy(ofp.fname, buf, INOUTF_SZ);
+        } else if (0 == strcmp(key, "oflag")) {
+            if (process_flags(buf, &ofp)) {
+                fprintf(stderr, ME "bad argument to 'oflag='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "retries")) {
+            ifp.retries = sg_get_num(buf);
+            ofp.retries = ifp.retries;
+            if (-1 == ifp.retries) {
+                fprintf(stderr, ME "bad argument to 'retries='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "seek")) {
+            seek = sg_get_llnum(buf);
+            if (-1LL == seek) {
+                fprintf(stderr, ME "bad argument to 'seek='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "skip")) {
+            skip = sg_get_llnum(buf);
+            if (-1LL == skip) {
+                fprintf(stderr, ME "bad argument to 'skip='\n");
+                return SG_LIB_SYNTAX_ERROR;
+            }
+        } else if (0 == strcmp(key, "time"))
+            do_time = sg_get_num(buf);
+        else if (0 == strncmp(key, "verb", 4))
+            verbose = sg_get_num(buf);
+        else if ((0 == strncmp(key, "--help", 7)) ||
+                   (0 == strcmp(key, "-?"))) {
+            usage();
+            return 0;
+        } else if ((0 == strncmp(key, "--vers", 6)) ||
+                   (0 == strcmp(key, "-V"))) {
+            fprintf(stderr, ME "%s\n", version_str);
+            return 0;
+        } else {
+            fprintf(stderr, "Unrecognized option '%s'\n", key);
+            fprintf(stderr, "For more information use '--help'\n");
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
+    if (blk_sz <= 0) {
+        blk_sz = DEF_BLOCK_SIZE;
+        fprintf(stderr, "Assume default 'bs' (block size) of %d bytes\n",
+                blk_sz);
+    }
+    if ((ibs && (ibs != blk_sz)) || (obs && (obs != blk_sz))) {
+        fprintf(stderr, "If 'ibs' or 'obs' given must be same as 'bs'\n");
+        fprintf(stderr, "For more information use '--help'\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+    if ((skip < 0) || (seek < 0)) {
+        fprintf(stderr, "skip and seek cannot be negative\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+    if ((ofp.append > 0) && (seek > 0)) {
+        fprintf(stderr, "Can't use both append and seek switches\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+    if (bpt < 1) {
+        fprintf(stderr, "bpt must be greater than 0\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+#ifdef SG_DEBUG
+    fprintf(stderr, ME "if=%s skip=%"PRId64" of=%s seek=%"PRId64" count=%"PRId64"\n",
+           ifp.fname, skip, ofp.fname, seek, dd_count);
+#endif
+    install_handler(SIGINT, interrupt_handler);
+    install_handler(SIGQUIT, interrupt_handler);
+    install_handler(SIGPIPE, interrupt_handler);
+    install_handler(SIGUSR1, siginfo_handler);
+
+    infd = STDIN_FILENO;
+    outfd = STDOUT_FILENO;
+    ifp.pdt = -1;
+    ofp.pdt = -1;
+    if (ifp.fname[0] && ('-' != ifp.fname[0])) {
+        infd = open_if(&ifp, verbose);
+        if (infd < 0)
+            return -infd;
+    }
+
+    if (ofp.fname[0] && ('-' != ofp.fname[0])) {
+        outfd = open_of(&ofp, verbose);
+        if (outfd < -1)
+            return -outfd;
+    }
+
+    if (open_sg(&ifp, verbose) < 0)
+        return SG_LIB_CAT_INVALID_OP;
+
+    if (open_sg(&ofp, verbose) < 0)
+        return SG_LIB_CAT_INVALID_OP;
+
+    if ((STDIN_FILENO == infd) && (STDOUT_FILENO == outfd)) {
+        fprintf(stderr,
+                "Can't have both 'if' as stdin _and_ 'of' as stdout\n");
+        fprintf(stderr, "For more information use '--help'\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
+
+    res = scsi_read_capacity(ifp.sg_fd, &in_num_sect, &in_sect_sz);
+    if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+        fprintf(stderr, "Unit attention (readcap in), continuing\n");
+        res = scsi_read_capacity(ifp.sg_fd, &in_num_sect, &in_sect_sz);
+    } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+        fprintf(stderr, "Aborted command (readcap in), continuing\n");
+        res = scsi_read_capacity(infd, &in_num_sect, &in_sect_sz);
+    }
+    if (0 != res) {
+        if (res == SG_LIB_CAT_INVALID_OP)
+            fprintf(stderr, "read capacity not supported on %s\n",
+                    ifp.fname);
+        else if (res == SG_LIB_CAT_NOT_READY)
+            fprintf(stderr, "read capacity failed on %s - not "
+                    "ready\n", ifp.fname);
+        else
+            fprintf(stderr, "Unable to read capacity on %s\n", ifp.fname);
+        in_num_sect = -1;
+    } else if (in_sect_sz != blk_sz) {
+        fprintf(stderr, ">> warning: block size on %s confusion: "
+                "bs=%d, device claims=%d\n", ifp.fname, blk_sz, in_sect_sz);
+    }
+
+    res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+    if (SG_LIB_CAT_UNIT_ATTENTION == res) {
+        fprintf(stderr, "Unit attention (readcap out), continuing\n");
+        res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+    } else if (SG_LIB_CAT_ABORTED_COMMAND == res) {
+        fprintf(stderr,
+                "Aborted command (readcap out), continuing\n");
+        res = scsi_read_capacity(outfd, &out_num_sect, &out_sect_sz);
+    }
+    if (0 != res) {
+        if (res == SG_LIB_CAT_INVALID_OP)
+            fprintf(stderr, "read capacity not supported on %s\n",
+                    ofp.fname);
+        else
+            fprintf(stderr, "Unable to read capacity on %s\n", ofp.fname);
+        out_num_sect = -1;
+    } else if (blk_sz != out_sect_sz) {
+        fprintf(stderr, ">> warning: block size on %s confusion: "
+                "bs=%d, device claims=%d\n", ofp.fname, blk_sz,
+                out_sect_sz);
+    }
+    if ((dd_count < 0) || ((verbose > 0) && (0 == dd_count))) {
+        if (skip && in_num_sect > skip)
+            in_num_sect -= skip;
+        if (skip && out_num_sect > skip)
+            out_num_sect -= skip;
+        if (out_num_sect > seek)
+            out_num_sect -= seek;
+
+        if (dd_count < 0) {
+            if (in_num_sect > 0) {
+                if (out_num_sect > 0)
+                    dd_count = (in_num_sect > out_num_sect) ? out_num_sect :
+                                                           in_num_sect;
+                else
+                    dd_count = in_num_sect;
+            } else
+                dd_count = out_num_sect;
+        }
+    }
+#ifdef SG_DEBUG
+    fprintf(stderr,
+            "Start of loop, count=%"PRId64", lba_in=%"PRId64", "
+            "in_num_sect=%"PRId64", lba_out=%"PRId64", out_num_sect=%"PRId64"\n",
+            dd_count, skip, in_num_sect, skip + seek, out_num_sect);
+#endif
+
+    res = scsi_operating_parameter(infd, ifp.sg_type, 0, &max_bytes_in);
+    if (res < 0) {
+        if (SG_LIB_CAT_UNIT_ATTENTION == -res) {
+            fprintf(stderr, "Unit attention (oper parm), continuing\n");
+            res = scsi_operating_parameter(infd, ifp.sg_type, 0, &max_bytes_in);
+        } else {
+            if (-res == SG_LIB_CAT_INVALID_OP) {
+                fprintf(stderr, "receive copy results not supported on %s\n",
+                        ifp.fname);
+#ifndef SG_DEBUG
+                return EINVAL;
+#endif
+            } else if (-res == SG_LIB_CAT_NOT_READY)
+                fprintf(stderr, "receive copy results failed on %s - not "
+                        "ready\n", ifp.fname);
+            else {
+                fprintf(stderr, "Unable to receive copy results on %s\n",
+                        ifp.fname);
+                return -res;
+            }
+        }
+    } else if (res == 0)
+        return SG_LIB_CAT_INVALID_OP;
+
+    if (res & TD_VPD) {
+        printf("  >> using VPD identification for source %s\n", ifp.fname);
+        res = desc_from_vpd_id(infd, src_desc, 256, in_sect_sz);
+        if (res > 256) {
+            fprintf(stderr, "source descriptor too large (%d bytes)\n", res);
+            return SG_LIB_CAT_MALFORMED;
+        }
+        /* src_desc_len = res; */
+    } else {
+        return SG_LIB_CAT_INVALID_OP;
+    }
+
+    res = scsi_operating_parameter(outfd, ofp.sg_type, 1, &max_bytes_out);
+    if (res < 0) {
+        if (SG_LIB_CAT_UNIT_ATTENTION == -res) {
+            fprintf(stderr, "Unit attention (oper parm), continuing\n");
+            res = scsi_operating_parameter(outfd, ofp.sg_type, 1, &max_bytes_out);
+        } else {
+            if (-res == SG_LIB_CAT_INVALID_OP) {
+                fprintf(stderr, "receive copy results not supported on %s\n",
+                        ifp.fname);
+#ifndef SG_DEBUG
+                return EINVAL;
+#endif
+            } else if (-res == SG_LIB_CAT_NOT_READY)
+                fprintf(stderr, "receive copy results failed on %s - not "
+                        "ready\n", ifp.fname);
+            else {
+                fprintf(stderr, "Unable to receive copy results on %s\n",
+                        ofp.fname);
+                return -res;
+            }
+        }
+    } else if (res == 0)
+        return SG_LIB_CAT_INVALID_OP;
+
+    if (res & TD_VPD) {
+        printf("  >> using VPD identification for destination %s\n",
+               ofp.fname);
+        res = desc_from_vpd_id(outfd, dst_desc, 256, out_sect_sz);
+        if (res > 256) {
+            fprintf(stderr, "destination descriptor too large (%d bytes)\n",
+                    res);
+            return SG_LIB_CAT_MALFORMED;
+        }
+        /* dst_desc_len = res; */
+    } else {
+        return SG_LIB_CAT_INVALID_OP;
+    }
+
+    if (dd_count < 0) {
+        fprintf(stderr, "Couldn't calculate count, please give one\n");
+        return SG_LIB_CAT_OTHER;
+    }
+
+    if (0 == bpt_given)
+        bpt = max_bytes_in / in_sect_sz;
+    if (max_bytes_out / out_sect_sz < (uint64_t)bpt)
+        bpt = max_bytes_out / out_sect_sz;
+    if (bpt > dd_count)
+        bpt = dd_count;
+
+    if (do_time) {
+        start_tm.tv_sec = 0;
+        start_tm.tv_usec = 0;
+        gettimeofday(&start_tm, NULL);
+        start_tm_valid = 1;
+    }
+
+    while (dd_count > 0) {
+        if (dd_count > bpt)
+            blocks = bpt;
+        else
+            blocks = dd_count;
+        res = scsi_extended_copy(infd, list_id, src_desc, dst_desc,
+                                 blocks, skip, skip + seek);
+        if (res != 0)
+            break;
+        in_full += blocks;
+        skip += blocks;
+        dd_count -= blocks;
+    }
+
+    if (do_time)
+        calc_duration_throughput(0);
+
+    return res;
+}